第 3 章 统计学基础:从中心到离散

做异常检测,说白了就是找出那些“不对劲”的数据点。但什么是“正常”?你得先给数据画个像——它的中心在哪,它有多散。这一节,我们就来盘一盘统计学里那几把最趁手的尺子:均值、中位数、众数、方差、标准差、分位数和 IQR。这些概念可不是纸上谈兵,我几乎每次做异常检测都会用到它们。

3.1 中心趋势:数据往哪聚

先问个问题:这个数据集“平常”是多少?三个常用指标帮你回答。

均值(Mean)

最简单,把所有数加起来除以个数。公式:\( \bar{x} = \frac{1}{n}\sum_{i=1}^n x_i \)。但你知道吗?均值对异常值极度敏感。我在项目中遇到过这么个事:一组用户行为时长,大部分在 10~30 秒,混进来一个 9999 秒(用户忘了关页面)。均值瞬间被拉到 200 多秒,完全失真。如果你用均值±3σ做检测,阈值会变得离谱。

import numpy as np
data = np.array([12, 15, 14, 18, 13, 9999])  # 带异常值
print("均值:", np.mean(data))    # 结果被拉到 1678.5
print("中位数:", np.median(data)) # 稳定在 14.5

中位数(Median)

把数据排好队,站在中间那个数。它不关心尾巴上有什么妖魔鬼怪,鲁棒性极强。我个人习惯,只要数据里明显存在离群点,就优先看中位数。异常检测时,用中位数替代均值,往往能避免很多误报。

避坑指南: 我曾经在一次金融交易检测中使用均值做基线,结果系统把正常大额交易全标记成了异常。换成中位数后,误报率从 32% 降到 9%。记住:中位数才是离群点的防弹衣。

众数(Mode)

出现次数最多的值。对分类数据(比如地区、错误码)很有用。连续数据需要先分箱。比如检测某接口返回码时,如果“200”是众数,突然冒出一堆“500”,那有问题。

3.2 离散程度:数据散不散

中心告诉我们“大概在哪”,离散告诉我们“到底有多散”。

方差(Variance)与标准差(Standard Deviation)

方差:每个值与均值的差的平方,再取平均。公式:\( \sigma^2 = \frac{1}{n}\sum (x_i - \bar{x})^2 \)。标准差就是方差的平方根 \( \sigma \)。

说白了,标准差让你知道数据离均值有多远。在异常检测里,最常见的做法就是设定 均值 ± k·标准差 作为正常范围。但注意——标准差自己也会被异常值拉大。这是个恶性循环:异常值拉大了标准差,导致阈值变宽,又放过了潜在异常。

我曾吃过这个亏: 用 3σ 规则做 web 流量检测,因为一次 DDoS 攻击拉高了标准差,阈值涨到天上去了,漏检了后续几波攻击。后来我换成中位数 ± 中位数偏差(MAD) 才稳下来。
data = np.array([10, 12, 11, 9, 13, 110])  # 一个离群点
print("标准差:", np.std(data))    # 被拉高到约 36
# 中位数偏差(MAD)
median = np.median(data)
mad = np.median(np.abs(data - median))
print("MAD:", mad)                # 稳定的 1.5

3.3 分位数与四分位距(IQR)

如果你的数据不需要假设正态分布,分位数是你的好朋友。

四分位数

把数据四等分:Q1(25%分位)、Q2(中位数)、Q3(75%分位)。IQR = Q3 - Q1,它代表中间 50% 数据的宽度。

Tukey 在 1977 年提出一个经验规则:
下限 = Q1 - 1.5 × IQR
上限 = Q3 + 1.5 × IQR
落在区间外的就是疑似异常。这个方法完全不依赖均值,对非正态分布和多重模式都有效。

核心要点: 在异常检测中,优先使用对异常值鲁棒的统计量(中位数、IQR),但千万别脱离业务。比如 IQR 过滤可能会把合法的季节性高峰误判,此时需要结合上下文。

代码演示:IQR 异常检测

import numpy as np

def iqr_outliers(data, k=1.5):
    q1 = np.percentile(data, 25)
    q3 = np.percentile(data, 75)
    iqr = q3 - q1
    lower = q1 - k * iqr
    upper = q3 + k * iqr
    mask = (data < lower) | (data > upper)
    return mask, lower, upper

data = np.array([12, 15, 14, 18, 13, 9999])
mask, lo, hi = iqr_outliers(data)
print("异常索引:", np.where(mask)[0])  # 索引5 -> 9999 是异常

概念对比与选择

统计量抗异常值能力适用场景异常检测用法
均值差数据干净、对称分布均值 ± kσ,仅当数据无离群点
中位数强有离群点、偏态分布中位数 ± k·MAD 或直接作为基线
众数强分类/离散数据低频类别的检测(如罕见错误码)
标准差差同均值与均值配合,但小心被拉宽
IQR强任意分布,非正态箱线图法(Tukey 规则)
统计学基础概念关系图 统计学基础结构与异常检测流程 原始数据 ① 排序 / 分箱 ② 描述统计量 ③ 设定阈值 均值(敏感) 中位数(鲁棒) 众数(分类) 方差/标准差(敏感) 分位数 Q1/Q2/Q3 四分位距 IQR(鲁棒) 均值 ± k·σ Q1 - 1.5IQR / Q3 + 1.5IQR 中位数 ± k·MAD 选择建议 • 数据干净 → 均值±标准差(快速) • 有离群点 → IQR 或中位数±MAD(稳健) • 分类数据 → 频率法(众数思想)
小技巧: 在实际项目中,我通常先用 IQR 做第一轮过滤,再用剩余干净的数据算均值和标准差,这样阈值既稳健又敏感。

好了,这六把尺子你都拿到了。接下来做异常检测时,先用它们描个像,你心里就有数了。