当前位置:首页>python>NumPy库——Python数据科学的基石

NumPy库——Python数据科学的基石

  • 2026-10-10 23:33:03
NumPy库——Python数据科学的基石

NumPy(Numerical Python)是Python科学计算的核心库。它提供了一个高性能的多维数组对象 ndarray 以及用于处理这些数组的工具。无论是数据分析、机器学习还是图像处理,NumPy都是不可或缺的底层支撑。


一、为什么选择NumPy?(高效性分析)

1.1 Python原生循环的瓶颈

在Python中,使用原生 for 循环处理大量数据时,由于动态类型检查和解释器开销,速度非常慢。

【代码实践】对比测试:

import numpy as np
import time

# 生成100万个数据
data_list = list(range(1000000))
data_array = np.arange(1000000)

# 测试Python原生循环
start_time = time.time()
result_list = [x * 2for x in data_list]
end_time = time.time()
print(f"Python列表推导式耗时: {end_time - start_time:.4f}秒")

# 测试NumPy向量化运算
start_time = time.time()
result_array = data_array * 2
end_time = time.time()
print(f"NumPy向量化运算耗时: {end_time - start_time:.4f}秒")

# 验证结果一致性
print(f"结果一致: {np.allclose(result_list, result_array)}")

运行结果示例:
Python列表推导式耗时: 0.0800 秒
NumPy向量化运算耗时: 0.0020 秒
结论:NumPy的速度通常是原生Python的几十倍甚至上百倍。

1.2 NumPy高效的三大秘诀

  1. 底层C语言实现
    :核心算法由C编写,绕过了解释器循环。
  2. 内存连续性
    :数组元素在内存中连续存储,且类型统一(如全是 int32),减少了内存碎片和类型判断开销。
  3. 向量化(Vectorization)
    :将循环操作转化为底层的SIMD(单指令多数据)指令集并行执行。

二、NumPy数组的创建方法

2.1 从Python列表创建

import numpy as np

# 从列表创建
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([[1, 2], [3, 4]], dtype=float) # 指定数据类型为浮点型

print(f"一维数组: {arr1}")
print(f"二维数组:\n{arr2}")
print(f"数据类型: {arr2.dtype}")

2.2 初始化特定数组

NumPy提供了多种快速生成特定模式数组的函数。

函数
描述
代码示例
输出形状/内容
np.zeros
全零数组
np.zeros((3, 4))
3行4列的0
np.ones
全一数组
np.ones((2, 2), dtype=int)
2x2的整数1
np.full
填充指定值
np.full((2, 2), 7)
2x2的全7数组
np.eye
单位矩阵
np.eye(3)
3x3对角线为1
np.arange
等差数列
np.arange(0, 10, 2)[0, 2, 4, 6, 8]
np.linspace
等间隔数列
np.linspace(0, 1, 5)[0., 0.25, 0.5, 0.75, 1.]
np.logspace
等比数列
np.logspace(0, 2, 3)[1, 10, 100]
 (100到102)

【代码实践】:

# 创建一个3x3的单位矩阵
identity = np.eye(3)
print("单位矩阵:\n", identity)

# 创建从0到1均匀分布的5个数
linear = np.linspace(0, 1, 5)
print("线性空间:", linear)

2.3 随机数组生成

np.random 模块是模拟数据和初始化的利器。

# 设置随机种子,保证结果可复现
np.random.seed(42)

# 1. 均匀分布 [0, 1)
rand_arr = np.random.rand(2, 3) 
print("均匀分布:\n", rand_arr)

# 2. 标准正态分布 (均值0, 方差1)
randn_arr = np.random.randn(2, 2)
print("正态分布:\n", randn_arr)

# 3. 随机整数 [low, high)
randint_arr = np.random.randint(1, 10, size=(2, 3))
print("随机整数:\n", randint_arr)

# 4. 随机重排 (不改变原数组,返回新数组)
perm_arr = np.random.permutation([1, 2, 3, 4, 5])
print("随机重排:", perm_arr)

# 5. 随机采样 (有放回/无放回)
# 从 [0, 1, 2, 3] 中随机选2个,概率分别为 [0.1, 0.1, 0.4, 0.4]
choice_arr = np.random.choice([0, 1, 2, 3], size=2, p=[0.1, 0.1, 0.4, 0.4])
print("加权采样:", choice_arr)

三、数组的属性与核心操作

3.1 基础属性

arr = np.array([[1, 2, 3], [4, 5, 6]])

print(f"形状 (shape): {arr.shape}")   # (2, 3) -> 2行3列
print(f"维度 (ndim): {arr.ndim}")     # 2
print(f"元素总数 (size): {arr.size}") # 6
print(f"数据类型 (dtype): {arr.dtype}") # int32 或 int64

3.2 索引与切片(重点)

NumPy的切片返回的是视图(View),修改切片会影响原数组。如果需要副本,请使用 .copy()。

【代码实践】:

data = np.arange(1, 10).reshape(3, 3)
# data = [[1, 2, 3],
#         [4, 5, 6],
#         [7, 8, 9]]

# 1. 基本索引:取第2行第3列的元素 (索引从0开始)
val = data[1, 2] 
print(f"特定元素: {val}") # 6

# 2. 切片:取前两行,所有列
sub_arr = data[:2, :] 
print("前两行:\n", sub_arr)

# 3. 步长切片:取所有行,每隔一列取一个 (第0列, 第2列...)
step_arr = data[:, ::2]
print("隔列取样:\n", step_arr)

# 4. 视图特性验证
view = data[0:1, 0:1]
view[0, 0] = 999
print(f"修改视图后原数组:\n{data}") # 原数组第一个元素也会变成999

# 5. 获取副本
copy_arr = data[0:1, 0:1].copy()
copy_arr[0, 0] = 0
print(f"修改副本后原数组不变:\n{data}") 

3.3 数组变形(Reshape)

改变数组形状,但元素总数必须一致。

arr = np.arange(1, 7) # [1, 2, 3, 4, 5, 6]

# 变为 2行3列
reshaped = arr.reshape(2, 3)
print("重塑后:\n", reshaped)

# 增加维度 (一维转二维列向量)
col_vec = arr[np.newaxis, :] # 或者 arr.reshape(1, -1)
print("行向量形状:", col_vec.shape) # (1, 6)

# 展平
flat = reshaped.ravel() # 返回视图
flat_copy = reshaped.flatten() # 返回副本
print("展平:", flat)

3.4 拼接与分裂

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 1. 垂直拼接 (上下叠放) -> axis=0
v_stack = np.vstack((a, b))
# 或者 np.concatenate((a, b), axis=0)
print("垂直拼接:\n", v_stack)

# 2. 水平拼接 (左右并排) -> axis=1
h_stack = np.hstack((a, b))
# 或者 np.concatenate((a, b), axis=1)
print("水平拼接:\n", h_stack)

# 3. 分裂
# 将 v_stack 沿行方向平均分成2份
split_arr = np.vsplit(v_stack, 2)
print("分裂结果1:\n", split_arr[0])

四、NumPy四大运算体系

4.1 向量化运算(元素级)

直接对数组整体进行数学运算,无需循环。

arr = np.array([1, 2, 3, 4])

print("加法:", arr + 10)       # [11, 12, 13, 14]
print("平方:", arr ** 2)       # [1, 4, 9, 16]
print("正弦:", np.sin(arr))    # 对每个元素求sin
print("比较:", arr > 2)        # [False, False, True, True]

4.2 矩阵化运算(线性代数)

区分 元素乘法 (*) 和 矩阵乘法 (dot 或 @)。

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 元素对应相乘
elem_mult = A * B 
# [[1*5, 2*6], [3*7, 4*8]] = [[5, 12], [21, 32]]
print("元素乘法:\n", elem_mult)

# 矩阵乘法 (行乘列)
mat_mult = np.dot(A, B) 
# 或者 A @ B
# 第一行第一列: 1*5 + 2*7 = 19
print("矩阵乘法:\n", mat_mult)

# 转置
print("A的转置:\n", A.T)

4.3 广播机制(Broadcasting)

当两个数组形状不同时,NumPy会自动扩展较小的数组以匹配较大的数组,前提是满足广播规则:

  1. 维度对齐(从后往前)。
  2. 对应维度长度相等,或其中一个为1。

【图解案例】:

# 场景1:标量与数组
arr = np.array([1, 2, 3])
print(arr + 5) # 5被广播为 [5, 5, 5] -> [6, 7, 8]

# 场景2:(3, 3) 与 (1, 3)
matrix = np.ones((3, 3))
row_vec = np.array([0, 1, 2]) # 形状 (3,) 被视为 (1, 3)
result = matrix + row_vec
# row_vec 被复制3次变成 (3, 3) 然后相加
print("广播相加:\n", result)

# 场景3:外积效果 (3, 1) + (1, 3) -> (3, 3)
col_vec = np.array([[0], [10], [20]]) # 形状 (3, 1)
row_vec = np.array([0, 1, 2])         # 形状 (3,) -> (1, 3)
outer_sum = col_vec + row_vec
print("外积式广播:\n", outer_sum)
# 结果:
# [[ 0,  1,  2],
#  [10, 11, 12],
#  [20, 21, 22]]

4.4 比较运算与布尔掩码

利用布尔数组筛选数据,是数据清洗的核心技巧。

data = np.random.randint(0, 10, size=(3, 4))
print("原始数据:\n", data)

# 1. 生成布尔掩码
mask = data > 5
print("大于5的掩码:\n", mask)

# 2. 统计满足条件的个数
count = np.sum(mask)
print(f"大于5的元素个数: {count}")

# 3. 筛选数据 (返回一维数组)
filtered = data[data > 5]
print(f"大于5的元素集合: {filtered}")

# 4. 原地修改满足条件的值
data[data > 5] = -1
print("将大于5的值替换为-1:\n", data)

五、花哨索引(Fancy Indexing)

与普通切片不同,花哨索引通过传入索引数组来访问数据,返回的永远是副本。

5.1 一维花哨索引

arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4]

# 提取第0, 2, 4个元素
result = arr[indices]
print(result) # [10, 30, 50]

5.2 二维花哨索引

需要同时提供行索引数组和列索引数组,它们会配对使用。

matrix = np.arange(1, 10).reshape(3, 3)
# matrix = [[1, 2, 3],
#           [4, 5, 6],
#           [7, 8, 9]]

# 想要提取 (0, 1), (1, 2), (2, 0) 这三个位置的元素 -> 对应值 2, 6, 7
rows = [0, 1, 2]
cols = [1, 2, 0]

result = matrix[rows, cols]
print(f"花哨索引结果: {result}") # [2, 6, 7]

# 结合广播:选取特定行和特定列的笛卡尔积(需配合np.ix_)
# 选取第0行和第2行,以及第1列和第2列的交叉点
sub_matrix = matrix[np.ix_([0, 2], [1, 2])]
print("子矩阵:\n", sub_matrix)
# 输出:
# [[2, 3],
#  [8, 9]]

六、常用通用函数(Universal Functions)

6.1 排序

arr = np.array([3, 1, 4, 1, 5, 9, 2])

# 1. 返回排序后的副本
sorted_arr = np.sort(arr)
print("排序后:", sorted_arr)

# 2. 原地排序
arr.sort()
print("原地排序后:", arr)

# 3. 获取排序后的索引位置 (argsort)
original = np.array([30, 10, 20])
indices = np.argsort(original) 
# 最小值10在原索引1,次小20在索引2...
print("排序索引:", indices) # [1, 2, 0]
print("通过索引还原排序:", original[indices]) # [10, 20, 30]

6.2 统计函数与轴(Axis)的概念

axis=0 代表跨行操作(即按列计算),axis=1 代表跨列操作(即按行计算)。

data = np.array([[1, 2, 3],
                 [4, 5, 6]])

# 全局统计
print("总和:", np.sum(data)) # 21
print("均值:", np.mean(data)) # 3.5

# 按轴统计
print("每列的和 (axis=0):", np.sum(data, axis=0)) # [5, 7, 9] -> 1+4, 2+5, 3+6
print("每行的和 (axis=1):", np.sum(data, axis=1)) # [6, 15]  -> 1+2+3, 4+5+6

# 其他常用统计
print("最大值:", np.max(data))
print("最大值索引:", np.argmax(data)) # 展平后的索引
print("标准差:", np.std(data))
print("中位数:", np.median(data))

七、综合实战案例:模拟学生成绩分析

假设我们有一个班级5名学生的3门课成绩(语文、数学、英语)。

import numpy as np

# 1. 生成模拟数据 (5行3列),分数在60-100之间
np.random.seed(100)
scores = np.random.randint(60, 101, size=(5, 3))
subjects = ['语文', '数学', '英语']
students = ['张三', '李四', '王五', '赵六', '孙七']

print("=== 原始成绩表 ===")
print(f"{'姓名':<6}{'语文':<6}{'数学':<6}{'英语':<6}")
for i, name inenumerate(students):
print(f"{name:<6}{scores[i][0]:<6}{scores[i][1]:<6}{scores[i][2]:<6}")

# 2. 计算每个学生的平均分 (按行计算 axis=1)
avg_scores = np.mean(scores, axis=1)
print("\n=== 学生平均分 ===")
for name, avg inzip(students, avg_scores):
print(f"{name}: {avg:.2f}")

# 3. 计算每门课的最高分 (按列计算 axis=0)
max_scores = np.max(scores, axis=0)
print("\n=== 各科最高分 ===")
for subj, max_s inzip(subjects, max_scores):
print(f"{subj}: {max_s}")

# 4. 找出所有数学成绩大于85分的学生姓名
math_col = scores[:, 1] # 提取数学列
high_math_mask = math_col > 85
high_math_students = np.array(students)[high_math_mask]
print(f"\n数学成绩大于85分的学生: {high_math_students}")

# 5. 给所有低于65分的成绩加分至65分 (及格线保护)
scores[scores < 65] = 65
print("\n=== 补考加分后的第一行数据 ===")
print(f"{students[0]}: {scores[0]}")

回顾

  1. 效率:NumPy利用C底层和向量化,比Python循环快数十倍。
  2. 对象:ndarray 是核心,注意其 shape, dtype, ndim 属性。
  3. 操作:掌握切片(视图)、变形(reshape)、拼接(concatenate)和广播机制。
  4. 运算:分清元素运算(*)与矩阵运算(dot/@),善用布尔掩码筛选数据。
  5. 统计:理解 axis 参数,熟练使用 sum, mean, sort, argsort。

最新文章

随机文章