异常值检测与处理方法总结

先别被"数据科学"这几个字吓到。异常值 其实天天出现在我们身边:

  • 你平时电费300块,这个月突然变成1500块
  • 你每天走8000步,某天微信运动显示50000步(而你那天明明在家躺平);
  • 班里同学考60~80分,突然冒出个100分20分

这些"画风突变"的数据点,就是我们今天要抓的"显眼包"------异常值(Outlier)

它们到底是该删掉的"垃圾数据",还是藏着惊喜的"宝藏信号"?

今天我们就彻底把它弄明白。

原理

异常值三大"出身"

拿到一个异常值,先别急着删。

出身决定命运

出身类型 特征 对待方式
录入错误 身高写成2.6米,工资多打一个零 直接删除或修改(物理消灭)
自然事件 双11网购暴增、夏天开空调电费飙升 保留或缩尾(不能删,这是真实业务)
重大信号 服务器访问量突然暴涨100倍 重点关注(可能是爆款或攻击,这是宝藏)

核心心法三句话

  1. 先看背景,再看数字(别把节假日当错误)。
  2. 先画图,再计算(图片比数字更诚实)。
  3. 先问为什么,再决定删不删(决定权在你的常识,不在代码)。

三大基础检测工具

在写代码前,先搞懂这三个"武器"的原理:

  1. 箱线图(Box Plot) + IQR法则 :最推荐的生活小工具。找出中间50%人群的分布范围(Q1~Q3),超出 Q3 + 1.5×IQR 或低于 Q1 - 1.5×IQR 的就是异常。不需要数据符合正态分布,非常皮实
  2. Z-Score(标准分数) :适合数据呈钟形分布。计算"这个点离平均值有多远",通常 |Z| > 3 就是异常(概率极低的事件)。
  3. 散点图(Scatter Plot) :对付多维组合异常 。比如"学习1小时却考98分"------单看成绩正常,单看时长也正常,但组合在一起就离谱!这种只有画图才能抓出来。

场景实战

揪出"电费刺客"(箱线图 + IQR法则)

背景:你记录了自家过去12个月的月度电费(元)。

大部分是200~250元,但7、8月开空调飙到了580和620。我们要精准定位它们。

代码对应 :上文 方法 1 和 2(可视化 + IQR计算)。

python 复制代码
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 1. 制造数据(12个月电费)
np.random.seed(42)
normal_months = np.random.normal(230, 15, 10).astype(int)  # 正常10个月
summer_months = [580, 620]  # 夏天异常值
data = np.concatenate([normal_months, summer_months])

df = pd.DataFrame({'month': range(1, 13), 'bill': data})
print("📋 原始电费:", df['bill'].tolist())

# 2. 🎨 画箱线图(一眼看出异常)
plt.figure(figsize=(10, 4))
sns.boxplot(x=df['bill'], color='skyblue')
plt.title("🔌 年度电费箱线图(右侧飘着的两个点就是异常值)", fontsize=14)
plt.xlabel("电费(元)")
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()

# 3. 🧮 IQR 精准计算(手撕异常值)
Q1 = df['bill'].quantile(0.25)
Q3 = df['bill'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

outliers_iqr = df[(df['bill'] < lower) | (df['bill'] > upper)]
print(f"✅ IQR检测结果:异常值为第 {outliers_iqr['month'].tolist()} 个月,金额 {outliers_iqr['bill'].tolist()} 元")
print(f"⚠️ 判定界限:超过 {upper:.2f} 元就是异常。")

运行结果:你会看到箱线图右侧有两个孤零零的小圆点,控制台精准输出 7, 8 月。

思考:这是季节因素,不能轻易删,后面会教你怎么处理。

抓出"摸鱼考勤"(Z-Score + 散点图)

背景:你是小老板,记录了10个员工每日平均步数(千步)。

大部分在5~8千步,但有个人天天坐着只有0.4千步,还有个兼职快递员走了15.2千步。

代码对应 :上文 方法 3(Z-Score检测)。

python 复制代码
from scipy import stats

# 员工步数数据(千步)
steps = [5.2, 6.1, 5.8, 7.0, 6.5, 0.4, 5.9, 6.3, 15.2, 6.0]
df_steps = pd.DataFrame({'employee': range(1, 11), 'steps': steps})

# 计算 Z-Score 并标记(|Z| > 2.5 视为异常,这里稍宽松方便演示)
df_steps['z_score'] = np.abs(stats.zscore(df_steps['steps']))
df_steps['is_outlier'] = df_steps['z_score'] > 2.5

print(df_steps)

# 画散点图(红点即异常)
plt.figure(figsize=(10, 4))
colors = ['red' if x else 'blue' for x in df_steps['is_outlier']]
plt.scatter(df_steps['employee'], df_steps['steps'], c=colors, s=100)
plt.axhline(y=df_steps['steps'].mean(), color='green', linestyle='--', label='平均值')
plt.title("👟 员工步数散点图(红点是Z-Score找出的异常值)", fontsize=14)
plt.xlabel("员工编号")
plt.ylabel("步数(千步)")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

运行结果:0.4(可能少写了个0)和15.2(特殊工种)被标红。

思考:前者该改,后者该单独分析。

识破"虚假学霸"(散点图趋势法)

背景:这是最精彩的多维异常。

数据:10个学生的每日学习时长考试成绩。正常情况下学得越久分越高。

但出现了一个学1小时考98分 (天才?)和一个学9小时考42分(方法错了?)。

这种组合异常,只能用散点图抓。

代码对应 :上文 方法 1(多维散点图可视化)。

python 复制代码
# 制造数据
study_hours = [2, 3, 4, 5, 5, 6, 7, 8, 1, 9]   # 学习时长
scores = [45, 55, 60, 68, 72, 78, 82, 88, 98, 42] # 成绩(最后两个是异常组合)

df_study = pd.DataFrame({'study': study_hours, 'score': scores})

# 画散点图 + 趋势线
plt.figure(figsize=(8, 6))
plt.scatter(df_study['study'], df_study['score'], color='blue', s=100)

# 标注出可疑的点
for i in range(len(df_study)):
    if df_study.loc[i, 'study'] in [1, 9]:
        plt.annotate(f'❓学生{i+1}', (df_study.loc[i, 'study'], df_study.loc[i, 'score']), 
                     textcoords="offset points", xytext=(10,10), ha='center', fontsize=12, color='red')

# 画一条正常趋势线
z = np.polyfit(study_hours, scores, 1)
p = np.poly1d(z)
plt.plot(sorted(study_hours), p(sorted(study_hours)), "g--", alpha=0.8, label='正常趋势线')

plt.title("📚 学习时长 vs 考试成绩(远离绿线的红点是异常)", fontsize=14)
plt.xlabel("每日学习时长(小时)")
plt.ylabel("考试成绩")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

运行结果:左上角(1小时,98分)和右下角(9小时,42分)严重偏离绿色趋势线。

思考:这是"新大陆信号",绝不能删,要作为特殊案例研究!

异常值怎么处置?

跑完上面的代码,你手头有了异常值列表。

别慌,掏出下面这张最终决策卡,对号入座:

处置方式 适用场景(对应上述案例) Python 代码一句通
1. 直接删除 确定是输入错误(如场景二的0.4千步,明显是漏了数字)。 df.drop(index=异常行索引, inplace=True)
2. 插补替换 错误但不舍得删样本(用中位数/均值代替离谱值)。 df.loc[条件, '列名'] = df['列名'].median()
3. 缩尾处理 数据本身没错,但不想让它拉高平均值(如场景一的夏季电费)。把极端值"拽"回正常上限。 df['bill'] = df['bill'].clip(upper=upper_bound)
4. 分层保留 数据真实且有意义(如场景三的"学1小时考98分"),把它作为特殊群体单独分析,不参与整体平均。 新建一个筛选列 df['special_flag'] = True/False

举个缩尾处理的实操代码(接上面的第一个场景):

python 复制代码
# 把580和620替换成上限值(假设上限是347.5)
df['bill_winsorized'] = df['bill'].clip(upper=upper)
print("缩尾处理后的电费:", df['bill_winsorized'].tolist())
# 输出:[280, 300, 290, 310, 285, 347.5]  <- 平均值更贴近真实日常消费水平

写在最后

异常值不是洪水猛兽,它们是数据世界里的警报器

下次遇到任何数据(无论是Excel报表、支付宝账单,还是智能手表心率数据),试试下面的流程:

  1. 画个散点图/箱线图(肉眼侦查)。
  2. 跑个IQR或Z-Score(算法验证)。
  3. 对着背景问三个问题:这是写错了吗?这是特殊事件吗?这是重大新趋势吗?
  4. 看看决策卡:错误的改/删,事件的缩尾,新趋势的留着写专项报告。
相关推荐
SelectDB1 小时前
Doris 还是 ClickHouse?一张表说清 6 个关键差异(实战笔记)
大数据·数据库·数据分析
SelectDB2 小时前
Doris 直查 Paimon 索引:快手湖上向量检索的共建与落地
大数据·数据库·数据分析
2601_966949658 小时前
批量 K 线不只是提速:因子研究中的数据质量、复权与回测偏差
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
AI职业加油站8 小时前
2026大数据运维行业趋势复盘:大数据运维工程师赋能发展
大数据·运维·人工智能·学习·数据分析·职场发展
BYSJMG9 小时前
计算机毕业设计选题推荐:基于大数据的水质多指标关联分析与可视化的设计与实现,Spark 加 K-Means 做水质分群
大数据·hadoop·信息可视化·数据分析·spark·kmeans·课程设计
小飞象—木兮11 小时前
BCG波士顿矩阵深度解析及应用指南:核心逻辑、落地路径、常见误区、案例
大数据·人工智能·矩阵·数据分析·用户运营
计算机源码社19 小时前
【大数据项目实战】基于大数据的影视内容生态综合质量分析与可视化-基于数据挖掘的影视内容类型共现与口碑聚类分析系统
大数据·人工智能·python·数据挖掘·数据分析·毕业设计·课程设计
Mr数据杨1 天前
议会事务多标签文本分类实战 从 Open Data St Gallen 看推荐排序建模
人工智能·数据分析·kaggle竞赛
2601_962780691 天前
2026 秋招数据产品分析岗位技能拆解
数据分析