第2章:统计学基础回顾归纳总结

做异常检测,说白了就是跟数据打交道。而跟数据打交道,离不开统计学这把尺子。我刚开始接触异常检测时,总觉得那些统计概念是理论课上的东西,离工程很远。结果呢?第一个项目就被一堆离群点打得措手不及——后来才明白,没有统计学基础,你连“什么是异常”都定义不清楚。

这一章,我带大家快速过一遍最常用的统计概念。不是教科书式的罗列,而是从异常检测的实际需求出发,挑出你写代码时一定会用到的那部分。

2.1 描述性统计:给你的数据“拍照”

拿到一份数据,第一步该干嘛?我个人习惯先看几个关键数字——均值、中位数、标准差。这三兄弟能告诉你数据的大致长相。

统计量 含义 异常检测中的应用
均值(Mean) 数据的“重心” Z-score 计算基准
中位数(Median) 不怕极端值的“中间值” 稳健估计,适合有真实异常的数据
众数(Mode) 出现最多的值 分类或离散型异常的参考
标准差(Std) 数据的“离散程度” 3σ 法则、Z-score 分母
四分位数(IQR) 数据的“分段边界” 箱线图异常判定:Q1-1.5IQR 或 Q3+1.5IQR
偏度(Skewness) 分布不对称的程度 判断是否可用正态假设
峰度(Kurtosis) 分布“尾巴”的厚薄 识别重尾分布,异常更容易躲

你可能觉得中位数不如均值常用——我在项目里也遇到过这种想法。但有一次处理网络时延数据,500个样本里混了十几个2000ms的异常点,均值直接被拉到400ms,而中位数还是正常的80ms。嗯,从那以后我再也不敢看不起中位数了。

避坑指南:计算均值前,先用箱线图或简单阈值过滤掉明显异常的极值。否则均值会被污染,后续所有基于均值的检测都跟着偏。

2.2 概率分布:异常 vs 正常的“边界”

异常检测的一个核心假设:正常数据服从某种分布,异常数据偏离该分布。

最常见的分布当然是正态分布。你想想看,如果数据符合正态分布,那根据3σ法则,99.7%的数据落在均值±3标准差内。落在外面?那大概率是异常。

# 3σ 法则的简单实现
import numpy as np

def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower = mean - 3 * std
    upper = mean + 3 * std
    outliers = [x for x in data if x < lower or x > upper]
    return outliers

# 示例:生成100个正态分布数据,混入1个异常点
data = np.random.normal(100, 15, 100).tolist()
data.append(200)  # 混入异常
outliers = detect_outliers_3sigma(data)
print(f"检测到 {len(outliers)} 个异常点:{outliers[:5]}")

但是,这个办法有个前提——数据真的服从正态分布。我以前做过一个CPU利用率监控项目,数据有明显的右偏(大部分时间在20%~40%,偶尔飙到90%)。直接用3σ,结果90%的正常峰值全被判成了异常。因为偏态分布下,均值被拉偏,标准差也虚高。

那怎么办?两个思路:一是用中位数和MAD(中位数偏差)代替均值和标准差,它对偏态分布更稳健;二是用IQR方法,它不依赖正态假设。

我的小建议:实际项目中,先用偏度和峰度做一次快速检验。如果 |偏度| > 1 或峰度 > 5,就别硬套正态分布了。改用箱线图IQR法或者基于分位数的方法。

2.3 Z-score:一归一,全局可对比

Z-score 是异常检测的入门算法之一。它把每个数据点转换为偏离均值的标准差倍数:

Z = (x - μ) / σ

Z-score的值越大,越可能是异常。通常阈值取3(对应3σ),也有取2.5或3.5的,取决于你的业务容忍度。

但这里有个坑:一旦数据里混入了真正的异常,均值和标准差都会被带偏,Z-score 就失灵了——这叫“掩膜效应”(masking effect)。

# Z-score 检测,解决掩膜效应的迭代方法
def zscore_iterative(data, threshold=3.0, max_iter=10):
    arr = np.array(data)
    for _ in range(max_iter):
        mean = np.mean(arr)
        std = np.std(arr)
        z = np.abs(arr - mean) / std
        # 保留非异常点继续计算
        arr_new = arr[z < threshold]
        if len(arr_new) == len(arr):
            break
        arr = arr_new
    return arr[z >= threshold]  # 返回异常值

data = np.r_[np.random.normal(0,1,200), [10, 12, 15]]
outliers = zscore_iterative(data, threshold=3)
print(f"迭代后检测到 {len(outliers)} 个异常")

我曾经在一个传感器故障检测中用过这种迭代Z-score。第一轮算出来的均值是0.8(实际应该是0),第二轮降到0.1,第三轮就稳定了。嗯,迭代一两轮通常就够了,太多反而可能把正常边缘点也踢掉。

2.4 假设检验:给异常一个“判决书”

异常检测说到底,是一种统计推断:这个点是否显著不同于正常分布?

这里涉及两个基本概念——p值和置信区间。你不用去背公式,只需要记住它们的直觉:

  • p值:如果这个点真的属于正常分布,它出现的概率有多小?小过0.05(或0.01),我们就“拒绝”它是正常的。
  • 置信区间:正常值应该落在哪个区间里?比如95%置信区间,就是“正常情况下,95%的点会落在这个范围内”。在外面?有理由怀疑异常。

当然,实际业务中很少真去跑t检验或方差分析。但你心里要有这根弦:阈值的选择本质上就是在设定置信水平。3σ ≈ 99.7%置信区间;2σ ≈ 95%;而IQR的1.5倍规则,大约对应2.7σ,给的数据更宽松一些。

特别注意:p值可不是“这个点是异常的概率”。这是很多初学者容易搞混的地方。p值的正确解释是:在原假设(数据正常)下,观察到当前或更极端结果的概率。别在PPT里写错,会被怼的……

2.5 一张图串联所有概念

下面这张SVG图,帮你把这些统计概念和异常检测的关系理一遍。你可以把它当成这一章的“导航图”。

统计学基础与异常检测关系图 原始数据 描述性统计 均值 / 中位数 / 标准差 / IQR 概率分布 正态分布 / 偏度 / 峰度 假设检验 p值 / 置信区间 3σ法则 / Z-score 基于均值+标准差 箱线图 / IQR 基于分位数,抗异常 统计假设检验 Grubbs检验 / ESD 异常检测 = 统计学基础 + 领域知识 + 合理阈值 先理解数据分布,再选择合适方法,最后调参验证

2.6 代码实战:快速计算关键统计量

上面讲的这些指标,用Python几行就能算。我在实际项目中习惯把它们封装成一个函数,每次拿到新数据先跑一遍,心里就有数了。

import numpy as np
import pandas as pd

def data_profile(data, name="Data"):
    """
    返回数据的统计画像,用于快速判断分布特征和异常可能性
    """
    s = pd.Series(data)
    return {
        "样本量": len(data),
        "均值": np.mean(data),
        "中位数": np.median(data),
        "标准差": np.std(data, ddof=1),
        "偏度": s.skew(),
        "峰度": s.kurtosis(),
        "25%分位": np.percentile(data, 25),
        "75%分位": np.percentile(data, 75),
        "IQR": np.percentile(data, 75) - np.percentile(data, 25),
        "最小非异常(Q1-1.5IQR)": np.percentile(data, 25) - 1.5*(np.percentile(data, 75)-np.percentile(data, 25)),
        "最大非异常(Q3+1.5IQR)": np.percentile(data, 75) + 1.5*(np.percentile(data, 75)-np.percentile(data, 25)),
    }

# 示例:生成一份带异常的数据
np.random.seed(42)
normal = np.random.normal(100, 15, 200)
outliers = np.array([10, 150, 200, 250])
data = np.concatenate([normal, outliers])
profile = data_profile(data)
for k, v in profile.items():
    print(f"{k:20s} : {v:.2f}")

运行结果会告诉你:中位数比均值小?说明有右偏或高异常拖高了均值。IQR很大?数据可能比较分散,但异常点还不一定。偏度接近0?可以试试正态假设。这些信号,就是你选择异常检测方法的起点。

经验之谈:别把profile输出当最终结论,它只是快筛工具。真正的异常检测还需要结合业务场景——有时候IQR外的不算异常(比如电商大促的流量峰值),落在IQR内的也可能是异常(比如缓慢漂移)。

2.7 本章小结

这一章我们快速回顾了:

  • 描述性统计量(均值、中位数、标准差、IQR、偏度、峰度)的含义和在异常检测中的用途
  • 正态分布假设及其局限性,以及偏态数据该怎么办
  • Z-score和3σ法则的原理与实战细节
  • p值和置信区间的直觉理解
  • 一张关系图帮你理清知识脉络