第四章:数据清洗与预处理——缺失值处理、异常值检测、复权数据处理
各位同学,欢迎来到第四章。说实话,数据清洗这活儿,看着不起眼,但我在实战中吃过不少亏。有一次我跑了一个主力资金模型,回测曲线漂亮得不像话,结果一上实盘就崩。查了三天,最后发现是原始数据里有一天的成交量少了个零。嗯,从那以后,我再也不敢轻视数据预处理了。
今天咱们就聊聊三个核心问题:缺失值怎么补?异常值怎么揪?复权数据怎么用? 说白了,就是让数据变得“干净”且“可用”。
一、缺失值处理——别让“空”坑了你
金融数据里,缺失值太常见了。停牌、节假日、数据源抽风,都会导致某天某列是空的。我个人习惯是:先看缺失比例,再决定策略。
核心原则:
- 缺失比例 < 5%:直接填充,别犹豫
- 缺失比例 5% - 20%:需要谨慎选择填充方法
- 缺失比例 > 20%:建议删除该特征或重新考虑数据源
常用的填充方法,我列个表给大家参考:
| 方法 | 适用场景 | 代码实现 | 我的经验 |
|---|---|---|---|
| 前向填充 | 时间序列数据(如股价) | df.fillna(method='ffill') | 最常用,适合连续行情 |
| 均值/中位数填充 | 截面数据(如财务指标) | df.fillna(df.mean()) | 注意异常值会影响均值 |
| 插值法 | 趋势明显的序列 | df.interpolate() | 适合成交量这类有趋势的数据 |
| 模型预测填充 | 缺失较多且重要 | 用回归模型预测 | 慎用,容易过拟合 |
💡 我的小技巧: 对于主力资金监控,我通常先用前向填充处理价格和成交量。如果遇到连续多天缺失(比如停牌一周),我会用插值法平滑过渡。记住,千万别用0填充价格数据——那会让你的模型以为股票白送了。
二、异常值检测——揪出那些“离谱”的数据
异常值在金融数据里很要命。一次乌龙指、一次数据录入错误,都可能让你的模型误判。我常用的方法就两个:3σ原则和IQR(四分位距)。
1. 3σ原则——适合正态分布的数据
说白了,就是假设数据服从正态分布,超过均值±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
outliers = (data < lower) | (data > upper)
return outliers
⚠️ 注意: 金融数据往往有厚尾特征(极端值比正态分布多),3σ原则可能会漏掉一些真正的异常。我在处理收益率数据时,会适当放宽到2.5σ。
2. IQR方法——更稳健的选择
IQR不假设数据分布,用四分位距来定义异常。我个人更偏爱这个方法,因为它对极端值不敏感。
def detect_outliers_iqr(data):
Q1 = np.percentile(data, 25)
Q3 = np.percentile(data, 75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = (data < lower) | (data > upper)
return outliers
为什么用1.5倍?这是统计学里的经验值。如果你觉得太宽松,可以收紧到1.2倍;想宽松点,用2倍也行。我在主力资金监控里,对成交量用1.5倍,对价格用2倍——因为价格异常往往更少。
实战对比:
- 3σ原则:适合收益率、因子值等近似正态的数据
- IQR方法:适合成交量、换手率等偏态分布的数据
- 我的建议:两个都跑一遍,取交集或并集,看业务逻辑决定
三、复权数据处理——别被“除权”骗了
这个问题,我当年刚入行时栽过大跟头。股票分红送股后,价格会跳空。如果你直接用不复权数据算收益率,那结果完全是错的。
复权有两种方式:
| 类型 | 含义 | 适用场景 |
|---|---|---|
| 前复权 | 调整历史价格,让价格连续 | 回测、技术分析(最常用) |
| 后复权 | 调整当前价格,保持历史不变 | 计算真实收益率、分红再投资 |
我个人习惯用前复权做回测。为什么呢?因为前复权后的价格曲线是连续的,均线、MACD这些指标不会出现虚假信号。但要注意:前复权后的价格不是真实成交价,只是数学上的调整。
# 使用akshare获取复权数据(示例)
import akshare as ak
# 获取前复权数据
df_fq = ak.stock_zh_a_hist(symbol="000001", adjust="qfq")
# 获取后复权数据
df_hfq = ak.stock_zh_a_hist(symbol="000001", adjust="hfq")
💡 避坑指南: 我曾经用后复权数据算主力资金流入,结果发现资金流和价格走势对不上。后来才意识到,后复权会放大早期价格,导致资金流计算失真。所以做主力资金监控时,我统一用前复权。
四、完整预处理流程——串起来用
好了,三个核心问题讲完了。咱们把它们串成一个完整的流程:
def data_preprocessing(df):
# 1. 缺失值处理
df = df.fillna(method='ffill') # 前向填充
df = df.fillna(method='bfill') # 剩余缺失用后向填充
# 2. 异常值检测(以成交量为例)
outliers = detect_outliers_iqr(df['volume'])
df.loc[outliers, 'volume'] = np.nan # 异常值置空
df['volume'] = df['volume'].interpolate() # 插值填充
# 3. 复权处理(假设已获取前复权数据)
# 这一步通常在数据获取阶段完成
return df
这个流程我用了好几年,基本没出过问题。你想想看,数据干净了,模型才能跑得稳。否则就是“垃圾进,垃圾出”。
最后提醒一句: 数据预处理没有银弹。每个数据集都有自己的脾气,多观察、多尝试、多验证。我在每个新项目开始时,都会花至少20%的时间在数据清洗上——这笔投资,绝对值。