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 计算中怎么用。
3.6 避坑小记
- 数据类型陷阱:整数数组做除法会截断,记住用
dtype=float。 - 视图污染:切片默认不是副本,修改切片会动原数据。不确定时就
.copy()。 - 随机种子:调试阶段一定要设 seed,否则每次结果不同,没法断言对错。
- 广播形状不匹配:比如 (3,1) 和 (3,) 在加法中会出错,要 align 维度。
推荐练习:用 np.random.normal 生成 1000 个模拟价格,再用手写 SMA 并绘图。这能帮你把本章所有知识点串起来。