Skip to content

NumPy 实战指南

Python 数值计算的基石——从数组操作到向量化思维


🚀 为什么 NumPy 是数据分析的第一课?

Pandas、scikit-learn、PyTorch、TensorFlow——这些库的底层都是 NumPy 数组。掌握 NumPy 意味着:

  1. 理解数据的内存模型(连续内存 vs 链表)
  2. 学会向量化思维(用数组运算替代 for 循环)
  3. 读懂所有 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)  # 内存减半,速度可能更快

🔗 相关资源