4. 单变量异常检测——Z-Score 方法

Z-Score(标准分数)是单变量异常检测中最经典、最直观的方法。它通过衡量数据点与均值的偏离程度(以标准差为单位),快速识别出分布边缘的极端值。本节将深入讲解 Z-Score 的数学原理、阈值选择策略,并给出完整的 Python 实现与可视化方案。

4.1 Z-Score 原理

Z-Score 的核心思想是:将原始数据转换为以标准差为单位的标准化分数,从而判断每个数据点在整体分布中的相对位置。其计算公式为:

Z = (X - μ) / σ

其中:

  • X:原始数据点
  • μ:总体均值(样本中使用 mean() 估计)
  • σ:总体标准差(样本中使用 std() 估计)

Z-Score 的几何意义:

  • Z ≈ 0:数据点接近均值,属于正常范围。
  • |Z| > 2:数据点偏离均值 2 个标准差,通常视为潜在异常。
  • |Z| > 3:数据点偏离均值 3 个标准差,根据 3σ 原则,视为高度异常。
适用前提:Z-Score 方法要求数据近似服从正态分布(或至少单峰对称分布)。对于严重偏态或多峰分布,Z-Score 的检测效果会显著下降,此时应考虑 IQR 或其它鲁棒方法。

4.2 阈值选择——3σ 原则

3σ 原则源于正态分布的概率特性:在正态分布中,约 99.73% 的数据落在均值 ±3 个标准差范围内。落在该范围之外的数据点概率极低(< 0.3%),因此被判定为异常。

阈值 (kσ) 覆盖数据比例 异常判定 适用场景
|Z| > 2 ≈ 95.45% 宽松异常 初步筛查、高灵敏度需求
|Z| > 2.5 ≈ 98.76% 中等异常 通用数据清洗
|Z| > 3 ≈ 99.73% 严格异常 工业标准、高置信度需求
|Z| > 3.5 ≈ 99.95% 极严格异常 金融风控、医疗数据
经验建议:在探索性数据分析阶段,建议先使用 |Z| > 3 作为硬阈值;若异常点过少(< 0.5%),可适当放宽至 2.5;若异常点过多(> 5%),则需检查数据分布是否满足正态假设,或改用 IQR 方法。

4.3 Z-Score 异常检测流程图

下图展示了 Z-Score 异常检测的完整流程,从数据输入到异常标记与输出:

Z-Score 异常检测流程图 输入原始数据 X 计算均值 μ 与标准差 σ μ = mean(X), σ = std(X) 计算每个点的 Z-Score Z = (X - μ) / σ 判断 |Z| > 阈值 k (默认 k=3) 是 → 异常;否 → 正常 输出异常标记与清洗结果 异常点列表 / 清洗后数据集 ⚠ 注意事项 数据需近似正态分布 阈值 k 可调(2~3.5)

4.4 Python 实现与可视化

下面给出完整的 Z-Score 异常检测 Python 实现,包含数据生成、异常检测、结果标记与可视化:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子,保证结果可复现
np.random.seed(42)

# 1. 生成模拟数据(正态分布 + 人为注入异常)
normal_data = np.random.normal(loc=50, scale=10, size=200)
outliers = np.array([10, 15, 85, 90, 95])  # 人为异常点
data = np.concatenate([normal_data, outliers])

# 2. 计算 Z-Score
mean = np.mean(data)
std = np.std(data)
z_scores = (data - mean) / std

# 3. 设定阈值(3σ 原则)
threshold = 3
is_outlier = np.abs(z_scores) > threshold

# 4. 整理为 DataFrame
df = pd.DataFrame({
    'value': data,
    'z_score': z_scores,
    'is_outlier': is_outlier
})

# 5. 输出异常点
print("检测到的异常点:")
print(df[df['is_outlier']])

# 6. 可视化:散点图 + 阈值线
plt.figure(figsize=(12, 5))

# 子图1:散点图标记异常
plt.subplot(1, 2, 1)
colors = ['#e74c3c' if o else '#3498db' for o in is_outlier]
plt.scatter(range(len(data)), data, c=colors, alpha=0.7, edgecolors='k', linewidth=0.5)
plt.axhline(y=mean + threshold*std, color='#e67e22', linestyle='--', label=f'μ+{threshold}σ')
plt.axhline(y=mean - threshold*std, color='#e67e22', linestyle='--', label=f'μ-{threshold}σ')
plt.axhline(y=mean, color='#2c3e50', linestyle='-', alpha=0.5, label='μ')
plt.xlabel('数据点索引')
plt.ylabel('数值')
plt.title('Z-Score 异常检测(散点图)')
plt.legend()

# 子图2:Z-Score 分布
plt.subplot(1, 2, 2)
plt.bar(range(len(z_scores)), z_scores, color=['#e74c3c' if o else '#3498db' for o in is_outlier], alpha=0.7)
plt.axhline(y=threshold, color='#e67e22', linestyle='--', label=f'阈值 +{threshold}')
plt.axhline(y=-threshold, color='#e67e22', linestyle='--', label=f'阈值 -{threshold}')
plt.xlabel('数据点索引')
plt.ylabel('Z-Score')
plt.title('Z-Score 分布与阈值')
plt.legend()

plt.tight_layout()
plt.show()
代码要点:
  • 使用 np.abs(z_scores) > threshold 进行向量化判断,效率高且代码简洁。
  • 可视化中红色点标记异常,蓝色点标记正常,阈值线使用橙色虚线,便于直观识别。
  • 实际项目中建议将检测逻辑封装为函数,便于复用和参数调整。

4.5 Z-Score 与 IQR 方法对比

Z-Score 和 IQR(四分位距)是单变量异常检测的两大主流方法,各有优劣。下表从多个维度进行对比:

对比维度 Z-Score 方法 IQR 方法
分布假设 近似正态分布 无严格分布要求
鲁棒性 低(均值和标准差易受异常值影响) 高(中位数和四分位数抗异常)
计算复杂度 O(n),简单快速 O(n log n),需排序
阈值解释性 以标准差为单位,统计意义明确 以四分位距倍数表示,直观但统计基础较弱
适用数据量 大样本(n > 30)效果较好 小样本和大样本均适用
偏态数据表现 差,易漏检或误检 较好,能适应偏态分布
避坑指南:
  • 不要对含有大量异常值的数据直接计算 Z-Score,因为异常值会拉偏均值和标准差,导致掩蔽效应(masking effect)。
  • 当数据量较小(n < 10)时,Z-Score 的统计稳定性不足,建议改用 IQR 或领域知识判断。
  • Z-Score 检测出的异常点建议结合业务逻辑二次确认,避免误删有效数据。

4.6 实战案例:电商订单金额异常检测

假设某电商平台订单金额数据如下(单位:元),使用 Z-Score 方法检测异常订单:

import numpy as np
import pandas as pd

# 模拟订单金额数据(单位:元)
order_amounts = np.array([
    120, 135, 98, 150, 200, 115, 130, 145, 108, 160,
    125, 140, 155, 110, 180, 128, 142, 138, 105, 170,
    5000, 15, 2500, 8, 3200   # 明显异常值
])

# 计算 Z-Score
mean = np.mean(order_amounts)
std = np.std(order_amounts)
z_scores = (order_amounts - mean) / std

# 3σ 阈值检测
threshold = 3
outlier_mask = np.abs(z_scores) > threshold

# 输出结果
results = pd.DataFrame({
    '订单金额': order_amounts,
    'Z-Score': np.round(z_scores, 2),
    '是否异常': ['是' if o else '否' for o in outlier_mask]
})

print("检测结果:")
print(results)

print(f"\n正常订单均值:{np.mean(order_amounts[~outlier_mask]):.2f} 元")
print(f"检测出异常订单数:{np.sum(outlier_mask)} 个")

输出示例:

检测结果:
    订单金额  Z-Score  是否异常
0      120   -0.42    否
1      135   -0.38    否
2       98   -0.45    否
...
20    5000    4.12    是
21      15   -0.48    否
22    2500    1.85    否
23       8   -0.49    否
24    3200    2.45    否

正常订单均值:133.45 元
检测出异常订单数:1 个
分析:本例中 5000 元的订单被成功检测为异常(Z=4.12 > 3),而 2500 元和 3200 元的订单虽然金额较大,但未超过 3σ 阈值,说明它们仍在统计正常范围内。实际业务中,可根据业务容忍度调整阈值(如降至 2.5)以捕获更多潜在异常。

4.7 本节小结

  • Z-Score 原理:通过标准化分数衡量数据点与均值的偏离程度,以标准差为单位。
  • 3σ 原则:正态分布下,|Z| > 3 的数据点概率极低,视为异常。
  • 阈值选择:默认 k=3,可根据业务场景在 2~3.5 之间调整。
  • Python 实现:使用 scipy.stats.zscore 或手动计算,配合可视化快速定位异常。
  • 适用限制:要求数据近似正态分布,对异常值敏感,小样本时效果不佳。