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原生列表了。


专注资料整理