NumPy 基础:数组创建、数组属性、索引与切片、广播机制

NumPy 是 Python 数据科学生态里的基石。说白了,没有 NumPy,后面那些 pandas、scikit-learn 都得趴窝。我刚开始做量化回测那会儿,天天跟它打交道,今天就把最核心的几个点给你捋清楚。

1. 数组创建:从零开始搭积木

创建数组的方式很多,但最常用的就那么几种。我个人习惯用 np.array() 直接传列表,简单粗暴。

import numpy as np

# 从列表创建
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)  # [1 2 3 4 5]

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

但实际项目中,更多时候我们需要特定结构的数组。比如初始化一个全零矩阵来存因子值:

# 全零数组,常用于占位
zeros = np.zeros((3, 4))
print(zeros)

# 全一数组
ones = np.ones((2, 3))

# 单位矩阵
eye = np.eye(5)

# 等差数列,做时间序列时常用
arange = np.arange(0, 10, 2)  # [0 2 4 6 8]
linspace = np.linspace(0, 1, 5)  # [0.   0.25 0.5  0.75 1.  ]
我的小经验: 做因子挖掘时,np.nan 填充的数组特别有用。比如某只股票停牌了,我就用 np.full((n_days, n_stocks), np.nan) 先占个坑,后面再填充真实值。

2. 数组属性:摸清它的底细

拿到一个数组,第一件事就是看它的形状和维度。你想想看,如果连数据长啥样都不清楚,后面怎么切片、怎么运算?

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

print(arr.shape)   # (2, 3)  —— 2行3列
print(arr.ndim)    # 2       —— 二维数组
print(arr.size)    # 6       —— 总共6个元素
print(arr.dtype)   # int64   —— 数据类型
print(arr.itemsize) # 8      —— 每个元素占8字节

这里有个坑: 我曾经在合并多个因子矩阵时,没注意两个数组的 dtype 不一致,结果一个 float64 一个 int64,运算结果全变成了 int,精度丢失得一塌糊涂。所以每次创建数组,我习惯显式指定 dtype

arr_float = np.array([1, 2, 3], dtype=np.float32)

3. 索引与切片:精准定位数据

NumPy 的索引和 Python 列表很像,但多了个维度。嗯,这里要注意:多维数组的索引是用逗号分隔的。

arr = np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

# 取单个元素
print(arr[1, 2])  # 7

# 取一行
print(arr[0])     # [1 2 3 4]

# 取一列
print(arr[:, 1])  # [2 6 10]

# 切片:取前两行,第1到第3列
print(arr[:2, 1:3])
# [[2 3]
#  [6 7]]
布尔索引——量化中的利器: 比如我想找出所有收益率大于5%的股票:
returns = np.array([0.03, 0.06, -0.02, 0.08, 0.01])
mask = returns > 0.05
print(returns[mask])  # [0.06 0.08]

为什么会这样?因为 returns > 0.05 返回了一个布尔数组,NumPy 自动用这个布尔数组去筛选元素。我在做因子选股时,经常用这种技巧来过滤掉 ST 股票或者停牌股。

4. 广播机制:不同形状也能运算

广播是 NumPy 最强大的特性之一,也是新手最容易踩坑的地方。说白了,就是让不同形状的数组能够进行算术运算。

核心规则: 从尾部维度开始比较,要么相等,要么其中一个为1。

# 最简单的例子:标量广播
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr + 10)
# [[11 12 13]
#  [14 15 16]]

# 一维数组广播到二维
row = np.array([10, 20, 30])
print(arr + row)
# [[11 22 33]
#  [14 25 36]]

# 列向量广播
col = np.array([[10], [20]])
print(arr + col)
# [[11 12 13]
#  [24 25 26]]
避坑指南: 我曾经在计算股票日收益率时,把 shape 为 (500, 1) 的昨日收盘价和 shape 为 (500,) 的今日收盘价直接相减,结果得到了一个 (500, 500) 的矩阵!因为 NumPy 把 (500,) 当成了行向量,广播后变成了所有股票两两相减。正确的做法是确保维度对齐:要么都用二维,要么用 reshape(-1, 1) 统一形状。

广播机制在因子标准化时特别有用。比如 z-score 标准化,需要每只股票减去自身均值再除以标准差:

# 假设 factor 是 (n_days, n_stocks) 的矩阵
factor = np.random.randn(100, 50)

# 计算每只股票的均值和标准差
mean = factor.mean(axis=0)   # shape (50,)
std = factor.std(axis=0)     # shape (50,)

# 广播:自动将 (50,) 扩展到 (100, 50)
factor_z = (factor - mean) / std

你看,这里 factor 是 (100, 50),mean 是 (50,),广播机制自动把 mean 沿着行方向复制了100份。如果没有广播,你得手动写循环,那效率就太低了。

知识体系总览

下面这张图把 NumPy 基础的核心脉络串起来了,你可以对照着复习:

NumPy 基础 数组创建 np.array() np.zeros / np.ones np.arange / np.linspace np.random.randn 数组属性 shape / ndim size / dtype itemsize 索引与切片 多维索引 [i, j] 切片 [start:stop] 布尔索引 花式索引 广播机制 标量广播 维度对齐规则 避免形状不匹配

NumPy 的这些基础操作,说白了就是量化工程师的「内功心法」。数组创建是起手式,属性是内视己身,索引切片是精准打击,广播机制则是四两拨千斤的巧劲。把这些练熟了,后面做因子计算、回测框架,你会觉得顺手很多。

最后说一句: 别小看这些基础。我见过不少同学一上来就搞复杂的因子模型,结果连个简单的切片都写错,debug 花了大半天。先把地基打牢,后面才能盖高楼。

蓝海数据掘金营,专注资料整理