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 倍以上。这是因为:

  1. NumPy 数组在内存中是连续存储的,而 Python 列表存储的是对象引用
  2. NumPy 使用 C 语言实现底层运算,避免了 Python 循环的开销
  3. 向量化操作直接作用于整个数组,无需显式循环

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
  2. 如果两个数组在某个维度上的大小不一致,且其中一个为 1,则在该维度上广播
  3. 如果两个数组在某个维度上的大小不一致,且都不为 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 一样轻松处理表格数据。