4. 统计学方法:Z-score、IQR规则、Grubbs检验
做异常检测,统计方法是入门首选。你不用上来就上深度学习,很多时候一个 Z-score 就能把脏数据揪出来。我个人习惯是先做一轮快速统计筛查,再看要不要上复杂模型。这节我们就聊三个最常用的统计工具:Z-score、IQR 规则和 Grubbs 检验。
4.1 Z-score:标准化距离法
Z-score 说白了就是告诉你:「这个点离均值有多远?」公式很简单:
Z = (x - μ) / σ
其中 μ 是均值,σ 是标准差。如果一个点的 Z-score 值超过某个阈值,就标记为异常。常用阈值是 3。为什么呢?因为正态分布下,超过均值 ±3σ 的概率只有 0.27% 左右。
我在项目中遇到过一个问题:某电商的支付金额数据,明明呈长尾分布,硬套 Z-score 阈值 3,结果把正常的大额交易全标成了异常。后来我改用修正的 Z-score(基于中位数和 MAD),效果才好转。
median + IQR 或 MAD 版 Z-score。
Python 实现也很直接:
import numpy as np
from scipy import stats
def detect_zscore(data, threshold=3):
z = np.abs(stats.zscore(data))
return np.where(z > threshold)[0]
# 示例
data = [10, 12, 12, 13, 12, 11, 14, 100, 13, 12]
outliers = detect_zscore(data)
print(outliers) # 索引 7
4.2 IQR规则:不用假设分布
Z-score 对分布有要求,IQR 规则就不一样了。它只看数据的分位数,完全不关心数据是不是正态的。公式:
IQR = Q3 - Q1
上界 = Q3 + 1.5 * IQR
下界 = Q1 - 1.5 * IQR
超出上下界的就认为是异常。这个 1.5 倍是 Tukey 老爷子提出来的经验值,用在箱线图上特别经典。
我记得有一次做传感器故障诊断,数据是明显的偏态分布,Z-score 完全失灵。换成 IQR 规则后,一下就找出了十几个传感器漂移点。嗯,这里要注意:如果数据本身就有很多异常,Q1/Q3 的估计会被污染。我建议先用少量干净数据算 IQR 边界,再用这个边界去筛查全量数据。
代码实现:
def detect_iqr(data, multiplier=1.5):
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
lower = q1 - multiplier * iqr
upper = q3 + multiplier * iqr
return np.where((data < lower) | (data > upper))[0]
data = [10, 12, 13, 12, 11, 14, 100, 13, 12]
print(detect_iqr(data)) # [6]
4.3 Grubbs检验:单点离群检测
Grubbs 检验是更「严谨」的方法。它假设数据来自正态分布,然后检验最大值或最小值是不是离群点。检验统计量就是 Z-score 的变体:
G = max(|xi - μ|) / σ
然后查表(或者用 p 值判断)看是否拒绝「无离群点」的原假设。Grubbs 检验一次只能检测一个极端点,检测完如果发现异常,剔除它,再对剩下的数据重复检验。
我在一个金融风控项目里用过它。客户要求每个异常点都要有统计显著性说明,Z-score 和 IQR 都拿不出 p 值,Grubbs 检验正好满足合规要求。当然,它也有个坑:数据量太小的时候检验力不足,少于 6 个样本就别用了。
- Z-score:数据近似正态,样本量大,快速筛查。
- IQR:分布未知或偏态,箱线图场景,无参数假设。
- Grubbs:需要统计显著性证明,单点逐一检验。
Python 里用 outliers 库或自己实现:
from scipy import stats as sp_stats
def grubbs_test(data, alpha=0.05):
mean = np.mean(data)
std = np.std(data, ddof=1)
z = np.abs(data - mean) / std
max_idx = np.argmax(z)
g = z[max_idx]
n = len(data)
# 计算临界值
t_crit = sp_stats.t.ppf(1 - alpha / (2 * n), n - 2)
g_crit = (n - 1) / np.sqrt(n) * np.sqrt(t_crit**2 / (n - 2 + t_crit**2))
if g > g_crit:
return max_idx # 异常点索引
return None
data = [10, 12, 13, 12, 11, 14, 100, 13, 12]
print(grubbs_test(data)) # 6
4.4 三种方法的选择策略
你想想看,什么时候用什么?我整理了一个表格,方便你快速对号入座:
| 方法 | 分布假设 | 检出能力 | 适用场景 | 主要限制 |
|---|---|---|---|---|
| Z-score | 正态 | 多异常同时 | 初步快速筛查 | 对偏态、多模态失效 |
| IQR规则 | 无 | 多异常同时 | 箱线图、探索性分析 | 尾部异常灵敏度低 |
| Grubbs检验 | 正态 | 单点逐一 | 需要 p 值的正式报告 | 一次只能检一个,多次检验膨胀 I 类错误 |
我曾经接过一个需求:传感器数据流每小时跑一次异常检测,样本量 100 左右。我第一版用 IQR 加滑动窗口,结果早晚高峰的正常波动也被标记了。后来改成 Z-score 用滚动均值和滚动标准差,阈值设成 3.5,才算稳定。所以啊,没有万能方法,得根据数据特点调。
4.5 流程总览
下面这张图概括了三个方法的使用路径。你可以把它贴在工位上,下次选方法时扫一眼。
4.6 避坑指南
我来给你盘点几个我踩过的坑:
- 多重比较问题:Grubbs 多次应用后,I 类错误率会膨胀。我用过 Bonferroni 校正来修正显著性水平。
- 样本量太小:少于 30 个样本时,Z-score 和 Grubbs 的统计效力都不高。这时候我倾向用 IQR 加上领域知识。
- 异常比例过高:如果数据里异常占了 10% 以上,Z-score 的均值和标准差会被拉偏。我曾经用 Winsorize(截尾)预处理后再算统计量。
- 业务规则优先级:统计方法只告诉你「统计上的异常」,不一定代表业务上有用。记得加上业务判断。
4.7 小结
三种方法各有脾气。Z-score 快但挑分布,IQR 皮实但尾部钝感,Grubbs 严谨但只能单挑。实践中我通常这样搭配:先用 IQR 做个箱线图快速定位,再用 Grubbs 给关键异常点算个 p 值写进报告。如果数据量大且近似正态,直接上 Z-score 配合滑动窗口。你根据手里数据的性格来选,肯定不翻车。