3. NumPy 数组操作:创建数组、切片、数学运算、随机数生成

NumPy 是整个 Python 数据科学的基石。说实话,我早期用纯 Python 列表写移动平均计算,那性能简直让人抓狂。后来换用 NumPy 数组,速度直接飞起。这一节就把我平时最常用的数组操作梳理一遍,包括创建、切片、数学运算和随机数生成。

本章核心:掌握 NumPy 数组的四种基本操作,为 MA 图绘制打好数据预处理基础。

3.1 创建数组——从零到一

NumPy 创建数组的方式特别多。我个人最常用的有三种:np.array()、np.arange()、np.linspace()。

3.1.1 从列表创建

import numpy as np

# 一维数组
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)      # [1 2 3 4 5]
print(arr1.dtype)# int32(或 int64,取决于平台)

# 二维数组(矩阵)
arr2 = np.array([[1, 2], [3, 4]])
print(arr2.shape)# (2, 2)

有一点需要注意:np.array() 会自动推断数据类型。我曾在项目里因为整数除法踩过坑——数组类型是 int,结果 1/2 给了 0。所以遇到小数运算,我会显式指定 dtype=np.float64。

3.1.2 等间隔序列

# 类似 range,但生成数组
arr = np.arange(0, 10, 2)  # [0 2 4 6 8]

# 指定个数,自动计算步长
arr_lin = np.linspace(0, 1, 5)  # [0.   0.25 0.5  0.75 1.  ]

我个人习惯用 np.linspace 来生成时间轴,比如画 MA 图时要等间距取 1000 个点。

3.1.3 特殊数组

函数作用举例
np.zeros((3,4))全零数组占位初始化
np.ones((2,3))全一数组权重初始化
np.eye(4)单位矩阵线性代数
np.full((2,2), 7)填充固定值定值矩阵

技巧:创建数组时尽量先指定 dtype,避免后续运算精度问题。比如 np.zeros(100, dtype=np.float32) 能省内存。

3.2 切片与索引——拿到你需要的数据

切片语法和 Python 列表类似,但 NumPy 支持多维切片,还能用布尔索引。嗯,这里要特别小心视图和副本的区别——我曾经因为没注意这个,花了半天排查 bug。

3.2.1 基本切片

arr = np.array([10, 20, 30, 40, 50])
print(arr[1:4])   # [20 30 40]
print(arr[:3])    # [10 20 30]
print(arr[::2])   # [10 30 50] 步长为2

3.2.2 多维切片

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

print(mat[0, :])   # 第一行 [1 2 3]
print(mat[:, 1])   # 第二列 [2 5 8]
print(mat[0:2, 1:3]) # 左上 2x2 子矩阵

3.2.3 布尔索引——这个最实用

arr = np.array([1, 2, 3, 4, 5])
mask = arr > 3
print(mask)               # [False False False  True  True]
print(arr[mask])          # [4 5]

# 直接写在一起
print(arr[arr % 2 == 0])  # [2 4]

我在处理 MA 数据时经常用布尔索引过滤异常值,比如去掉价格低于 0 的点。

警告:切片返回的是原始数组的视图,不是副本。修改切片会影响原数组。需要独立数据时,请用 .copy()。

a = np.array([1,2,3])
b = a[0:2]
b[0] = 99
print(a)  # [99  2  3]  原数组被改了!

# 正确做法
c = a[0:2].copy()
c[0] = 0
print(a)  # 不受影响

3.3 数学运算——向量化的威力

NumPy 的数学运算都是元素级别的,底层用 C 实现,比 Python 循环快几十倍。你想想看,用纯 Python 算 100 万个数要 0.5 秒,NumPy 只要几毫秒。

3.3.1 基本算术

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

print(a + b)   # [5 7 9]
print(a * b)   # [4 10 18]  对应位置相乘
print(a ** 2)  # [1 4 9]
print(np.sqrt(a))  # [1.         1.41421356 1.73205081]

3.3.2 统计函数

函数说明示例
np.mean()平均值np.mean(arr)
np.std()标准差np.std(arr, ddof=1)
np.sum()求和np.sum(arr, axis=0)
np.max() / np.min()最大/最小值np.max(arr)
np.cumsum()累积和np.cumsum(arr)

画 MA 曲线时,np.cumsum() 可以用来快速计算简单移动平均(SMA):

data = np.array([10, 12, 11, 13, 14, 12])
window = 3
sma = np.cumsum(data)
sma[window:] = sma[window:] - sma[:-window]
sma = sma[window-1:] / window
print(sma)  # [11.  12.  12.66666667 13.        ]

你看,两行代码就搞定了,比写循环干净太多。

3.3.3 广播(Broadcasting)

这是 NumPy 最强大的特性。形状不同的数组也能做运算,只要符合广播规则。举个例子:

arr = np.array([[1, 2, 3],
                [4, 5, 6]])
print(arr + 10)  # 标量加给每个元素
print(arr * np.array([10, 20, 30]))  # 一行乘三列

说白了,NumPy 会自动把标量或低维数组“拉伸”到和高维数组一样的形状,然后逐元素运算。不过要注意,形状不兼容时会报错——我曾经把 (3,1) 和 (3,) 搞混,调试了半天。

3.4 随机数生成——模拟与测试

画 MA 图之前,我经常先生成随机数据来测试算法。NumPy 的随机模块 np.random 比 Python 内置的 random 强很多,能生成多维数组,还支持多种分布。

3.4.1 均匀分布

# 生成 5 个 [0,1) 均匀分布随机数
r = np.random.rand(5)
print(r)

# 生成 3x4 矩阵,值在 [0,1)
r_mat = np.random.rand(3, 4)

# 指定范围 [low, high)
r2 = np.random.uniform(low=0, high=100, size=10)

3.4.2 正态分布(高斯分布)

# 标准正态(均值0,标准差1)
normal = np.random.randn(1000)

# 指定均值和标准差
normal2 = np.random.normal(loc=50, scale=10, size=(5, 3))

我在项目里模拟股价时,常常用 np.random.normal 生成日收益率,再累积成价格序列。

3.4.3 整数随机

# 随机整数 [low, high)
ints = np.random.randint(0, 10, size=(2, 3))
print(ints)

3.4.4 设置种子——保证可复现

np.random.seed(42)
a = np.random.rand(3)
print(a)  # 每次运行结果一样

# 或用 Generator(推荐)
rng = np.random.default_rng(2024)
b = rng.normal(size=5)
print(b)

经验:做教学或调试时一定要设 seed,不然每次结果都变,没法验证逻辑。新版的 np.random.default_rng() 性能更好,建议优先用。

3.5 知识结构图

下面这张图总结了 NumPy 数组操作的四个核心板块,以及它们之间的关系。建议你对照本章内容,边看边想每个操作在 MA 计算中怎么用。

NumPy 数组操作知识结构 NumPy 数组操作知识结构 创建数组 切片与索引 数学运算 随机数生成 np.array np.arange 视图 vs 副本 布尔索引 广播机制 统计函数 均匀分布 正态分布 应用场景 生成模拟价格 → 计算 MA

3.6 避坑小记

  • 数据类型陷阱:整数数组做除法会截断,记住用 dtype=float。
  • 视图污染:切片默认不是副本,修改切片会动原数据。不确定时就 .copy()。
  • 随机种子:调试阶段一定要设 seed,否则每次结果不同,没法断言对错。
  • 广播形状不匹配:比如 (3,1) 和 (3,) 在加法中会出错,要 align 维度。

推荐练习:用 np.random.normal 生成 1000 个模拟价格,再用手写 SMA 并绘图。这能帮你把本章所有知识点串起来。