8 minutes
NumPy 入门
NumPy(Numerical Python)是 Python 科学计算的基石。几乎所有的数据科学工具(Pandas、Scikit-learn、Matplotlib)都建立在 NumPy 之上。它的核心是高性能的多维数组对象 ndarray,以及丰富的数组运算函数。
为什么需要 NumPy?
Python 内置的列表虽然灵活,但在数值计算方面存在明显短板:
import numpy as np
import time
# 对比 Python 列表和 NumPy 数组的性能
size = 1_000_000
# Python 列表
py_list = list(range(size))
start = time.time()
py_result = [x * 2 for x in py_list]
print(f"Python 列表用时: {time.time() - start:.4f} 秒")
# NumPy 数组
np_array = np.arange(size)
start = time.time()
np_result = np_array * 2
print(f"NumPy 数组用时: {time.time() - start:.4f} 秒")
在我的机器上,NumPy 的运算速度快了 50 倍以上。这是因为:
- NumPy 数组在内存中是连续存储的,而 Python 列表存储的是对象引用
- NumPy 使用 C 语言实现底层运算,避免了 Python 循环的开销
- 向量化操作直接作用于整个数组,无需显式循环
ndarray:NumPy 的核心
ndarray 是同质的多维数组(所有元素类型相同)。我们来创建各种数组:
import numpy as np
# 从列表创建
arr1 = np.array([1, 2, 3, 4, 5]) # 一维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]]) # 二维数组(矩阵)
arr3 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) # 三维数组
print(arr1) # [1 2 3 4 5]
print(arr2) # [[1 2 3]
# [4 5 6]]
print(arr3) # [[[1 2]
# [3 4]]
# [[5 6]
# [7 8]]]
# 指定数据类型
arr_int32 = np.array([1, 2, 3], dtype=np.int32)
arr_float64 = np.array([1, 2, 3], dtype=np.float64)
arr_bool = np.array([0, 1, 0, 1], dtype=np.bool_)
常用创建函数
# 全零数组
zeros = np.zeros((3, 4)) # 3 行 4 列全零矩阵
print(zeros)
# 全一数组
ones = np.ones((2, 3)) # 2 行 3 列全一矩阵
# 单位矩阵
identity = np.eye(3) # 3x3 单位矩阵
print(identity) # [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# 指定填充值
filled = np.full((2, 3), 7) # 2 行 3 列全为 7
# 等差数列
arange1 = np.arange(10) # [0, 1, 2, ..., 9]
arange2 = np.arange(2, 10, 2) # [2, 4, 6, 8]
arange3 = np.arange(1, 2, 0.2) # [1.0, 1.2, 1.4, 1.6, 1.8]
# 均匀间隔
linspace1 = np.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0]
linspace2 = np.linspace(0, np.pi, 3) # [0.0, 1.5708, 3.1416]
# 随机数组
np.random.seed(42) # 设置随机种子,确保结果可重现
rand_arr = np.random.rand(3, 3) # [0, 1) 均匀分布
randn_arr = np.random.randn(1000) # 标准正态分布
randint_arr = np.random.randint(0, 10, (3, 4)) # [0, 10) 随机整数
数组属性
arr = np.array([[1, 2, 3, 4],
[5, 6, 7, 8]])
print(f"形状 (shape): {arr.shape}") # (2, 4)
print(f"维度 (ndim): {arr.ndim}") # 2
print(f"元素数量 (size): {arr.size}") # 8
print(f"数据类型 (dtype): {arr.dtype}") # int64
print(f"每个元素字节数: {arr.itemsize}") # 8
print(f"总字节数: {arr.nbytes}") # 64
索引和切片
NumPy 的索引和切片非常灵活,比 Python 列表更强大:
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 基本索引
print(arr[0]) # 0
print(arr[-1]) # 9
# 基本切片
print(arr[2:5]) # [2, 3, 4]
print(arr[:3]) # [0, 1, 2]
print(arr[::2]) # [0, 2, 4, 6, 8]
# 二维数组索引
matrix = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
print(matrix[1, 2]) # 7(第 2 行第 3 列)
print(matrix[1]) # [5, 6, 7, 8](第 2 行)
print(matrix[:, 1]) # [2, 6, 10](第 2 列)
print(matrix[0:2, 1:3]) # [[2, 3], [6, 7]](子矩阵)
# 花式索引(使用整数数组)
indices = [0, 2, 4]
print(arr[indices]) # [0, 2, 4]
# 布尔索引(非常实用)
arr = np.array([1, 2, 3, 4, 5, 6])
mask = arr > 3
print(mask) # [False, False, False, True, True, True]
print(arr[mask]) # [4, 5, 6]
print(arr[arr % 2 == 0]) # [2, 4, 6](筛选偶数)
# 组合条件
print(arr[(arr > 2) & (arr < 5)]) # [3, 4](与)
print(arr[(arr < 3) | (arr > 5)]) # [1, 2, 6](或)
数组变形
arr = np.arange(12) # [0, 1, ..., 11]
# reshape:改变形状(不改变数据)
reshaped = arr.reshape(3, 4)
print(reshaped)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# -1 表示自动推断
auto = arr.reshape(2, -1) # 相当于 reshape(2, 6)
print(auto)
# [[ 0 1 2 3 4 5]
# [ 6 7 8 9 10 11]]
# 展平
flat = reshaped.flatten() # 返回拷贝
ravel = reshaped.ravel() # 返回视图(可能共享内存)
print(flat) # [0, 1, 2, ..., 11]
# 转置
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(matrix.T)
# [[1 4]
# [2 5]
# [3 6]]
# 拼接
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
v_stack = np.vstack((a, b)) # 垂直拼接
print(v_stack) # [[1 2], [3 4], [5 6]]
h_stack = np.hstack((a, b.T)) # 水平拼接
print(h_stack) # [[1 2 5], [3 4 6]]
# 分割
arr = np.arange(12).reshape(3, 4)
print(np.vsplit(arr, 3)) # 垂直分割成 3 个
print(np.hsplit(arr, 2)) # 水平分割成 2 个
广播 (Broadcasting)
广播是 NumPy 最强大的特性之一,它允许不同形状的数组之间进行运算:
# 标量与数组
arr = np.array([1, 2, 3, 4])
print(arr + 10) # [11, 12, 13, 14]
print(arr * 2) # [2, 4, 6, 8]
print(arr ** 2) # [1, 4, 9, 16]
# 一维与二维数组相加
matrix = np.ones((3, 3)) # 3x3 全 1 矩阵
row = np.array([1, 2, 3]) # 一维数组
result = matrix + row
print(result)
# [[2. 3. 4.]
# [2. 3. 4.]
# [2. 3. 4.]]
# 广播规则:从尾部维度开始比较,维度为 1 或相等即可广播
a = np.ones((3, 1)) # 形状 (3, 1)
b = np.ones((1, 4)) # 形状 (1, 4)
print(a + b) # 结果形状 (3, 4)
广播的三个规则:
- 如果两个数组维度不同,将维度较小的数组前面补 1
- 如果两个数组在某个维度上的大小不一致,且其中一个为 1,则在该维度上广播
- 如果两个数组在某个维度上的大小不一致,且都不为 1,则抛出异常
通用函数 (ufunc)
通用函数是作用于数组元素的快速向量化函数:
arr = np.array([1, 2, 3, 4, 5])
# 算术运算
np.add(arr, 10) # 加法
np.subtract(arr, 3) # 减法
np.multiply(arr, 2) # 乘法
np.divide(arr, 2) # 除法
np.power(arr, 2) # 幂运算
np.mod(arr, 2) # 取模
# 三角函数
np.sin(np.pi / 2) # 正弦
np.cos(0) # 余弦
np.tan(np.pi / 4) # 正切
# 指数和对数
np.exp(arr) # e^x
np.log(arr) # 自然对数
np.log10(arr) # 以 10 为底的对数
np.log2(arr) # 以 2 为底的对数
# 统计函数
arr2d = np.random.randn(4, 3)
print(np.sum(arr2d)) # 总和
print(np.mean(arr2d)) # 均值
print(np.std(arr2d)) # 标准差
print(np.var(arr2d)) # 方差
print(np.min(arr2d)) # 最小值
print(np.max(arr2d)) # 最大值
# 沿指定轴操作(axis=0 沿行轴,axis=1 沿列轴)
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
print(np.sum(matrix, axis=0)) # [5, 7, 9](每列求和)
print(np.sum(matrix, axis=1)) # [6, 15](每行求和)
print(np.mean(matrix, axis=0)) # [2.5, 3.5, 4.5]
# 累积计算
arr = np.array([1, 2, 3, 4, 5])
print(np.cumsum(arr)) # [1, 3, 6, 10, 15](累加)
print(np.cumprod(arr)) # [1, 2, 6, 24, 120](累乘)
线性代数基础
import numpy as np
from numpy.linalg import inv, eig, det, solve
# 矩阵乘法
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 三种方式实现矩阵乘法
print(A @ B) # Python 3.5+ 的 @ 运算符
print(np.dot(A, B)) # dot 函数
print(A.dot(B)) # 方法调用
# 结果都是 [[19, 22], [43, 50]]
# 矩阵求逆
A = np.array([[1, 2], [3, 4]])
A_inv = inv(A)
print(A_inv)
# [[-2. 1. ]
# [ 1.5 -0.5]]
# 验证:A * A_inv ≈ 单位矩阵
print(A @ A_inv)
# [[1.00000000e+00, 0.00000000e+00],
# [8.88178420e-16, 1.00000000e+00]]
# 行列式
print(det(A)) # -2.0000000000000004
# 特征值和特征向量
eigenvalues, eigenvectors = eig(A)
print(f"特征值: {eigenvalues}")
print(f"特征向量:\n{eigenvectors}")
# 解线性方程组 Ax = b
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])
x = solve(A, b)
print(x) # [2. 3.] 即 x1=2, x2=3
# 验证
print(A @ x) # [9. 8.]
随机数生成
np.random.seed(42) # 设置种子确保可重现
# 基本随机数
rand = np.random.rand(1000) # [0, 1) 均匀分布
randn = np.random.randn(1000) # 标准正态分布
randint = np.random.randint(0, 100, 10) # [0, 100) 随机整数
# 不同分布
uniform = np.random.uniform(0, 10, 1000) # 均匀分布
normal = np.random.normal(0, 1, 1000) # 正态分布(均值 0,标准差 1)
binomial = np.random.binomial(10, 0.5, 100) # 二项分布
poisson = np.random.poisson(3, 100) # 泊松分布
exponential = np.random.exponential(1, 100) # 指数分布
# 随机抽样
arr = np.arange(10)
sample = np.random.choice(arr, 5, replace=False) # 不放回抽样
with_replacement = np.random.choice(arr, 10, replace=True) # 有放回抽样
# 打乱数组
arr = np.arange(10)
np.random.shuffle(arr) # 原地打乱
性能对比:向量化 vs 循环
import numpy as np
import time
# 创建一个大型数组
n = 10_000_000
arr = np.random.randn(n)
# Python 循环方式
start = time.time()
py_result = []
for i in range(n):
py_result.append(np.sin(arr[i]) + np.cos(arr[i]))
py_time = time.time() - start
# NumPy 向量化方式
start = time.time()
np_result = np.sin(arr) + np.cos(arr)
np_time = time.time() - start
print(f"Python 循环: {py_time:.3f} 秒")
print(f"NumPy 向量化: {np_time:.3f} 秒")
print(f"加速比: {py_time / np_time:.1f}x")
常用技巧和注意事项
# 1. 复制数组(深拷贝 vs 浅拷贝)
arr = np.array([1, 2, 3, 4])
view = arr[0:2] # 切片创建视图(修改会影响原数组)
copy = arr[0:2].copy() # copy() 创建独立副本
view[0] = 100
print(arr) # [100, 2, 3, 4](原数组被修改)
copy[0] = -1
print(arr) # [100, 2, 3, 4](原数组不受影响)
# 2. 条件替换
arr = np.array([1, 2, 3, 4, 5])
arr[arr > 3] = 0 # 所有 > 3 的元素替换为 0
print(arr) # [1, 2, 3, 0, 0]
# 3. np.where(类似条件表达式)
arr = np.array([1, 2, 3, 4, 5])
result = np.where(arr > 3, "high", "low")
print(result) # ['low', 'low', 'low', 'high', 'high']
# 4. 唯一值和计数
arr = np.array([1, 1, 2, 2, 2, 3, 4, 4])
unique, counts = np.unique(arr, return_counts=True)
print(dict(zip(unique, counts))) # {1: 2, 2: 3, 3: 1, 4: 2}
# 5. 排序
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print(np.sort(arr)) # 返回排序后的副本
arr.sort() # 原地排序
indices = np.argsort(arr) # 排序后的索引
# 6. 集合运算
a = np.array([1, 2, 3, 4])
b = np.array([3, 4, 5, 6])
print(np.intersect1d(a, b)) # [3, 4]
print(np.union1d(a, b)) # [1, 2, 3, 4, 5, 6]
print(np.setdiff1d(a, b)) # [1, 2]
实战:统计分析
import numpy as np
# 生成模拟数据:100 名学生的考试成绩
np.random.seed(42)
n_students = 100
data = {
"语文": np.random.normal(75, 10, n_students).clip(0, 100),
"数学": np.random.normal(70, 15, n_students).clip(0, 100),
"英语": np.random.normal(72, 12, n_students).clip(0, 100)
}
# 综合统计分析
print("=" * 50)
print("考试成绩统计分析")
print("=" * 50)
for subject, scores in data.items():
print(f"\n{subject}:")
print(f" 平均分: {np.mean(scores):.1f}")
print(f" 标准差: {np.std(scores):.1f}")
print(f" 最高分: {np.max(scores):.1f}")
print(f" 最低分: {np.min(scores):.1f}")
print(f" 中位数: {np.median(scores):.1f}")
print(f" 25% 分位: {np.percentile(scores, 25):.1f}")
print(f" 75% 分位: {np.percentile(scores, 75):.1f}")
# 分数段分布
excellent = np.sum(scores >= 90)
good = np.sum((scores >= 80) & (scores < 90))
pass_num = np.sum((scores >= 60) & (scores < 80))
fail = np.sum(scores < 60)
print(f" 优秀(>=90): {excellent}人")
print(f" 良好(80-89): {good}人")
print(f" 及格(60-79): {pass_num}人")
print(f" 不及格(<60): {fail}人")
# 计算科目间的相关性矩阵
scores_matrix = np.column_stack([data["语文"], data["数学"], data["英语"]])
correlation = np.corrcoef(scores_matrix, rowvar=False)
print("\n\n科目相关性矩阵:")
print(" 语文 数学 英语")
subjects = ["语文", "数学", "英语"]
for i, subject in enumerate(subjects):
corr_str = " ".join(f"{correlation[i, j]:.3f}" for j in range(3))
print(f" {subject} {corr_str}")
下一步
NumPy 为高性能数值计算打下了坚实基础。下一篇我们将学习 Pandas,它构建在 NumPy 之上,提供了更高级的数据结构和数据分析方法,让你能像操作 Excel 一样轻松处理表格数据。