NumPy 实战指南¶
Python 数值计算的基石——从数组操作到向量化思维
🚀 为什么 NumPy 是数据分析的第一课?¶
Pandas、scikit-learn、PyTorch、TensorFlow——这些库的底层都是 NumPy 数组。掌握 NumPy 意味着:
- 理解数据的内存模型(连续内存 vs 链表)
- 学会向量化思维(用数组运算替代 for 循环)
- 读懂所有 ML 库的输出格式(predict() 返回 numpy array)
📦 数组基础¶
import numpy as np
# === 创建数组 ===
np.array([1, 2, 3]) # 从列表创建
np.zeros((3, 4)) # 全零矩阵
np.ones((2, 3)) # 全一矩阵
np.eye(4) # 单位矩阵
np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0]
np.random.randn(100) # 100 个标准正态随机数
np.random.seed(42) # 固定随机种子 ← 必须养成习惯!
# === 数组属性 ===
arr = np.random.randn(3, 4)
print(f"shape: {arr.shape}") # (3, 4)
print(f"ndim: {arr.ndim}") # 2
print(f"dtype: {arr.dtype}") # float64
print(f"size: {arr.size}") # 12
print(f"内存: {arr.nbytes} bytes") # 96 (12 × 8 bytes for float64)
⚡ 向量化运算(能不用 for 就不用 for)¶
# ❌ 慢:Python for 循环
import time
n = 1_000_000
a = list(range(n))
b = list(range(n))
start = time.time()
c_py = [a[i] + b[i] for i in range(n)]
print(f"Python for: {time.time()-start:.4f}s")
# ✅ 快:NumPy 向量化
a_np = np.arange(n)
b_np = np.arange(n)
start = time.time()
c_np = a_np + b_np
print(f"NumPy: {time.time()-start:.4f}s")
# NumPy 比 Python 快 10-100 倍!
常用向量化运算¶
arr = np.array([1, 2, 3, 4, 5])
# 逐元素运算
arr * 2 # [2, 4, 6, 8, 10]
arr ** 2 # [1, 4, 9, 16, 25]
np.sqrt(arr) # 平方根
np.log(arr) # 自然对数
np.exp(arr) # e^x
# 聚合
arr.sum() # 15
arr.mean() # 3.0
arr.std() # 标准差
arr.min() # 1
arr.max() # 5
np.percentile(arr, 50) # 中位数
# 布尔索引
arr > 3 # [False, False, False, True, True]
arr[arr > 3] # [4, 5]
(arr > 2).sum() # 3 个元素大于 2
# 条件赋值
np.where(arr > 3, 'high', 'low') # ['low', 'low', 'low', 'high', 'high']
🧮 矩阵运算(线性代数的核心)¶
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 矩阵乘法(不是逐元素乘!)
A @ B # [[19, 22], [43, 50]]
np.dot(A, B) # 同上
# 转置
A.T # [[1, 3], [2, 4]]
# 逆矩阵
np.linalg.inv(A) # [[-2., 1.], [1.5, -0.5]]
# 特征值 & 特征向量(PCA 的核心)
eigenvalues, eigenvectors = np.linalg.eig(A)
# 解线性方程组 Ax = b
b = np.array([1, 2])
x = np.linalg.solve(A, b) # 解 Ax = b
print(f"Ax = {A @ x}") # 验证:应该 ≈ b
# SVD 分解(推荐系统、降维的核心)
U, S, Vt = np.linalg.svd(A)
📐 广播机制(Broadcasting)¶
不同形状的数组如何在一起运算?NumPy 会自动"拉伸"小数组。
# 矩阵 (3×4) + 向量 (4,)
# → 向量自动广播到每一行
matrix = np.ones((3, 4))
row_vector = np.array([1, 2, 3, 4])
result = matrix + row_vector
print(result)
# [[2, 3, 4, 5],
# [2, 3, 4, 5],
# [2, 3, 4, 5]]
# 矩阵 (3×4) + 列向量 (3,1)
col_vector = np.array([[10], [20], [30]])
result = matrix + col_vector
print(result)
# [[11, 11, 11, 11],
# [21, 21, 21, 21],
# [31, 31, 31, 31]]
广播规则(从后往前对齐): 1. 如果维度数不同,在较小的 shape 前面补 1 2. 如果某维度 size 不同但有一个是 1 → 可以广播 3. 如果两维度 size 不同且都不是 1 → 报错
(3, 4) + (4,) → (3, 4) + (1, 4) → (3, 4) ✅
(3, 4) + (3, 1) → (3, 4) + (3, 1) → (3, 4) ✅
(3, 4) + (3,) → (3, 4) + (1, 3) → ❌ (4 ≠ 3)
🎲 随机数生成(模拟与抽样的基础)¶
rng = np.random.default_rng(42) # 推荐:新的 Generator API
# 常见分布
rng.normal(0, 1, 1000) # 正态 μ=0, σ=1
rng.uniform(0, 1, 1000) # 均匀 [0, 1]
rng.binomial(10, 0.5, 100) # 二项 n=10, p=0.5
rng.poisson(3, 100) # 泊松 λ=3
rng.exponential(1, 100) # 指数 λ=1
rng.beta(2, 5, 100) # Beta α=2, β=5
# 抽样
indices = rng.choice(1000, 50, replace=False) # 无放回抽 50 个
data[indices] # 取样本
# 洗牌
arr = np.arange(10)
rng.shuffle(arr)
🔄 重塑与拼接(数据预处理必备)¶
# 重塑
arr = np.arange(12)
arr.reshape(3, 4) # 3 行 4 列
arr.reshape(-1, 1) # 转列向量 (12, 1)
# 拼接
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
np.vstack([a, b]) # 垂直拼接 (4, 2)
np.hstack([a, b]) # 水平拼接 (2, 4)
# 展平
arr.ravel() # 展平(返回视图,不复制)
arr.flatten() # 展平(返回副本)
📂 文件读写¶
# 保存 / 加载
np.save('data.npy', arr)
loaded = np.load('data.npy')
# CSV(小数据用 NumPy,大数据用 Pandas)
data = np.genfromtxt('data.csv', delimiter=',', skip_header=1)
np.savetxt('output.csv', data, delimiter=',', fmt='%.4f')
🎯 实战案例:用 NumPy 做描述性统计¶
# 模拟 10000 个用户的消费数据
rng = np.random.default_rng(42)
spending = rng.lognormal(mean=3.5, sigma=1.0, size=10000)
# 基础统计
print(f"均值: ¥{spending.mean():.2f}")
print(f"中位数: ¥{np.median(spending):.2f}")
print(f"标准差: ¥{spending.std():.2f}")
print(f"偏度: {stats.skew(spending):.2f}") # >0 = 右偏(常见于消费数据)
# 分位数
percentiles = [25, 50, 75, 90, 95, 99]
for p in percentiles:
print(f"P{p}: ¥{np.percentile(spending, p):.2f}")
# 分段统计
bins = [0, 10, 50, 100, 500, 1000, np.inf]
labels = ['¥0-10', '¥10-50', '¥50-100', '¥100-500', '¥500-1K', '¥1K+']
counts, _ = np.histogram(spending, bins=bins)
for label, count in zip(labels, counts):
print(f"{label}: {count} 人 ({count/len(spending)*100:.1f}%)")
⚡ 性能技巧¶
# 1. 预分配数组(不要动态增长)
# ❌ 慢
result = []
for i in range(100000):
result.append(i * 2)
result = np.array(result)
# ✅ 快
result = np.empty(100000)
for i in range(100000):
result[i] = i * 2
# 2. 用 inplace 操作节省内存
arr += 1 # inplace
arr = arr + 1 # 创建新数组(多占内存)
# 3. 使用合适的 dtype
arr_f32 = arr.astype(np.float32) # 内存减半,速度可能更快
🔗 相关资源¶
- Pandas 实战指南 — Pandas 的底层就是 NumPy
- 数学基础资源 — 线性代数 + 微积分
- 描述性统计基础 — NumPy 在统计中的应用