4、必备Python库(上):NumPy基础:创建数组、索引与切片、数学运算

做量化选股,说白了就是跟数据打交道。股票价格、成交量、财务指标……这些数据动辄成千上万条。你要是用纯Python的列表去处理,跑个回测能等到你怀疑人生。这时候,NumPy就该登场了。

我个人习惯把NumPy叫做「Python数据处理的发动机」。它底层用C语言实现,运算速度比纯Python快几十倍甚至上百倍。今天我们就来啃下这块硬骨头,把NumPy最核心的三个技能拿下:创建数组、索引与切片、数学运算。

NumPy 核心三剑客 量化选股数据基石 创建数组 array / arange / zeros 索引与切片 花式索引 / 布尔索引 数学运算 向量化 / 广播机制 从列表/元组创建 指定形状与类型 一维/二维切片 条件筛选数据 逐元素运算 矩阵乘法 统计函数

1. 创建数组:你的数据容器

NumPy的核心数据结构叫ndarray,也就是N维数组。你可以把它想象成一个超级版的Python列表,但人家是专门为数值计算设计的。

创建数组最直接的方式,就是从Python列表转换过来:

import numpy as np

# 从列表创建一维数组
prices = np.array([28.5, 29.1, 28.8, 30.2, 29.9])
print(prices)
# 输出: [28.5 29.1 28.8 30.2 29.9]

# 创建二维数组(比如:3只股票,5天的收盘价)
stock_data = np.array([
    [28.5, 29.1, 28.8, 30.2, 29.9],
    [15.2, 15.8, 15.5, 16.1, 15.9],
    [42.3, 43.1, 42.8, 44.0, 43.5]
])
print(stock_data.shape)  # 输出: (3, 5)
💡 我的小习惯:创建数组时,我总会顺手检查一下dtype属性。NumPy默认会用float64,但如果你确定数据是整数,可以指定dtype=np.int32,能省不少内存。我在处理全市场5000多只股票的历史数据时,这个习惯帮我省下了好几个G的内存。

除了手动输入,NumPy还提供了一些快速生成数组的函数,写量化策略时特别常用:

# 生成等差数列(比如:模拟时间序列)
time_points = np.arange(0, 10, 0.5)  # 从0到10,步长0.5
print(time_points)
# 输出: [0.  0.5 1.  1.5 2.  2.5 3.  3.5 4.  4.5 5.  5.5 6.  6.5 7.  7.5 8.  8.5 9.  9.5]

# 生成全0数组(比如:初始化持仓矩阵)
positions = np.zeros((3, 5))  # 3只股票,5天,初始仓位全为0
print(positions)

# 生成全1数组(比如:初始化权重)
weights = np.ones(3) / 3  # 等权重配置
print(weights)  # 输出: [0.33333333 0.33333333 0.33333333]

# 生成随机数(蒙特卡洛模拟常用)
np.random.seed(42)  # 固定随机种子,保证结果可复现
random_returns = np.random.randn(1000)  # 生成1000个标准正态分布的随机收益率
⚠️ 注意:我曾经在回测中忘记设置np.random.seed(),结果每次跑出来的结果都不一样,排查了半天才发现是随机数没固定。做量化研究,可复现性是底线,记得设种子!

2. 索引与切片:精准定位你的数据

数组建好了,怎么取数据?NumPy的索引和Python列表很像,但功能更强大。你想想看,股票数据通常是二维的——行是股票,列是时间。怎么快速取出某只股票的全部价格?

# 沿用上面的 stock_data (3只股票 x 5天)
# 取出第一只股票的全部价格
stock_1 = stock_data[0, :]  # 第0行,所有列
print(stock_1)  # 输出: [28.5 29.1 28.8 30.2 29.9]

# 取出所有股票第3天的价格
day_3 = stock_data[:, 2]  # 所有行,第2列(索引从0开始)
print(day_3)  # 输出: [28.8 15.5 42.8]

# 取出前2只股票,后3天的数据
sub_data = stock_data[:2, 2:5]
print(sub_data)
# 输出:
# [[28.8 30.2 29.9]
#  [15.5 16.1 15.9]]

这里有个坑,我刚开始学的时候踩过——切片返回的是视图,不是副本。什么意思?就是你修改切片后的数据,原数组也会跟着变。

# 踩坑演示
temp = stock_data[0, :]  # 取第一只股票
temp[0] = 999  # 修改切片数据
print(stock_data[0, 0])  # 输出: 999.0 —— 原数组也被改了!
✅ 避坑指南:如果你只想复制数据,不想影响原数组,用 .copy() 方法。
temp = stock_data[0, :].copy()

NumPy还有一种特别酷的索引方式——布尔索引。这在量化选股中太实用了,比如筛选出所有涨幅超过5%的股票:

# 假设这是10只股票的日收益率
returns = np.array([0.02, -0.01, 0.06, 0.03, -0.02, 0.08, 0.01, -0.03, 0.05, 0.04])

# 找出收益率大于5%的股票
high_returns = returns[returns > 0.05]
print(high_returns)  # 输出: [0.06 0.08]

# 找出收益率在2%到5%之间的股票
mid_returns = returns[(returns >= 0.02) & (returns <= 0.05)]
print(mid_returns)  # 输出: [0.02 0.03 0.05 0.04]
💡 实战经验:我在做因子选股时,经常用布尔索引来筛选股票池。比如「选出市盈率低于20且ROE大于15%的所有股票」,一行代码就能搞定。这比用纯Python写循环快太多了。

3. 数学运算:让数据飞起来

终于到了最爽的部分。NumPy的数学运算,说白了就是「向量化操作」——不用写循环,直接对整个数组做运算。速度?那是真的快。

# 基础运算:逐元素操作
prices = np.array([28.5, 29.1, 28.8, 30.2, 29.9])

# 计算每日收益率(百分比变化)
daily_returns = (prices[1:] - prices[:-1]) / prices[:-1]
print(daily_returns)
# 输出: [ 0.02105 -0.01031  0.04861 -0.00993]

# 计算对数收益率(量化中更常用)
log_returns = np.diff(np.log(prices))
print(log_returns)

为什么量化中常用对数收益率?因为对数收益率具有可加性,多期收益率直接相加就行。嗯,这里不展开讲,你记住这个结论就好。

NumPy还提供了丰富的统计函数,写策略信号时几乎天天用:

# 统计函数
data = np.array([28.5, 29.1, 28.8, 30.2, 29.9, 31.0, 30.5])

print(f"均值: {np.mean(data):.2f}")      # 输出: 均值: 29.71
print(f"标准差: {np.std(data):.2f}")     # 输出: 标准差: 0.86
print(f"最大值: {np.max(data):.2f}")     # 输出: 最大值: 31.00
print(f"最小值: {np.min(data):.2f}")     # 输出: 最小值: 28.50

# 计算滚动均值(简单移动平均线)
def simple_moving_average(data, window=3):
    weights = np.ones(window) / window
    return np.convolve(data, weights, mode='valid')

sma_3 = simple_moving_average(data, 3)
print(sma_3)  # 输出: [28.8  29.37 29.63 30.37 30.47]
🔥 核心概念:广播机制
NumPy最强大的特性之一。不同形状的数组做运算时,NumPy会自动扩展小数组的维度。

比如:你想给每只股票的价格都加上一个基准偏移量:
prices = np.array([[28.5, 29.1], [15.2, 15.8]])
bias = np.array([0.1, 0.2])
result = prices + bias # 广播自动完成

结果就是第一列加0.1,第二列加0.2。不用写循环,不用手动扩展,NumPy全帮你搞定。

矩阵运算在量化投资中也经常用到,比如计算投资组合的方差:

# 矩阵乘法
# 假设我们有3只股票的权重向量和协方差矩阵
weights = np.array([0.4, 0.3, 0.3])
cov_matrix = np.array([
    [0.1, 0.02, 0.01],
    [0.02, 0.08, 0.03],
    [0.01, 0.03, 0.12]
])

# 计算组合方差:w^T * Σ * w
portfolio_variance = weights.T @ cov_matrix @ weights
print(f"组合方差: {portfolio_variance:.4f}")
# 输出: 组合方差: 0.0589
⚠️ 注意:Python 3.5+ 可以用 @ 运算符做矩阵乘法,比 np.dot() 更直观。但要注意,* 是逐元素乘法,@ 才是矩阵乘法。这两个搞混了,算出来的结果会完全不对。我曾经在写均值-方差优化时搞混过一次,优化出来的权重全是错的,排查了整整一个下午。

好了,NumPy的基础就讲到这里。创建数组、索引切片、数学运算——这三个技能掌握了,你已经能处理大部分量化数据了。下一节我们会继续讲NumPy的高级功能,包括如何用NumPy高效处理时间序列数据。

记住,做量化选股,数据处理的效率决定了你的策略迭代速度。NumPy就是你手中的利器,多用、多练,自然就熟练了。


专注资料整理