当前位置: 代码网 > it编程>前端脚本>Python > Python中检测数据异常值的五种统计技术详解

Python中检测数据异常值的五种统计技术详解

2026年09月28日 • Python •我要评论
处理数据的时候,最让人头大的往往不是复杂的模型调参,而是那些藏在角落里的异常值。你是不是也遇到过这种糟心事:明明业务逻辑看着没毛病,一跑统计指标就飘红;或者模型效果突然断崖式下跌,排查半天发现是几个极

处理数据的时候,最让人头大的往往不是复杂的模型调参,而是那些藏在角落里的异常值。

你是不是也遇到过这种糟心事:明明业务逻辑看着没毛病,一跑统计指标就飘红;或者模型效果突然断崖式下跌,排查半天发现是几个极端值在捣乱。

今天我们来聊聊最常用的 5 种异常值检测方法:z-score、iqr、lof、isolation forest、马氏距离。

通过这篇文章,你将看到:

  • 什么数据形态该用哪种异常值检测方法。
  • 怎么用 python 快速进行异常值检测并跑出结果。
  • 怎么把代码套到自己的业务数据上。

下面我们一一来看。

1. z-score:用标准差倍数判断异常值

z-score 是最简单的一种,它算每个点和均值的距离,相当于几个标准差。一般 |z| > 3 就算异常。

适合单变量、分布接近正态的数据。

下面拿北京地铁某站早高峰每分钟进站人数举例。

正常情况每分钟一百多人,偶尔会有突发大客流或临时限流。

下面造 200 个正常值,围绕 120 波动,再塞 280、20、300 三个极端值。

import numpy as np

np.random.seed(42)
normal = np.random.normal(120, 15, 200)
data = np.concatenate([normal, [280, 20, 300]])

mean = np.mean(data)
std = np.std(data)
z_scores = (data - mean) / std
outlier_indices = np.where(np.abs(z_scores) > 3)[0]

print("z-score 异常值:")
for idx in outlier_indices:
    print(f"索引 {idx}, 值 {data[idx]:.2f}, z={z_scores[idx]:.2f}")

输出:

z-score 异常值:
索引 200, 值 280.00, z=6.95
索引 201, 值 20.00, z=-4.38
索引 202, 值 300.00, z=7.82

跑完的输出里,280、20、300 的 z 值绝对值都超过 3,会被标出来,正常点基本不会误报。

这种单变量、近似正态的场景,z-score 简单直接,够用。

2. iqr:用四分位距判断异常值

iqr 不依赖均值和标准差,它看中间 50% 的数据范围。

超出 q1 - 1.5 * iqr 或 q3 + 1.5 * iqr 的点算异常。

适合偏态分布,或者有极端值的数据。

拿成都某小区住户月度电费举例。

大多数住户几十到两三百,少数开民宿、挖矿或者空调常开的,电费特别高。

电费本身是偏态的,用 iqr 比 z-score 稳。

import numpy as np

np.random.seed(1)
normal = np.random.gamma(shape=4, scale=30, size=300)
data = np.concatenate([normal, [2000, 5000, 8000]])

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 = data[(data < lower_bound) | (data > upper_bound)]

print(f"q1={q1:.2f}, q3={q3:.2f}, iqr={iqr:.2f}")
print(f"下界={lower_bound:.2f}, 上界={upper_bound:.2f}")
print("iqr 异常值:", outliers)

输出:

q1=77.89, q3=161.82, iqr=83.93
下界=-48.01, 上界=287.72
iqr 异常值: [ 289.37700599  289.53075874  289.93834029  328.543792   2000.
 5000.         8000.        ]

跑完会看到上界通常在两三百左右,2000、5000、8000 这些高额电费会被抓出来,少数正常偏高的电费也可能被带上。

整体上 iqr 对偏态数据比较抗干扰,适合先做一轮粗筛。

3. lof:用局部密度判断异常值

lof 看的是局部密度。它比较一个点和它邻居的密度,如果明显比周围稀疏,就认为异常。

适合多变量、局部密度不一样的数据。

拿杭州共享单车停放点早高峰借还车量举例。

正常站点借车和还车量大致都在 30 左右,少数站点借车量极高、还车量极低,或者反过来。

这种异常不看单列数值,而是看组合和周围像不像。

import numpy as np
from sklearn.neighbors import localoutlierfactor

np.random.seed(42)
normal = np.random.normal([30, 30], [5, 5], size=(200, 2))
outliers = np.array([[5, 80], [85, 10], [90, 90], [10, 10]])
data = np.vstack([normal, outliers])

lof = localoutlierfactor(n_neighbors=20, contamination=0.02)
labels = lof.fit_predict(data)
outlier_indices = np.where(labels == -1)[0]

print("lof 异常点:")
for idx in outlier_indices:
    print(f"索引 {idx}, 借车量={data[idx][0]:.1f}, 还车量={data[idx][1]:.1f}")

输出:

lof 异常点:
索引 104, 借车量=32.6, 还车量=49.3
索引 200, 借车量=5.0, 还车量=80.0
索引 201, 借车量=85.0, 还车量=10.0
索引 202, 借车量=90.0, 还车量=90.0
索引 203, 借车量=10.0, 还车量=10.0

跑完输出里,像 (5,80)、(85,10)、(90,90)、(10,10) 这种和正常站点密度明显不同的点会被标出来。

lof 的好处是不要求全局统一分布,能找出局部行为异常的站点。

4. isolation forest:用随机隔离判断异常值

isolation forest 的思路是随机切分数据,异常点因为稀疏,通常几步就被单独隔离出来。适合数据量大、维度高的场景。

拿 618 电商订单举例。正常订单金额几十到几百,商品件数 1 到 5 件。异常订单可能是额采购或恶意测试,金额和件数组合很离谱。

import numpy as np
from sklearn.ensemble import isolationforest

np.random.seed(7)
n = 1000
amount = np.random.normal(200, 80, n).clip(20, 800)
items = np.random.poisson(2, n) + 1
normal = np.column_stack([amount, items])

outliers = np.array([
    [9999, 100],
    [0.01, 500],
    [5000, 1],
    [3000, 200]
])
data = np.vstack([normal, outliers])

iso = isolationforest(contamination=0.01, random_state=42)
labels = iso.fit_predict(data)
outlier_indices = np.where(labels == -1)[0]

print("isolation forest 异常订单:")
for idx in outlier_indices:
    print(f"索引 {idx}, 金额={data[idx][0]:.2f}, 件数={data[idx][1]:.0f}")

输出:

isolation forest 异常订单:
索引 47, 金额=379.81, 件数=1
索引 186, 金额=90.41, 件数=7
索引 267, 金额=224.73, 件数=9
索引 658, 金额=356.52, 件数=6
索引 660, 金额=109.25, 件数=8
索引 662, 金额=329.58, 件数=9
索引 888, 金额=60.75, 件数=9
索引 1000, 金额=9999.00, 件数=100
索引 1001, 金额=0.01, 件数=500
索引 1002, 金额=5000.00, 件数=1
索引 1003, 金额=3000.00, 件数=200

跑完输出里,9999 元 100 件、0.01 元 500 件、5000 元 1 件、3000 元 200 件这些订单会被标出来。

因为 contamination 设成 0.01,可能还会误伤少量正常订单。

总体看,isolation forest 跑得快,适合先在大数据里筛可疑样本。

5. 马氏距离:用协方差结构判断异常值

马氏距离考虑变量之间的协方差。单看某一项可能正常,组合起来很怪,它就能抓出来。

适合多变量且变量之间有相关性的数据。

拿某高中体测数据举例。身高、体重、肺活量三个变量有关联。

正常情况身高越高,体重和肺活量通常也会高一些。

下面造 300 条正常记录,再塞三条组合很怪的数据。

import numpy as np
from scipy.spatial.distance import mahalanobis

np.random.seed(42)
n = 300
height = np.random.normal(170, 6, n)
weight = 60 + 0.7 * (height - 170) + np.random.normal(0, 5, n)
lung = 3500 + 30 * (height - 170) + np.random.normal(0, 300, n)
normal = np.column_stack([height, weight, lung])

outliers = np.array([
    [170, 120, 2000],
    [150, 80, 5000],
    [190, 45, 3000]
])
data = np.vstack([normal, outliers])

mean = np.mean(data, axis=0)
cov = np.cov(data, rowvar=false)
inv_cov = np.linalg.inv(cov)

distances = np.array([mahalanobis(x, mean, inv_cov) for x in data])
threshold = np.percentile(distances, 97.5)
outlier_indices = np.where(distances > threshold)[0]

print("马氏距离异常体测记录:")
for idx in outlier_indices:
    print(f"索引 {idx}, 身高={data[idx][0]:.1f}, 体重={data[idx][1]:.1f}, 肺活量={data[idx][2]:.0f}, 距离={distances[idx]:.2f}")

输出:

马氏距离异常体测记录:
索引 46, 身高=167.2, 体重=50.8, 肺活量=2608, 距离=2.87
索引 74, 身高=154.3, 体重=59.8, 肺活量=2606, 距离=3.33
索引 179, 身高=186.3, 体重=77.0, 肺活量=4176, 距离=3.08
索引 209, 身高=193.1, 体重=72.0, 肺活量=3925, 距离=3.82
索引 262, 身高=150.6, 体重=50.7, 肺活量=3381, 距离=3.31
索引 300, 身高=170.0, 体重=120.0, 肺活量=2000, 距离=10.35
索引 301, 身高=150.0, 体重=80.0, 肺活量=5000, 距离=8.29
索引 302, 身高=190.0, 体重=45.0, 肺活量=3000, 距离=6.06

跑完输出里,身高 170、体重 120、肺活量 2000;

身高 150、体重 80、肺活量 5000;

身高 190、体重 45、肺活量 3000 这种组合会被标出来。

因为阈值取 97.5 百分位,可能还会带上少量边缘正常记录。

马氏距离适合处理变量有关联的多维异常检测,但协方差估计不准时结果会受影响。

总结

怎么选这 5 种方法?

  • z-score:单变量,近似正态分布,最省事。
  • iqr:单变量,偏态分布,或者有极端值,比较稳。
  • lof:多变量,数据有局部密度差异,适合找“周围不一样”的点。
  • isolation forest:数据量大、维度高,想快速筛异常,优先用。
  • 马氏距离:多变量之间有明显相关性,需要把相关性考虑进去。

注意:不管用哪种方法,最后都要结合业务进一步来判断是否异常值,不能只看算法结果。

以上就是python中检测数据异常值的五种统计技术详解的详细内容,更多关于python检测数据异常值的资料请关注代码网其它相关文章!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com