4、异常值检测与处理:四种主流方法实战

做市数据里最让人头疼的是什么?我个人觉得,不是数据缺失,而是那些「看起来正常、实际上有毒」的异常值。它们混在正常数据里,像一颗老鼠屎坏了一锅粥。今天我就把这四种方法掰开揉碎了讲,都是我在实盘环境里反复验证过的。

核心观点:没有万能的方法,只有合适的场景。Z-score适合正态分布数据,IQR适合偏态分布,孤立森林适合高维数据,业务规则是最后一道防线。

4.1 基于统计学的Z-score方法

Z-score的原理其实很简单——它衡量一个数据点偏离均值多少个标准差。我习惯用3倍标准差作为阈值,超过这个范围的,基本可以认定为异常。

为什么会是3倍?因为正态分布下,99.7%的数据都落在±3σ范围内。换句话说,超出这个范围的数据,出现的概率不到0.3%。

import numpy as np
import pandas as pd

def detect_outliers_zscore(df, column, threshold=3):
    """
    基于Z-score的异常值检测
    :param df: DataFrame
    :param column: 要检测的列名
    :param threshold: Z-score阈值,默认3
    :return: 异常值布尔索引
    """
    z_scores = np.abs((df[column] - df[column].mean()) / df[column].std())
    return z_scores > threshold

# 示例:检测价格异常
df['price_zscore_outlier'] = detect_outliers_zscore(df, 'price')
print(f"检测到 {df['price_zscore_outlier'].sum()} 个价格异常点")

我的经验:Z-score对极端值非常敏感。我在处理某交易所的Level2数据时,一次闪电暴跌导致价格瞬间跌了80%,Z-score直接飙到15。这时候阈值设3就太宽松了,我一般会结合业务场景动态调整。

适用场景:

  • 数据近似正态分布(如收益率、买卖价差)
  • 样本量较大(>1000条)
  • 异常值比例较低(<5%)

局限性:

  • 对均值、标准差敏感,少量极端值会拉偏统计量
  • 不适用于多模态分布

4.2 基于IQR的箱线图法

箱线图法是我个人最常用的方法,因为它不假设数据分布。它的核心是四分位距(IQR),也就是Q3 - Q1。通常把小于Q1 - 1.5×IQR或大于Q3 + 1.5×IQR的点视为异常。

你想想看,为什么是1.5倍?这个系数其实是个经验值。我在做国债期货做市数据时,发现1.5倍太严格了,把很多正常的波动都标记成了异常。后来我改成了2倍,效果好了很多。

def detect_outliers_iqr(df, column, multiplier=1.5):
    """
    基于IQR的异常值检测
    :param df: DataFrame
    :param column: 要检测的列名
    :param multiplier: IQR倍数,默认1.5
    :return: 异常值布尔索引
    """
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    
    lower_bound = Q1 - multiplier * IQR
    upper_bound = Q3 + multiplier * IQR
    
    return (df[column] < lower_bound) | (df[column] > upper_bound)

# 示例:检测成交量异常
df['volume_iqr_outlier'] = detect_outliers_iqr(df, 'volume', multiplier=2)
print(f"IQR方法检测到 {df['volume_iqr_outlier'].sum()} 个成交量异常点")

避坑指南:我曾经在清洗某股票的高频数据时,直接用默认的1.5倍IQR,结果把开盘集合竞价阶段的巨量成交全部标记为异常。后来才发现,那个阶段的成交量本身就比正常时段大一个数量级。所以,一定要按时间段分组计算IQR。

适用场景:

  • 数据分布偏态严重(如成交量、持仓量)
  • 样本量中等(100-10000条)
  • 需要快速、直观的异常检测

4.3 基于机器学习的孤立森林法

孤立森林(Isolation Forest)的思路很巧妙——它不试图描述正常数据,而是直接孤立异常点。异常点因为数量少、特征明显,更容易被「孤立」出来。

我记得第一次用这个方法时,心里还有点打鼓。但跑完结果后,发现它把那些「看起来正常但组合起来异常」的点都揪出来了,比如价格正常但成交量异常小的挂单。

from sklearn.ensemble import IsolationForest

def detect_outliers_isolation_forest(df, columns, contamination=0.05):
    """
    基于孤立森林的异常值检测
    :param df: DataFrame
    :param columns: 特征列列表
    :param contamination: 预期异常比例,默认5%
    :return: 异常值布尔索引(True为异常)
    """
    model = IsolationForest(
        contamination=contamination,
        random_state=42,
        n_estimators=100
    )
    
    # 训练并预测
    df['iforest_outlier'] = model.fit_predict(df[columns])
    # 转换:-1为异常,1为正常
    return df['iforest_outlier'] == -1

# 示例:使用价格和成交量两个特征
features = ['price', 'volume', 'bid_ask_spread']
df['iforest_outlier'] = detect_outliers_isolation_forest(df, features)
print(f"孤立森林检测到 {df['iforest_outlier'].sum()} 个多维异常点")

参数调优建议:contamination参数很关键。我一般先用业务规则估算一个大概的异常比例,比如0.01(1%),然后根据结果微调。n_estimators设100就够用了,再大收益不大。

适用场景:

  • 高维数据(多个特征联合异常)
  • 数据分布复杂,无法用简单统计量描述
  • 样本量大(>10000条)

4.4 基于业务规则的过滤

前面三种方法都是数据驱动的,但最后这道防线必须靠业务规则。说白了,就是「常识」。价格不能为负、成交量不能为0、买卖价差不能为负——这些规则看似简单,但能拦住大部分低级错误。

我在做期权做市数据时,遇到过一笔数据:行权价是负数。你想想看,行权价怎么可能为负?后来查出来是数据源的一个bug。这种异常,任何统计方法都检测不出来,只有业务规则能拦住。

def apply_business_rules(df):
    """
    应用业务规则过滤异常数据
    :param df: DataFrame
    :return: 清洗后的DataFrame
    """
    rules = []
    
    # 规则1:价格必须为正
    rules.append(df['price'] > 0)
    
    # 规则2:成交量不能为0或负
    rules.append(df['volume'] > 0)
    
    # 规则3:买卖价差不能为负
    if 'ask_price' in df.columns and 'bid_price' in df.columns:
        rules.append(df['ask_price'] >= df['bid_price'])
    
    # 规则4:涨跌幅不能超过阈值(比如±20%)
    if 'prev_close' in df.columns:
        max_change = 0.20
        rules.append(
            (df['price'] / df['prev_close'] - 1).abs() <= max_change
        )
    
    # 合并所有规则
    valid_mask = pd.concat(rules, axis=1).all(axis=1)
    invalid_count = (~valid_mask).sum()
    
    print(f"业务规则过滤掉 {invalid_count} 条数据")
    return df[valid_mask].copy()

# 示例
df_clean = apply_business_rules(df)

重要提醒:业务规则要写在数据清洗的最前面。先过滤掉明显错误的数据,再用统计方法检测「看起来正常但实际异常」的数据。顺序搞反了,统计结果会被脏数据带偏。

常见业务规则清单:

字段 规则 说明
价格 price > 0 价格不能为负或零
成交量 volume > 0 成交量必须为正整数
买卖价差 ask >= bid 卖价不能低于买价
涨跌幅 |change| <= 20% 根据品种设定合理阈值
时间戳 timestamp 在交易时段内 过滤非交易时间数据

4.5 四种方法对比与选择策略

说了这么多,到底该用哪种?我画了张图,帮你理清思路。

异常值检测方法选择流程图 原始数据 先应用业务规则过滤 数据是否近似正态分布? Z-score方法 适合正态分布数据 IQR箱线图法 适合偏态分布数据 孤立森林 适合高维复杂数据

我的建议是:先用业务规则做粗过滤,再用统计方法做细检测。如果数据维度高、关系复杂,直接上孤立森林。如果只是简单的价格、成交量,Z-score或IQR就够用了。

实战总结:我在做市系统里,每天处理上亿条数据。我的流程是:业务规则过滤 → IQR检测(因为做市数据偏态严重)→ 孤立森林复核(针对多维特征)。这套组合拳下来,异常值基本无处遁形。

好了,这四种方法你都掌握了。记住,没有银弹,只有最适合你场景的方法。动手试试看,你的数据里藏着多少「异常」?

专注资料整理