3. NumPy基础:ndarray数组创建、数组索引与切片、数组运算、通用函数、线性代数基础
NumPy,说白了就是Python科学计算的基石。我刚开始做量化回测那会儿,还没意识到它的重要性,直到手动用Python列表去算一堆股票的协方差矩阵——那速度,简直让人崩溃。后来换了NumPy,同样的计算,快了几十倍不止。嗯,从那天起,我就再也没离开过它。
这一章,咱们就聊聊NumPy最核心的东西——ndarray。你把它想象成一个超级高效的表格或者矩阵就行。量化投资里,价格序列、收益率、因子数据,本质上都是这玩意儿。
核心要点:NumPy的ndarray是向量化计算的基石。用它对数据进行批量操作,比用Python原生循环快1-2个数量级。在量化中,你处理的是成千上万只股票、数千个时间点的数据,没有NumPy,寸步难行。
3.1 ndarray数组的创建
创建数组的方法很多,我挑几个最常用的说说。
从列表创建——最直接的方式:
import numpy as np
# 一维数组
prices = np.array([100.5, 101.2, 102.8, 103.1])
print(prices) # [100.5 101.2 102.8 103.1]
# 二维数组(比如:4天 × 3只股票)
portfolio = np.array([[100, 200, 150],
[101, 198, 152],
[102, 201, 149],
[103, 199, 151]])
print(portfolio.shape) # (4, 3) —— 4行3列
用内置函数创建——我平时用得最多的几个:
| 函数 | 说明 | 示例 |
|---|---|---|
np.zeros() |
创建全0数组 | np.zeros((3,4)) → 3行4列全0 |
np.ones() |
创建全1数组 | np.ones((2,5)) → 2行5列全1 |
np.arange() |
类似range(),生成等差数列 | np.arange(0, 10, 2) → [0,2,4,6,8] |
np.linspace() |
在区间内生成等间隔数 | np.linspace(0, 1, 5) → [0, 0.25, 0.5, 0.75, 1] |
np.random.randn() |
标准正态分布随机数 | np.random.randn(1000) → 1000个随机数 |
我的小技巧:做蒙特卡洛模拟时,我习惯用 np.random.seed(42) 固定随机种子。这样每次运行结果都一样,方便调试。不然每次跑出来的结果都不一样,你都不知道是代码写错了还是随机性导致的。
3.2 数组索引与切片
索引和切片,说白了就是怎么从数组里取数据。跟Python列表很像,但维度更多。
一维数组的索引——跟列表一模一样:
arr = np.array([10, 20, 30, 40, 50])
print(arr[0]) # 10 —— 第一个元素
print(arr[-1]) # 50 —— 最后一个元素
print(arr[1:4]) # [20 30 40] —— 切片,左闭右开
二维数组的索引——这个在量化里太常用了:
# 假设 data 是 5天 × 3只股票的收盘价
data = np.array([[100, 200, 300],
[101, 198, 302],
[102, 201, 299],
[103, 199, 301],
[104, 202, 298]])
# 取第3天(索引2)的所有股票
print(data[2]) # [102 201 299]
# 取第2只股票(索引1)的所有天数
print(data[:, 1]) # [200 198 201 199 202]
# 取第1到第3天,第0到第2只股票
print(data[0:3, 0:2]) # 前3天,前2只股票
我曾经在写因子分析时,因为索引搞混了,把第0列当成了第1列,结果算出来的相关性全是错的。排查了整整一下午才发现——嗯,从那以后我写索引时都会在注释里标清楚维度含义。
注意:NumPy的切片返回的是视图,不是副本。也就是说,你修改切片后的数据,原数组也会跟着变。如果你不想影响原数组,记得用 .copy() 方法。
3.3 数组运算
这才是NumPy最爽的地方。向量化运算,一行代码搞定循环。
算术运算——对应元素直接算:
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
print(a + b) # [11 22 33 44]
print(a * b) # [10 40 90 160]
print(a ** 2) # [1 4 9 16]
print(np.sqrt(a)) # [1. 1.41 1.73 2.]
广播机制——这个得重点说说。说白了,就是形状不同的数组也能做运算:
# 计算每只股票的日收益率(百分比)
prices = np.array([[100, 200, 150],
[101, 198, 152],
[102, 201, 149]])
# 用广播:每一行减去第一行
returns = (prices - prices[0]) / prices[0] * 100
print(returns)
# [[0. 0. 0. ]
# [1. -1. 1.33]
# [2. 0.5 -0.67]]
你想想看,如果没有广播,你得写两层循环,又慢又容易出错。有了广播,一行搞定。
3.4 通用函数
通用函数,就是那些能对数组每个元素进行快速运算的函数。我管它叫「向量化的数学工具箱」。
常用的通用函数:
- 数学运算:
np.abs(),np.exp(),np.log(),np.sign() - 统计函数:
np.mean(),np.std(),np.var(),np.sum() - 聚合函数:
np.min(),np.max(),np.argmin(),np.argmax()
# 计算一组股票收益率的统计量
returns = np.array([0.02, -0.01, 0.03, 0.01, -0.02, 0.04])
print(f"均值: {np.mean(returns):.4f}") # 0.0117
print(f"标准差: {np.std(returns):.4f}") # 0.0214
print(f"最大值: {np.max(returns):.4f}") # 0.0400
print(f"最大值位置: {np.argmax(returns)}") # 5
避坑指南:我曾经用 np.mean() 算二维数组的均值,忘了指定 axis 参数,结果算出来的是全局均值,而不是每列或每行的均值。记住:axis=0 是按列算,axis=1 是按行算。
3.5 线性代数基础
量化投资里,线性代数无处不在。组合优化、因子模型、风险分解,都离不开它。NumPy的 linalg 模块就是干这个的。
矩阵乘法——用 @ 运算符或 np.dot():
# 假设有3只股票,权重向量 w,收益率矩阵 R
w = np.array([0.4, 0.3, 0.3]) # 权重
R = np.array([[0.02, 0.01, 0.03], # 3天的收益率
[0.01, 0.03, 0.02],
[0.03, 0.02, 0.01]])
# 计算组合每日收益率
portfolio_returns = R @ w # 或者 np.dot(R, w)
print(portfolio_returns) # [0.02 0.019 0.021]
协方差矩阵——衡量股票之间相关性的核心工具:
# 3只股票的收益率数据(10天)
returns = np.random.randn(10, 3) * 0.02 # 模拟数据
# 计算协方差矩阵
cov_matrix = np.cov(returns, rowvar=False)
print(cov_matrix.shape) # (3, 3)
# 计算相关系数矩阵
corr_matrix = np.corrcoef(returns, rowvar=False)
print(corr_matrix)
求解线性方程组——比如在均值-方差优化中:
# 解方程组:2x + 3y = 8, 5x + 2y = 9
A = np.array([[2, 3],
[5, 2]])
b = np.array([8, 9])
x = np.linalg.solve(A, b)
print(x) # [1. 2.] —— 即 x=1, y=2
实战经验:我在做风险平价策略时,需要频繁计算协方差矩阵的逆。直接用 np.linalg.inv() 在矩阵接近奇异时会报错。后来我改用 np.linalg.pinv()(伪逆),稳定多了。记住:金融数据经常有多重共线性,伪逆是你的好朋友。
好了,NumPy的基础就聊到这儿。这些工具在后面的章节里会反复用到。你先把这些代码敲一遍,感受一下向量化运算的爽快感。相信我,等你习惯了NumPy,就再也回不去Python原生列表了。