4. 数据清洗:处理缺失值、异常值检测与处理、数据对齐与重采样
数据清洗这步,说白了就是给原始数据「洗澡」。我见过太多人,模型调得花里胡哨,结果数据一塌糊涂,最后跑出来的结果连自己都不敢信。嗯,咱们今天就把这块硬骨头啃下来。
核心观点:脏数据进,脏数据出。你花在清洗上的每一分钟,都会在模型性能上得到回报。
4.1 缺失值处理:别让「空」坑了你
美股数据里,缺失值太常见了。比如节假日没交易,或者某些小盘股流动性差,某天直接没成交。我个人习惯,拿到数据第一件事就是检查缺失情况。
import pandas as pd
import numpy as np
# 加载数据
df = pd.read_csv('sp500_data.csv', index_col='Date', parse_dates=True)
# 检查缺失值
print(df.isnull().sum())
# 缺失比例
print(df.isnull().mean() * 100)
处理缺失值,我一般分三种情况:
- 直接删除:缺失比例小于5%,且数据量够大。简单粗暴,但要注意别把时间序列搞断了。
- 向前填充:用上一个有效值填充。适合价格数据,因为股价变化是连续的。我在做日内策略时常用这个。
- 插值法:线性插值或时间插值。适合成交量这类波动较大的指标。
我的经验:千万别用均值填充时间序列数据!我曾经犯过这个错,结果模型学到的全是假规律。股价有趋势,均值填充会破坏这种结构。
# 向前填充(推荐用于价格)
df['Close'].fillna(method='ffill', inplace=True)
# 线性插值(适合成交量)
df['Volume'].interpolate(method='linear', inplace=True)
# 时间插值(更精确)
df['Volume'].interpolate(method='time', inplace=True)
4.2 异常值检测:揪出那些「离谱」的数据点
异常值是什么?就是那些明显偏离正常范围的数据。比如某只股票一天涨了1000%,或者成交量突然变成平时的100倍。这些多半是数据录入错误,或者极端事件。
我常用的方法有三种:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| Z-Score | 正态分布数据 | 简单,但对极端值敏感 |
| IQR(四分位距) | 非正态分布 | 稳健,不受极端值影响 |
| 滚动窗口法 | 时间序列 | 考虑局部趋势,适合金融数据 |
我个人最常用的是滚动窗口法。为什么?因为金融数据有波动聚集性,今天涨10%可能正常,但放在过去30天里可能就是异常。
# 滚动窗口异常检测
window = 20
mean = df['Close'].rolling(window=window).mean()
std = df['Close'].rolling(window=window).std()
# 3倍标准差作为阈值
df['is_outlier'] = (abs(df['Close'] - mean) > 3 * std)
# 处理异常值:用中位数替换
df.loc[df['is_outlier'], 'Close'] = df['Close'].rolling(window=window).median()
注意:不要机械地删除所有异常值。有些异常值其实是市场信号,比如财报发布后的跳空缺口。我曾经因为误删这些「真异常」,导致模型错过了重要的价格发现。
4.3 数据对齐:让不同频率的数据「对上话」
做多因子模型时,你经常会遇到这种情况:价格数据是日频的,宏观经济数据是月频的,新闻情绪数据可能是不规则的。怎么把它们对齐?
说白了,就是让所有数据都站在同一个时间点上。我一般这样做:
- 低频转高频:用向前填充。比如把月度的GDP数据填充到每一天。
- 高频转低频:用聚合函数。比如把日频收益率聚合成周频。
- 对齐索引:确保所有数据框的索引一致。
# 假设有两个数据框
price_df = pd.DataFrame(...) # 日频
macro_df = pd.DataFrame(...) # 月频
# 低频转高频:向前填充
macro_daily = macro_df.reindex(price_df.index, method='ffill')
# 对齐索引
aligned_df = pd.concat([price_df, macro_daily], axis=1).dropna()
避坑指南:我曾经在回测时用了未来数据,就是因为对齐没做好。月频数据发布有滞后,你拿到数据时已经是下个月了。记得用 shift() 把数据往后挪一期。
4.4 重采样:改变数据的时间粒度
重采样就是把数据从一种频率变成另一种。比如把5分钟K线变成日K线,或者把日数据变成周数据。
为什么需要重采样?
- 不同策略需要不同频率的数据
- 降低噪声,看清长期趋势
- 匹配不同数据源的频率
# 日频转周频
weekly_df = df.resample('W').agg({
'Open': 'first',
'High': 'max',
'Low': 'min',
'Close': 'last',
'Volume': 'sum'
})
# 日频转月频
monthly_df = df.resample('M').agg({
'Open': 'first',
'High': 'max',
'Low': 'min',
'Close': 'last',
'Volume': 'sum'
})
嗯,这里要注意:重采样时聚合函数的选择很关键。开盘价用第一个,收盘价用最后一个,最高价用最大值,成交量用总和。这些是行业惯例,别搞混了。
知识体系总览
下面这张图,把数据清洗的整个流程串起来了。你想想看,从原始数据到干净数据,每一步都有坑,但每一步也都有解法。
数据清洗这件事,说白了就是「磨刀不误砍柴工」。我见过太多人急着建模,结果被脏数据坑得怀疑人生。你想想看,如果连数据都没洗干净,模型再花哨又有什么用?
最后说一句:清洗后的数据一定要做可视化检查。画个K线图,看看有没有明显的跳空或异常。眼睛有时候比统计指标更管用。