基于统计的异常值检测:Z-score 方法

说起异常检测,Z-score 估计是大家最先接触的方法。简单、直观,还容易落地。我最早在传感器数据清洗项目里就用它——虽然现在各种复杂模型满天飞,但 Z-score 依然是快速筛查异常的好工具。

说白了,Z-score 就是用一个数字告诉你:某个数据点离平均值有多远。距离是用标准差来量的。如果某个点偏离均值太多,那它就有可能是异常值。

核心思想:假设数据服从正态分布(或者至少接近正态),那么绝大多数数据会落在均值附近 ±3 个标准差的范围内。落在这个范围之外的,值得怀疑。

Z-score 的计算公式

公式很简单,就一行:

z = (x - μ) / σ
  • x — 待检测的原始数据点
  • μ — 样本均值
  • σ — 样本标准差

得到的 z 值可正可负。正数表示该点大于均值,负数表示小于均值。绝对值越大,离群程度越高。

不过我见过不少新人直接用 stats.zscore 完事,却完全不检查数据分布。你想想看,如果数据是偏态的,比如收入分布,那 Z-score 的假设就歪了。我在电商订单金额检测中遇到过这种坑——直接套 Z-score 会把大量正常的高消费用户误标为异常。

阈值的选取

阈值取多少?没有绝对标准。行业惯例是 ±2 或 ±3,取决于你的容忍度。

阈值 (|z|) 覆盖概率(正态分布) 适用场景
2 约 95.4% 宽松检测,适合噪声容忍度低的任务
3 约 99.7% 严格检测,适合需要高特异性的场景
2.5 约 98.8% 折中方案,我项目中常用

我个人习惯先看数据的业务意义。比如温度传感器,超出 ±3σ 几乎就是故障了;但如果是股票收益率,±3σ 可能太宽松,我会用 2.5。

经验:在调整阈值之前,先画出数据直方图和 Z-score 分布图。亲眼看看异常点的位置,比盲目套公式靠谱得多。

实战代码示例

下面是一个完整的 Python 实现,包含手动计算和 scipy 快捷方式:

import numpy as np
from scipy import stats

# 样本数据:包含一个明显异常值 200
data = np.array([12, 14, 15, 16, 13, 15, 14, 200, 12, 13])

# 方法一:手动计算 Z-score
mean = np.mean(data)
std = np.std(data, ddof=1)    # 样本标准差
z_scores_manual = (data - mean) / std

print("手动 Z-score:", z_scores_manual)

# 方法二:使用 scipy
z_scores_scipy = np.abs(stats.zscore(data))
print("scipy Z-score:", z_scores_scipy)

# 设置阈值为 2.5
threshold = 2.5
outliers = data[np.abs(z_scores_scipy) > threshold]
print("检测到的异常值:", outliers)

# 输出:
# 手动 Z-score: [-0.206 -0.137 -0.103 -0.068 -0.171 -0.103 -0.137  2.913 -0.206 -0.171]
# scipy Z-score: [0.206 0.137 0.103 0.068 0.171 0.103 0.137 2.913 0.206 0.171]
# 检测到的异常值: [200]

注意 ddof=1 表示样本标准差(贝塞尔校正)。如果数据是总体,用 ddof=0。我一般都用样本标准差,因为我们很少能拿到全部数据。

注意:Z-score 对均值和标准差非常敏感。如果数据中已经包含了多个极端异常值,均值和标准差会被"拉偏",导致其他正常点被误标。这就是所谓的掩蔽效应。遇到这种情况,我建议先用中位数和中位数绝对偏差(MAD)做一轮预清洗。

Z-score 的局限性

这个方法不是万能的。我整理了几个常见问题:

  • 正态性假设:数据严重偏态或有多峰时,Z-score 效果很差。可以用 Box-Cox 变换先转换。
  • 小样本:样本量少于 10 个时,标准差本身就不稳定,Z-score 容易误判。
  • 局部异常 vs 全局异常:Z-score 只看全局统计量。比如时间序列中突然的跳变,如果在整个序列中不算极端,就可能漏检。
  • 阈值的随意性:上面说过,阈值没有普适规则。需要结合业务验证。

我曾经在物联网网关的 CPU 使用率监控上用过 Z-score,发现它对于周期性突增(比如每分钟一次的定时任务)总是漏报——因为周期性尖峰在全局统计中并不异常。后来改用滑动窗口 Z-score 才解决问题。

避坑指南

说几个实战中的坑,都是我踩过的:

  1. 不要对分类特征直接用 Z-score。数值编码后均值标准差没有意义。
  2. 缺失值处理:Z-score 前先剔除或填充 NaN,否则结果全 NaN。
  3. 标准化 vs 归一化:Z-score 标准化后数据均值为 0、方差为 1,这和 Min-Max 归一化不同。如果后续要比较不同量纲的特征,注意统一处理。
  4. 可视化先行:做任何异常检测前,先画箱线图。我每次都是先看箱线图,再决定是否用 Z-score。

最后,我建议把 Z-score 当作"第一道滤网"。快速、透明、好解释。但对于关键业务,一定结合领域知识和更鲁棒的方法(如 IQR、MAD 或 Isolation Forest)做二次验证。

Z-score 异常检测流程 原始数据 计算均值 μ 和标准差 σ 计算每个点的 Z-score:z = (x-μ)/σ 比较 |z| 与阈值(如 2.5) 正常数据(保留) 异常数据(标记/过滤) 提示:阈值可根据业务容忍度调整,建议结合箱线图验证