第三章:Pandas数据处理核心

说实话,做量化投资这几年,我见过太多人把精力花在找所谓的「神奇因子」上。但真正让我在实盘中赚到钱的,反而是那些最基础的数据处理功夫。Pandas就是你的左膀右臂,今天咱们就把这块硬骨头啃下来。

本章核心:掌握DataFrame与Series的底层逻辑,学会清洗脏数据,玩转时间序列。这些都是我每天开盘前必做的功课。

3.1 DataFrame与Series:量化数据的基石

先说说这两个最基础的数据结构。Series说白了就是一列带标签的数据,而DataFrame就是多列Series拼成的表格。我刚开始用的时候总觉得差不多,直到有一次回测结果对不上,才发现是索引对齐出了问题。

import pandas as pd
import numpy as np

# 创建一个Series,就像股票的一列收盘价
close_prices = pd.Series([10.5, 10.8, 10.3, 11.0, 10.9],
                        index=['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'],
                        name='close')
print(close_prices)

# DataFrame就是多只股票的行情表
data = {
    'stock_A': [10.5, 10.8, 10.3, 11.0, 10.9],
    'stock_B': [20.1, 20.5, 19.8, 21.0, 20.7],
    'stock_C': [15.0, 15.3, 14.9, 15.5, 15.2]
}
df = pd.DataFrame(data, index=close_prices.index)
print(df)

我的小经验:创建DataFrame时,一定要先确认索引类型。我吃过亏,字符串索引和datetime索引混用,导致merge时全乱了。

3.2 数据清洗:脏数据是回测的毒药

你想想看,如果原始数据里就有缺失值和异常值,那算出来的因子能靠谱吗?我见过有人用含空值的因子跑出年化50%的回测,实盘直接亏到怀疑人生。

3.2.1 缺失值处理

缺失值在量化数据里太常见了。停牌、数据源故障、节假日,都会导致空值。我的原则是:能不删就不删,能填充就填充。

# 模拟有缺失值的数据
df_with_na = df.copy()
df_with_na.loc['2024-01-03', 'stock_A'] = np.nan
df_with_na.loc['2024-01-04', 'stock_B'] = np.nan

print("原始数据:")
print(df_with_na)

# 方法1:向前填充(我最常用的)
df_ffill = df_with_na.fillna(method='ffill')
print("\n向前填充后:")
print(df_ffill)

# 方法2:用均值填充
df_mean = df_with_na.fillna(df_with_na.mean())
print("\n均值填充后:")
print(df_mean)

# 方法3:删除含空值的行(慎用)
df_drop = df_with_na.dropna()
print("\n删除空值后:")
print(df_drop)

避坑指南:我曾经在回测中用了dropna(),结果发现样本量少了30%,因子显著性全变了。后来我改用向前填充,效果好了很多。记住:删除数据前,先想想它为什么缺失。

3.2.2 异常值处理

异常值比缺失值更隐蔽。我记得有一次,某只股票突然出现-9999的收盘价,明显是数据录入错误。如果不处理,算出来的收益率能吓死人。

# 用3倍标准差法检测异常值
def detect_outliers(series, n_std=3):
    mean = series.mean()
    std = series.std()
    outliers = (series < mean - n_std * std) | (series > mean + n_std * std)
    return outliers

# 模拟异常值
df_outlier = df.copy()
df_outlier.loc['2024-01-02', 'stock_A'] = 100  # 明显异常

outliers = detect_outliers(df_outlier['stock_A'])
print("异常值标记:")
print(outliers)

# 处理异常值:用中位数替换
median_val = df_outlier['stock_A'].median()
df_outlier.loc[outliers, 'stock_A'] = median_val
print("\n处理后:")
print(df_outlier)

我的习惯:对于金融数据,我一般用中位数而不是均值来替换异常值。因为均值容易被极端值影响,而中位数更稳健。

3.3 时间序列数据处理:量化分析的核心

量化投资说白了就是和时间赛跑。Pandas的时间序列功能,是我每天使用频率最高的工具。

3.3.1 时间索引与切片

# 将索引转换为datetime类型
df.index = pd.to_datetime(df.index)

# 按年份切片
print("2024年数据:")
print(df['2024'])

# 按月份切片
print("\n2024年1月数据:")
print(df['2024-01'])

# 按日期范围切片
print("\n1月2日到1月4日:")
print(df['2024-01-02':'2024-01-04'])

3.3.2 重采样:改变数据频率

做量化时,经常需要把日线数据转成周线或月线。重采样就是干这个的。我一般用收盘价做周线,用成交量做周累计。

# 模拟更多日线数据
dates = pd.date_range('2024-01-01', periods=20, freq='D')
df_daily = pd.DataFrame({
    'close': np.random.randn(20).cumsum() + 100,
    'volume': np.random.randint(1000, 5000, 20)
}, index=dates)

# 重采样为周线
weekly = df_daily.resample('W').agg({
    'close': 'last',      # 周收盘价
    'volume': 'sum'       # 周成交量
})
print("周线数据:")
print(weekly)

# 重采样为月线
monthly = df_daily.resample('M').agg({
    'close': 'last',
    'volume': 'sum'
})
print("\n月线数据:")
print(monthly)

关键点:重采样时,不同字段要用不同的聚合函数。收盘价用last,成交量用sum,最高价用max,最低价用min。千万别搞混了。

3.3.3 滚动窗口计算:因子的灵魂

滚动窗口是计算技术指标的基础。移动平均线、布林带、RSI,都离不开它。我刚开始做因子挖掘时,就是靠滚动窗口算出了第一个有效因子。

# 计算5日移动平均线
df_daily['ma_5'] = df_daily['close'].rolling(window=5).mean()

# 计算20日滚动标准差(布林带用)
df_daily['std_20'] = df_daily['close'].rolling(window=20).std()

# 计算滚动相关系数(因子相关性分析)
df_daily['corr_5'] = df_daily['close'].rolling(window=5).corr(df_daily['volume'])

print("滚动窗口计算结果:")
print(df_daily.head(10))

避坑指南:我曾经用滚动窗口算因子时,忘了处理前几期的NaN值。结果回测时,前几天的因子全是空的,导致策略无法交易。记住:滚动窗口的前window-1期都是NaN,需要手动填充或截断。

3.4 实战:构建一个简单的动量因子

说了这么多,咱们来点实际的。用滚动窗口构建一个动量因子,这是量化里最经典的因子之一。

# 构建动量因子:过去5天的收益率
def momentum_factor(close_prices, window=5):
    # 计算收益率
    returns = close_prices.pct_change()
    # 滚动求和
    momentum = returns.rolling(window=window).sum()
    return momentum

# 应用因子
df_daily['momentum_5'] = momentum_factor(df_daily['close'], window=5)

# 因子标准化(Z-score)
df_daily['momentum_z'] = (df_daily['momentum_5'] - df_daily['momentum_5'].mean()) / df_daily['momentum_5'].std()

print("动量因子结果:")
print(df_daily[['close', 'momentum_5', 'momentum_z']].tail(10))

我的经验:因子构建后一定要做标准化,不然不同股票的因子值没法比较。我习惯用Z-score,简单有效。

3.5 本章知识体系

下面这张图是我自己总结的Pandas数据处理核心逻辑,每次写代码前我都会看一眼,确保思路清晰。

Pandas数据处理核心知识体系 Pandas核心 数据结构基础 Series:一维标签数组 DataFrame:二维表格 索引对齐与类型转换 数据清洗 缺失值:填充/删除 异常值:检测与替换 数据一致性检查 时间序列处理 时间索引与切片 重采样:改变频率 滚动窗口计算 实战应用:动量因子构建 数据清洗 → 时间序列处理 → 滚动窗口计算 → 因子标准化

嗯,到这里,Pandas数据处理的核心内容就讲完了。这些技能我每天都在用,从数据清洗到因子构建,每一步都离不开它们。记住:好的因子来自干净的数据,干净的数据来自扎实的基础。

专注资料整理