第四章:数据清洗与预处理——缺失值处理、异常值检测、复权数据处理

各位同学,欢迎来到第四章。说实话,数据清洗这活儿,看着不起眼,但我在实战中吃过不少亏。有一次我跑了一个主力资金模型,回测曲线漂亮得不像话,结果一上实盘就崩。查了三天,最后发现是原始数据里有一天的成交量少了个零。嗯,从那以后,我再也不敢轻视数据预处理了。

今天咱们就聊聊三个核心问题:缺失值怎么补?异常值怎么揪?复权数据怎么用? 说白了,就是让数据变得“干净”且“可用”。

数据清洗与预处理核心流程 原始行情数据 缺失值处理 异常值检测 复权 前复权 / 后复权 均值/中位数/前向填充 3σ原则 / IQR 干净可用的主力资金监控数据

一、缺失值处理——别让“空”坑了你

金融数据里,缺失值太常见了。停牌、节假日、数据源抽风,都会导致某天某列是空的。我个人习惯是:先看缺失比例,再决定策略

核心原则:

  • 缺失比例 < 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%的时间在数据清洗上——这笔投资,绝对值。


专注资料整理