3、基于统计的异常检测:Z-Score方法、IQR(四分位距)方法、假设检验与p值

大家好,我是老张。今天我们来聊聊量化风控里最基础、也最实用的异常检测方法——基于统计的那一套。

说实话,我刚入行那会儿,觉得异常检测就得用深度学习、神经网络才够酷。后来在真实项目里碰了一鼻子灰,才发现统计方法才是真正的"扫地僧"。简单、可解释、还特别稳。

这一章,我带你过三个经典方法:Z-Score、IQR、还有假设检验与p值。每个我都会结合项目经验来讲,保证你听完就能用。

基于统计的异常检测方法体系 异常检测统计方法 Z-Score 方法 |Z| > 3 视为异常 IQR 四分位距法 Q1 - 1.5*IQR / Q3 + 1.5*IQR 假设检验与p值 p < 0.05 拒绝原假设 需正态分布假设 对极端值敏感 稳健、不受极端值影响 适用于偏态分布 需设定显著性水平 可量化置信程度

3.1 Z-Score 方法:标准化后的"离群值"

Z-Score 说白了就是看一个数据点离均值有多远,用标准差做尺子量一下。

公式很简单:

Z = (x - μ) / σ

其中 μ 是均值,σ 是标准差。Z 的绝对值越大,说明这个点越异常。

经验阈值:

  • |Z| > 2:值得关注(约 95% 的数据落在这个范围内)
  • |Z| > 3:高度异常(约 99.7% 的数据落在这个范围内)
  • |Z| > 4:基本可以判定为异常

重要提醒:Z-Score 对正态分布假设比较敏感。如果数据本身偏态严重,Z-Score 的效果会打折扣。

我在项目中遇到过一件事。有一次做信用卡交易金额的异常检测,直接用 Z-Score 发现一堆"异常"。后来一查,原来是双十一当天的正常大额消费。为什么?因为交易金额分布是长尾的,不满足正态假设。

代码示例:

import numpy as np
from scipy import stats

def zscore_detection(data, threshold=3):
    z_scores = np.abs(stats.zscore(data))
    return np.where(z_scores > threshold)[0]

# 示例
data = [10, 12, 11, 13, 100, 12, 11, 10, 13, 12]
outliers = zscore_detection(data)
print(f"异常索引: {outliers}")  # 输出: [4]

我的小技巧:如果数据量小(比如少于30个样本),Z-Score 的可靠性会下降。我一般会结合 IQR 一起看,互相验证。

3.2 IQR(四分位距)方法:稳健的"箱线图"法则

IQR 方法是我个人最喜欢的。它不依赖正态分布假设,对极端值也不敏感,特别适合风控场景里的"脏数据"。

计算步骤:

  1. 计算第一四分位数 Q1(25% 分位数)
  2. 计算第三四分位数 Q3(75% 分位数)
  3. 计算 IQR = Q3 - Q1
  4. 定义异常边界:
    • 下界 = Q1 - 1.5 × IQR
    • 上界 = Q3 + 1.5 × IQR
  5. 超出边界的点即为异常

你想想看,为什么是 1.5 倍?这个系数其实来自经验。我在做信贷审批策略时,用 1.5 倍 IQR 能筛掉大约 0.3% 的极端样本,刚好符合业务上"万分之几"的坏账容忍度。

代码示例:

def iqr_detection(data):
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    outliers = np.where((data < lower_bound) | (data > upper_bound))[0]
    return outliers, lower_bound, upper_bound

data = [10, 12, 11, 13, 100, 12, 11, 10, 13, 12]
outliers, lb, ub = iqr_detection(data)
print(f"异常索引: {outliers}, 下界: {lb:.2f}, 上界: {ub:.2f}")

避坑指南:我曾经在某个项目中,直接用 IQR 检测用户收入数据,结果把高收入人群全判成了异常。后来发现,收入数据是右偏的,上界被拉得很高。解决办法:对数据做对数变换后再用 IQR。

3.3 假设检验与p值:用统计学做"判决"

假设检验的思路很直接:先假设数据是正常的(原假设 H₀),然后看当前这个点出现的概率有多大。如果概率太小(p 值小于显著性水平 α),就拒绝原假设,认为它是异常。

常用的方法有:

  • Grubbs 检验:适用于单变量正态分布,一次检测一个异常点
  • Dixon 检验:适用于小样本(n ≤ 30),不要求正态分布
  • 卡方检验:适用于分类变量的异常检测

Grubbs 检验示例:

from scipy import stats

def grubbs_test(data, alpha=0.05):
    n = len(data)
    mean = np.mean(data)
    std = np.std(data, ddof=1)
    z_scores = np.abs(data - mean) / std
    max_z = np.max(z_scores)
    
    # 计算临界值
    t_crit = 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))
    
    return max_z > g_crit, max_z, g_crit

data = [10, 12, 11, 13, 100, 12, 11, 10, 13, 12]
is_outlier, g_stat, g_crit = grubbs_test(data)
print(f"是否异常: {is_outlier}, 统计量: {g_stat:.3f}, 临界值: {g_crit:.3f}")

核心要点:p 值不是"异常的概率",而是"在原假设成立下,观察到当前结果或更极端结果的概率"。p 值越小,越有理由怀疑原假设。

我记得有一次做反欺诈模型,用 Grubbs 检验发现某个用户的交易金额 p 值只有 0.001。按理说应该标记为异常,但业务方说这个用户是 VIP 大客户。后来我们调整了策略:对 VIP 用户单独建模,用不同的阈值。

三种方法对比:

方法 适用场景 优点 缺点
Z-Score 近似正态分布、大样本 计算简单、直观 对偏态分布不鲁棒
IQR 任意分布、含极端值 稳健、无需分布假设 对尾部数据不敏感
假设检验 需要统计显著性判断 可量化置信度、理论严谨 计算复杂、需选择检验方法

我的建议:实际项目中,我通常先用 IQR 做快速筛查,再用 Z-Score 或假设检验做精细化判断。别迷信单一方法,组合使用才是王道。

嗯,这一章的内容就到这儿。统计方法虽然基础,但用好了能解决 80% 的异常检测问题。下一章我们聊聊基于距离的方法,到时候见。


公众号:蓝海资料掘金营,微信deep3321