金融数据清洗基础:缺失值处理与异常值检测

做金融量化分析的人都知道一句话:垃圾进,垃圾出。数据清洗这一步,我见过太多人栽跟头了。今天咱们就聊聊金融数据清洗里最核心的三个问题:缺失值怎么补、异常值怎么抓、数据怎么标准化。

核心观点:金融数据清洗不是简单的"删掉坏数据",而是要在保留信息量和保证数据质量之间找平衡。说白了,你删掉的每一行数据,背后可能都是真金白银的交易信号。

金融数据清洗 缺失值处理 异常值检测 标准化与归一化 直接删除 均值/中位数填充 前向填充 3-sigma 法 IQR 法 业务规则法 Z-score 标准化 Min-Max 归一化 RobustScaler

一、缺失值处理:别急着删,先想想

金融数据里缺失值太常见了。停牌日、节假日、数据源故障,都会导致空值。我刚开始做量化那会儿,遇到缺失值就直接 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 后,问题迎刃而解。

四、清洗流程总结

说了这么多,我一般按这个顺序处理数据:

  1. 业务规则过滤:先干掉明显不合理的数据(价格负值、涨跌幅超限等)
  2. 缺失值处理:时序数据用前向填充,截面数据用中位数填充
  3. 异常值检测:先用 IQR 法粗筛,再用 3-sigma 精筛,最后人工确认
  4. 标准化/归一化:优先用 RobustScaler,其次是 Z-score,最后才是 Min-Max

这个流程我用了好几年,踩过不少坑,也积累了一些经验。记住一点:数据清洗没有银弹,每个方法都有适用场景。你得多试、多对比,找到最适合你数据的那一套方案。

方法 适用场景 优点 缺点
直接删除 缺失比例 < 5%,随机缺失 简单、不影响分布 丢失信息,破坏时序
均值/中位数填充 缺失比例适中,数据稳定 保留样本量 降低方差,引入偏差
前向填充 时序数据,停牌、节假日 符合金融逻辑 填充过多会失真
3-sigma 法 近似正态分布的数据 理论成熟 对极端值敏感
IQR 法 有偏分布,金融数据首选 稳健,不受极端值影响 边界选择较主观
Z-score 标准化 收益率等近似正态的数据 保留分布形状 不适用于有界数据
Min-Max 归一化 有明确上下界的数据 结果在 [0,1] 区间 对异常值敏感
RobustScaler 金融数据,含异常值 最稳健,推荐使用 计算稍复杂

好了,数据清洗这块就聊到这儿。记住,清洗不是目的,让数据能真实反映市场才是。下一章咱们聊聊特征工程,到时候见。

专注资料整理