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就够用了。
实战总结:我在做市系统里,每天处理上亿条数据。我的流程是:业务规则过滤 → IQR检测(因为做市数据偏态严重)→ 孤立森林复核(针对多维特征)。这套组合拳下来,异常值基本无处遁形。
好了,这四种方法你都掌握了。记住,没有银弹,只有最适合你场景的方法。动手试试看,你的数据里藏着多少「异常」?