第四章:财务数据清洗与预处理

做量化投资的朋友都知道一句话:垃圾进,垃圾出。我干了这么多年,见过太多人拿着漂亮模型往脏数据上一跑,结果惨不忍睹。数据清洗这步,说白了就是给财务数据「洗澡」。洗不干净,后面全是白费功夫。

4.1 缺失值处理:别让「空」坑了你

财务数据里缺失值太常见了。公司停牌、数据源断更、报表调整,都会留下空值。我刚开始做的时候,直接删掉有缺失的行,结果回测曲线漂亮得不行,实盘一跑就崩——后来才发现,那些缺失值往往出现在极端行情里。

⚠️ 注意: 千万别无脑删除缺失值。尤其是时间序列数据,删一行可能破坏整个序列的连续性。

我个人习惯分三步处理:

  1. 先诊断:看看缺失比例。低于5%的,用均值或中位数填充就行。超过30%的,我建议直接放弃这个字段。
  2. 再选方法:财务数据用前向填充(ffill)最稳妥。比如某公司一季度没披露营收,用上一季度的值顶上去,比瞎猜靠谱。
  3. 最后验证:填充完跑个简单统计,看看分布有没有突变。
import pandas as pd
import numpy as np

# 加载财务数据
df = pd.read_csv('financial_data.csv', index_col='date', parse_dates=True)

# 检查缺失比例
missing_ratio = df.isnull().mean()
print('缺失比例:\n', missing_ratio[missing_ratio > 0])

# 前向填充(财务数据常用)
df.fillna(method='ffill', inplace=True)

# 如果开头还有缺失,用后向填充兜底
df.fillna(method='bfill', inplace=True)
💡 我的经验: 遇到那种连续缺失超过3期的数据,我一般直接扔掉。为什么?因为连续缺失说明这家公司信息披露有问题,留着也是隐患。

4.2 异常值检测:揪出「捣乱分子」

异常值是什么?就是那些明显离谱的数据。比如某公司毛利率突然飙到200%,或者营收一夜之间翻了100倍。你想想看,这合理吗?

我曾经在回测一个价值因子时,发现某只股票的市盈率突然变成负数——不是亏损那种负,而是数据录入错误导致的-9999。这个异常值直接拉偏了整个因子的分布,差点让我放弃一个本来有效的策略。

常用的检测方法就三种:

方法 适用场景 阈值
Z-score 正态分布的数据 |Z| > 3
IQR(四分位距) 偏态分布的数据 Q1 - 1.5*IQR 或 Q3 + 1.5*IQR
MAD(中位数绝对偏差) 含较多异常值的数据 MAD > 3.5
from scipy import stats

# Z-score 方法
z_scores = np.abs(stats.zscore(df['pe_ratio']))
df_clean = df[(z_scores < 3)]

# IQR 方法
Q1 = df['pe_ratio'].quantile(0.25)
Q3 = df['pe_ratio'].quantile(0.75)
IQR = Q3 - Q1
df_clean = df[(df['pe_ratio'] >= Q1 - 1.5*IQR) & 
              (df['pe_ratio'] <= Q3 + 1.5*IQR)]
🔑 核心原则: 异常值不一定要删除。有时候异常值本身就是信号——比如突然的业绩暴雷。我建议先标记出来,单独分析。

4.3 数据标准化与归一化:让不同量纲的数据「对话」

这个问题我踩过坑。早期做多因子模型时,把市盈率(几十倍)和市净率(几倍)直接放一起算综合得分,结果市盈率完全主导了结果。说白了,量纲不同,没法直接比。

标准化和归一化,就是解决这个问题的。我一般这样选:

  • 归一化(Min-Max):把数据压到[0,1]区间。适合分布比较均匀的数据,比如换手率、波动率。
  • 标准化(Z-score):变成均值为0、标准差为1的分布。适合有异常值的数据,比如市盈率、市净率。
  • RobustScaler:用中位数和四分位距做。我个人的最爱,对异常值不敏感。
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# 标准化(Z-score)
scaler_std = StandardScaler()
df['pe_standardized'] = scaler_std.fit_transform(df[['pe_ratio']])

# 归一化(Min-Max)
scaler_mm = MinMaxScaler()
df['turnover_normalized'] = scaler_mm.fit_transform(df[['turnover_rate']])

# RobustScaler(推荐)
scaler_robust = RobustScaler()
df['pb_robust'] = scaler_robust.fit_transform(df[['pb_ratio']])
💡 避坑指南: 我曾经在回测时对整个数据集做了标准化,结果引入了未来信息。正确的做法是:用训练集的参数去标准化测试集。记住,测试集的数据在回测时是「看不到」的。

4.4 时间序列重采样:对齐不同频率的数据

财务数据有个麻烦事:不同指标更新频率不一样。营收数据按季度出,股价数据每天都有,估值指标可能每周变一次。怎么把它们对齐?重采样。

嗯,这里要注意:重采样不是简单的插值。财务数据有它自己的节奏。

  • 日频转周频:用收盘价取最后一天,成交量求和。
  • 周频转月频:财务指标取月末值,避免月中波动干扰。
  • 季频转日频:这个最坑。我建议用前向填充,别用插值。因为季报数据在发布前是未知的,插值等于用了未来数据。
# 日频转周频
df_weekly = df.resample('W').agg({
    'close': 'last',      # 收盘价取最后一天
    'volume': 'sum',      # 成交量求和
    'pe_ratio': 'last'    # 市盈率取周末值
})

# 季频转日频(前向填充)
df_quarterly = df.resample('Q').last()
df_daily = df_quarterly.resample('D').ffill()
⚠️ 重要提醒: 重采样时一定要搞清楚「时间戳」的含义。财务数据的时间戳通常是「报告期」,不是「发布日期」。两者可能差好几个月。我建议用「发布日期」做对齐,否则会引入前瞻偏差。

4.5 本章知识体系

下面这张图是我自己梳理的清洗流程,每次做项目前都会看一眼,防止漏步骤。

财务数据清洗与预处理流程 原始财务数据 步骤1:缺失值处理 步骤2:异常值检测与处理 步骤3:标准化 / 归一化 步骤4:时间序列重采样 关键要点 • 缺失<5%:填充 • 缺失>30%:删除 • 异常值:标记优先 • 标准化:Z-score • 归一化:Min-Max • 重采样:前向填充 • 避免未来信息 • 用发布日期对齐

这四步走完,数据基本就干净了。我个人习惯在清洗完后,再跑一遍描述性统计,看看每个字段的均值、标准差有没有异常变化。如果有,回头检查一下是不是哪步操作出了问题。

记住一句话:数据清洗花的时间,会在模型效果上十倍地还给你。别嫌麻烦。

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