金融数据清洗基础:缺失值处理与异常值检测
做金融量化分析的人都知道一句话:垃圾进,垃圾出。数据清洗这一步,我见过太多人栽跟头了。今天咱们就聊聊金融数据清洗里最核心的三个问题:缺失值怎么补、异常值怎么抓、数据怎么标准化。
核心观点:金融数据清洗不是简单的"删掉坏数据",而是要在保留信息量和保证数据质量之间找平衡。说白了,你删掉的每一行数据,背后可能都是真金白银的交易信号。
一、缺失值处理:别急着删,先想想
金融数据里缺失值太常见了。停牌日、节假日、数据源故障,都会导致空值。我刚开始做量化那会儿,遇到缺失值就直接 dropna(),后来发现回测曲线漂亮得不行,实盘却一塌糊涂。为什么?因为删掉的数据里藏着重要的市场信息。
1. 直接删除法
什么时候可以删?我个人习惯是:缺失比例低于 5%,且缺失完全是随机的。比如某只股票因为数据商问题缺失了某天的交易量,这种删了问题不大。
# 删除含有缺失值的行
df.dropna(inplace=True)
# 删除缺失比例超过阈值的列
threshold = len(df) * 0.3 # 30% 缺失就删列
df.dropna(thresh=threshold, axis=1, inplace=True)
⚠️ 注意:金融时序数据里,千万别在中间随意删除行。你删掉一行,时间序列就断了,后面算收益率、滚动指标全都会错位。我曾经因为这个坑,回测多赚了 5 个点,实盘直接打脸。
2. 均值/中位数填充
这是最常用的方法。但金融数据有个特点——它往往有偏。比如收益率数据,极端值很多,均值会被拉偏。这时候中位数更靠谱。
# 均值填充(适合正态分布的数据)
df['close'].fillna(df['close'].mean(), inplace=True)
# 中位数填充(适合有偏分布,金融数据推荐)
df['volume'].fillna(df['volume'].median(), inplace=True)
💡 实战经验:我处理沪深 300 成分股数据时,发现成交量分布严重右偏。用均值填充会把缺失值填得偏高,导致后续的流动性指标失真。换成中位数后,效果好了很多。
3. 前向填充(Forward Fill)
金融时序数据里,前向填充是我最常用的方法。逻辑很简单:用上一个有效值填充当前缺失值。这符合金融数据的特性——价格不会凭空消失,停牌后的第一个交易日,价格往往从停牌前的位置开始。
# 前向填充
df['close'].fillna(method='ffill', inplace=True)
# 限制填充步数,最多向前填充 3 个交易日
df['close'].fillna(method='ffill', limit=3, inplace=True)
为什么要限制步数?你想啊,如果一只股票停牌一个月,你用一个月前的价格填充,那这数据还有意义吗?我一般限制在 5 个交易日以内。
二、异常值检测:别把黑天鹅当噪声
金融数据里的异常值,有时候是数据错误,有时候是真正的市场异动。怎么区分?我总结了三个方法,从简单到复杂。
1. 3-sigma 法
假设数据服从正态分布,超过均值 ±3 倍标准差的值就是异常。这个方法简单粗暴,但金融数据往往不服从正态分布,所以容易误判。
import numpy as np
def detect_outliers_3sigma(data):
mean = np.mean(data)
std = np.std(data)
lower = mean - 3 * std
upper = mean + 3 * std
return (data < lower) | (data > upper)
# 使用
outliers = detect_outliers_3sigma(df['return'])
print(f"检测到 {outliers.sum()} 个异常值")
⚠️ 注意:3-sigma 对极端值非常敏感。如果数据本身就有几个极端值,均值和标准差都会被拉偏,导致检测失效。我建议先用 IQR 法过滤一遍,再用 3-sigma。
2. IQR 法(四分位距法)
这个方法不受极端值影响,更稳健。用 Q1 - 1.5*IQR 和 Q3 + 1.5*IQR 作为边界。金融数据里,我更喜欢用这个方法。
def detect_outliers_iqr(data):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return (data < lower) | (data > upper)
# 使用
outliers = detect_outliers_iqr(df['volume'])
print(f"IQR 法检测到 {outliers.sum()} 个异常值")
💡 实战经验:处理美股分钟级数据时,我遇到过某只股票因为财报发布,成交量瞬间放大 50 倍。IQR 法把它标成了异常值。但我知道这不是数据错误,而是真实的市场事件。所以异常值检测之后,一定要结合业务判断。
3. 业务规则法
这是最灵活的方法。比如股票涨跌幅限制、交易时间限制、价格不能为负等。我习惯把业务规则写在最前面,先过滤掉明显不合理的数据。
# A 股涨跌幅限制(非新股)
df = df[(df['pct_change'] >= -0.1) & (df['pct_change'] <= 0.1)]
# 交易量不能为负
df = df[df['volume'] > 0]
# 价格不能为零
df = df[df['close'] > 0]
三、数据标准化与归一化:让不同量纲的数据能对话
金融数据里,价格是几十到几百,成交量是几万到几亿,收益率是 -0.1 到 0.1。量纲不同,直接扔进模型里,大数会吃掉小数。所以标准化是必须的。
1. Z-score 标准化
把数据变成均值为 0、标准差为 1 的分布。适合数据本身近似正态分布的情况。金融里的收益率数据,用 Z-score 效果不错。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df['return_scaled'] = scaler.fit_transform(df[['return']])
# 手动计算验证
mean = df['return'].mean()
std = df['return'].std()
df['return_scaled_manual'] = (df['return'] - mean) / std
2. Min-Max 归一化
把数据缩放到 [0, 1] 区间。适合有明确上下界的数据,比如技术指标 RSI(0-100)、KDJ(0-100)。但注意,如果新数据超出了原来的范围,归一化会失效。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(feature_range=(0, 1))
df['rsi_normalized'] = scaler.fit_transform(df[['rsi']])
# 手动计算
min_val = df['rsi'].min()
max_val = df['rsi'].max()
df['rsi_normalized_manual'] = (df['rsi'] - min_val) / (max_val - min_val)
⚠️ 注意:Min-Max 对异常值非常敏感。如果数据里有一个极端值,其他数据都会被压缩到很小的区间里。我建议先处理异常值,再做归一化。
3. RobustScaler(稳健标准化)
这是我个人最推荐的方法。它用中位数和四分位距代替均值和标准差,对异常值不敏感。金融数据里,异常值太多了,RobustScaler 是救命稻草。
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
df['volume_scaled'] = scaler.fit_transform(df[['volume']])
# 手动计算
median = df['volume'].median()
Q1 = df['volume'].quantile(0.25)
Q3 = df['volume'].quantile(0.75)
IQR = Q3 - Q1
df['volume_scaled_manual'] = (df['volume'] - median) / IQR
💡 实战经验:做多因子模型时,我同时用了市盈率(PE)和市净率(PB)。PE 偶尔会出现几百甚至几千的极端值,如果用 Min-Max 归一化,大部分股票的 PE 都会被压到 0.01 以下,完全失去了区分度。换成 RobustScaler 后,问题迎刃而解。
四、清洗流程总结
说了这么多,我一般按这个顺序处理数据:
- 业务规则过滤:先干掉明显不合理的数据(价格负值、涨跌幅超限等)
- 缺失值处理:时序数据用前向填充,截面数据用中位数填充
- 异常值检测:先用 IQR 法粗筛,再用 3-sigma 精筛,最后人工确认
- 标准化/归一化:优先用 RobustScaler,其次是 Z-score,最后才是 Min-Max
这个流程我用了好几年,踩过不少坑,也积累了一些经验。记住一点:数据清洗没有银弹,每个方法都有适用场景。你得多试、多对比,找到最适合你数据的那一套方案。
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 直接删除 | 缺失比例 < 5%,随机缺失 | 简单、不影响分布 | 丢失信息,破坏时序 |
| 均值/中位数填充 | 缺失比例适中,数据稳定 | 保留样本量 | 降低方差,引入偏差 |
| 前向填充 | 时序数据,停牌、节假日 | 符合金融逻辑 | 填充过多会失真 |
| 3-sigma 法 | 近似正态分布的数据 | 理论成熟 | 对极端值敏感 |
| IQR 法 | 有偏分布,金融数据首选 | 稳健,不受极端值影响 | 边界选择较主观 |
| Z-score 标准化 | 收益率等近似正态的数据 | 保留分布形状 | 不适用于有界数据 |
| Min-Max 归一化 | 有明确上下界的数据 | 结果在 [0,1] 区间 | 对异常值敏感 |
| RobustScaler | 金融数据,含异常值 | 最稳健,推荐使用 | 计算稍复杂 |
好了,数据清洗这块就聊到这儿。记住,清洗不是目的,让数据能真实反映市场才是。下一章咱们聊聊特征工程,到时候见。