异常值处理:3σ原则、IQR方法、基于分位数的截断、Winsorize处理

做量化交易的朋友都知道,数据里藏着"脏东西"是家常便饭。尤其是异常值,处理不好,模型直接崩给你看。我这些年踩过的坑,十有八九都跟异常值有关。

今天咱们就把异常值处理这四种方法聊透。3σ原则、IQR方法、分位数截断、Winsorize处理,一个一个来。

一、先说说异常值到底是个啥

异常值,说白了就是那些"不合群"的数据点。比如某只股票平时波动1%,突然某天涨了20%,这大概率是数据录入错误,或者发生了极端事件。

我个人习惯把异常值分成两类:

  • 自然异常值:比如金融危机时的暴跌,这是真实的市场行为
  • 错误异常值:比如数据采集时多打了个0,或者传感器故障

你想想看,如果混进一个错误数据,模型会拼命去拟合这个"假信号",结果就是实盘时一塌糊涂。所以,处理异常值不是可选项,是必选项。

二、3σ原则:最经典的方法

3σ原则基于正态分布假设。数据落在均值±3个标准差之外的概率只有0.3%,这些点就可以视为异常值。

核心公式:如果 |x - μ| > 3σ,则判定为异常值

代码实现很简单:

import numpy as np
import pandas as pd

def remove_outliers_3sigma(data, col):
    mean = data[col].mean()
    std = data[col].std()
    lower = mean - 3 * std
    upper = mean + 3 * std
    return data[(data[col] >= lower) & (data[col] <= upper)]

# 使用示例
df_clean = remove_outliers_3sigma(df, 'close_price')

我的经验:3σ原则对金融数据其实不太友好。因为收益率分布通常有"肥尾"特征,3σ会误杀很多真实的市场波动。我一般只在数据质量检查的初步阶段用一下。

三、IQR方法:更稳健的选择

IQR(四分位距)方法不依赖正态分布假设,它用中位数和四分位数来界定异常值。我个人更偏爱这个方法,尤其是在处理非对称分布的数据时。

计算步骤:

  1. 计算第一四分位数Q1(25%分位数)
  2. 计算第三四分位数Q3(75%分位数)
  3. IQR = Q3 - Q1
  4. 下界 = Q1 - 1.5 * IQR,上界 = Q3 + 1.5 * IQR
def remove_outliers_iqr(data, col):
    Q1 = data[col].quantile(0.25)
    Q3 = data[col].quantile(0.75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    return data[(data[col] >= lower) & (data[col] <= upper)]

# 使用示例
df_clean = remove_outliers_iqr(df, 'volume')

注意:1.5这个系数是经验值。对于金融数据,我建议调整到2或2.5,否则会剔除太多有效数据。我曾经在回测时用默认的1.5,结果发现2015年股灾的数据被删掉了一大半,回测结果变得异常漂亮——但那是假的。

四、基于分位数的截断:简单粗暴但有效

这个方法更直接——直接砍掉数据两端一定比例的值。比如去掉最小的1%和最大的1%。

为什么需要这个方法?因为有些场景下,我们不需要判断"是不是异常值",只需要保证数据在合理范围内。

def truncate_by_quantile(data, col, lower_percent=0.01, upper_percent=0.99):
    lower = data[col].quantile(lower_percent)
    upper = data[col].quantile(upper_percent)
    return data[(data[col] >= lower) & (data[col] <= upper)]

# 去掉两端1%的极端值
df_clean = truncate_by_quantile(df, 'return_rate', 0.01, 0.99)

避坑指南:我曾经在构建因子时,直接用分位数截断处理了所有特征。结果发现某个因子的区分度大幅下降。后来才意识到,那个因子本身就有"长尾"特征,截断后信息丢失严重。所以,分位数截断适合做"粗筛",不适合精细处理。

五、Winsorize处理:保留样本,修正极端值

前面三种方法都是"删除"异常值。但有时候,删除样本太奢侈了——尤其是样本量本来就不大的时候。

Winsorize处理的做法是:把异常值替换成边界值。比如把小于1%分位数的值,全部替换成1%分位数本身。

from scipy.stats.mstats import winsorize

def winsorize_data(data, col, limits=[0.01, 0.01]):
    # limits参数:左尾和右尾分别截断的比例
    data[col] = winsorize(data[col], limits=limits)
    return data

# 使用示例
df_clean = winsorize_data(df, 'pe_ratio', limits=[0.05, 0.05])

嗯,这里要注意:Winsorize处理后的数据分布会变得"平坦",方差会减小。如果你做的是波动率相关的策略,这个方法要慎用。

六、四种方法对比:什么时候用哪个?

方法 适用场景 优点 缺点
3σ原则 近似正态分布的数据 计算简单,理论成熟 对肥尾数据不友好
IQR方法 非对称分布、金融数据 稳健,不受极端值影响 系数需要调整
分位数截断 数据质量检查、粗筛 简单直接,可控性强 可能丢失有用信息
Winsorize处理 样本量小、不想删除数据 保留样本量,修正极端值 改变数据分布特征

七、实战中的选择策略

我个人在实际项目中,通常这样组合使用:

  1. 第一步:用分位数截断做快速清洗,去掉明显错误的数据(比如价格为负值)
  2. 第二步:用IQR方法识别潜在异常值,但系数调大到2.5或3
  3. 第三步:对于识别出的异常值,用Winsorize处理,而不是直接删除

这样做的好处是:既清理了数据,又最大程度保留了样本量。尤其是在做高频数据时,每一笔数据都很珍贵。

核心原则:不要为了处理异常值而处理。先理解你的数据,再选择合适的方法。金融数据里,有些"异常"其实是信号。

八、本章知识体系

下面这张图总结了四种异常值处理方法的核心逻辑和适用场景:

异常值处理方法体系 异常值处理 3σ原则 IQR方法 分位数截断 Winsorize处理 正态分布假设 删除异常值 基于四分位数 删除异常值 固定比例截断 删除异常值 替换为边界值 保留样本量 适用场景:数据质量检查 → 初步清洗 → 精细处理 → 模型输入 推荐流程:分位数截断(粗筛) → IQR(识别) → Winsorize(修正) 注意:金融数据具有肥尾特征,建议调整IQR系数至2.0-2.5

好了,这四种方法够你应对大部分场景了。记住,没有万能的方法,只有最适合你数据的方案。多试、多对比,慢慢你就会有感觉。


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