当前位置: 首页 > news >正文

模块三-数据清洗与预处理——15. 异常值检测与处理

15. 异常值检测与处理

1. 概述

异常值(Outlier)是指与其他观测值显著不同的数据点。它们可能来自测量错误、数据录入错误,也可能是真实的极端情况(如高收入人群)。正确识别和处理异常值对数据分析至关重要。

importpandasaspdimportnumpyasnpimportmatplotlib.pyplotaspltimportseabornassns# 设置中文字体plt.rcParams['font.sans-serif']=['SimHei','Arial Unicode MS','DejaVu Sans']plt.rcParams['axes.unicode_minus']=False# 创建包含异常值的示例数据np.random.seed(42)df=pd.DataFrame({'ID':range(1,101),'年龄':np.random.normal(35,10,100).round(0),'工资':np.random.normal(8000,2000,100).round(0),'消费次数':np.random.poisson(10,100)})# 添加异常值df.loc[10,'年龄']=120# 年龄异常(120岁)df.loc[25,'工资']=50000# 工资异常(极高)df.loc[40,'年龄']=5# 年龄异常(5岁)df.loc[55,'工资']=500# 工资异常(极低)df.loc[70,'消费次数']=100# 消费次数异常print("原始数据:")print(df.head(15))print(f"\n基本统计:")print(df[['年龄','工资','消费次数']].describe())

2. 异常值检测方法

2.1 描述性统计

# 查看数据分布print("各列统计信息:")print(df[['年龄','工资','消费次数']].describe())# 观察 min 和 max 是否在合理范围print(f"年龄范围:{df['年龄'].min()}-{df['年龄'].max()}")print(f"工资范围:{df['工资'].min()}-{df['工资'].max()}")

2.2 箱线图(IQR)方法

IQR(四分位距)= Q3 - Q1,通常将小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的值视为异常。

defdetect_outliers_iqr(data,column):"""使用 IQR 方法检测异常值"""Q1=data[column].quantile(0.25)Q3=data[column].quantile(0.75)IQR=Q3-Q1 lower_bound=Q1-1.5*IQR upper_bound=Q3+1.5*IQR outliers=data[(data[column]<lower_bound)|(data[column]>upper_bound)]returnoutliers,lower_bound,upper_bound# 检测年龄异常值age_outliers,age_lower,age_upper=detect_outliers_iqr(df,'年龄')print(f"年龄正常范围: [{age_lower:.0f},{age_upper:.0f}]")print(f"年龄异常值数量:{len(age_outliers)}")print(f"年龄异常值:{age_outliers['年龄'].tolist()}")# 检测工资异常值salary_outliers,salary_lower,salary_upper=detect_outliers_iqr(df,'工资')print(f"\n工资正常范围: [{salary_lower:.0f},{salary_upper:.0f}]")print(f"工资异常值数量:{len(salary_outliers)}")print(f"工资异常值:{salary_outliers['工资'].tolist()}")

2.3 Z-Score 方法

Z-Score 表示数据点偏离均值的标准差倍数,通常 |Z| > 3 视为异常。

defdetect_outliers_zscore(data,column,threshold=3):"""使用 Z-Score 方法检测异常值"""z_scores=np.abs((data[column]-data[column].mean())/data[column].std())outliers=data[z_scores>threshold]returnoutliers,z_scores# 检测年龄异常值age_outliers_z,age_z=detect_outliers_zscore(df,'年龄')print(f"年龄 Z-Score 异常值数量:{len(age_outliers_z)}")print(f"年龄异常值:{age_outliers_z['年龄'].tolist()}")# 检测工资异常值salary_outliers_z,salary_z=detect_outliers_zscore(df,'工资')print(f"\n工资 Z-Score 异常值数量:{len(salary_outliers_z)}")print(f"工资异常值:{salary_outliers_z['工资'].tolist()}")

2.4 可视化检测

# 箱线图fig,axes=plt.subplots(1,3,figsize=(15,5))columns=['年龄','工资','消费次数']fori,colinenumerate(columns):axes[i].boxplot(df[col])axes[i].set_title(f'{col}箱线图')axes[i].set_ylabel(col)plt.tight_layout()plt.show()# 散点图plt.figure(figsize=(10,6))plt.scatter(df['年龄'],df['工资'],alpha=0.6)plt.xlabel('年龄')plt.ylabel('工资')plt.title('年龄 vs 工资 散点图')# 标记异常值age_outliers_idx=age_outliers.index.tolist()plt.scatter(df.loc[age_outliers_idx,'年龄'],df.loc[age_outliers_idx,'工资'],color='red',s=100,label='年龄异常')salary_outliers_idx=salary_outliers.index.tolist()plt.scatter(df.loc[salary_outliers_idx,'年龄'],df.loc[salary_outliers_idx,'工资'],color='orange',s=100,marker='s',label='工资异常')plt.legend()plt.show()

3. 异常值处理方法

3.1 删除异常值

# 删除年龄异常的行df_clean=df[~df.index.isin(age_outliers.index)]print(f"删除年龄异常前:{len(df)}行")print(f"删除年龄异常后:{len(df_clean)}行")# 删除工资异常的行df_clean=df_clean[~df_clean.index.isin(salary_outliers.index)]print(f"删除工资异常后:{len(df_clean)}行")

3.2 截断/缩尾处理

# 百分位数截断lower_bound=df['工资'].quantile(0.01)upper_bound=df['工资'].quantile(0.99)print(f"1%分位数:{lower_bound:.0f}, 99%分位数:{upper_bound:.0f}")df['工资_截断']=df['工资'].clip(lower_bound,upper_bound)print("\n截断后:")print(df[['工资','工资_截断']].head(10))# 比较原值和截断后的统计print("\n原工资统计:")print(df['工资'].describe())print("\n截断后统计:")print(df['工资_截断'].describe())

3.3 替换异常值

# 用中位数替换异常值median_age=df['年龄'].median()df['年龄_替换']=df['年龄'].copy()df.loc[age_outliers.index,'年龄_替换']=median_ageprint("年龄替换:")print(df[['年龄','年龄_替换']].loc[age_outliers.index])# 用均值替换异常值mean_salary=df['工资'].mean()df['工资_替换']=df['工资'].copy()df.loc[salary_outliers.index,'工资_替换']=mean_salaryprint("\n工资替换:")print(df[['工资','工资_替换']].loc[salary_outliers.index])

4. 多维度异常检测

4.1 业务规则检测

# 定义业务规则defcheck_business_rules(df):"""基于业务规则的异常检测"""rules=[]# 规则1:年龄应在 18-70 之间rule1=(df['年龄']<18)|(df['年龄']>70)rules.append(('年龄超出业务范围',rule1))# 规则2:工资应在 2000-50000 之间rule2=(df['工资']<2000)|(df['工资']>50000)rules.append(('工资超出业务范围',rule2))# 规则3:消费次数应在 0-50 之间rule3=(df['消费次数']<0)|(df['消费次数']>50)rules.append(('消费次数超出业务范围',rule3))returnrules rules=check_business_rules(df)forrule_name,rule_conditioninrules:violation_count=rule_condition.sum()ifviolation_count>0:print(f"{rule_name}:{violation_count}条记录")print(df[rule_condition][['ID','年龄','工资','消费次数']])print()

4.2 组合条件检测

# 年龄与工资的组合异常(如 18岁但工资 50000)df['年龄工资比']=df['工资']/df['年龄']unusual=df[(df['年龄']<25)&(df['工资']>30000)]print("年龄<25且工资>30000的异常组合:")print(unusual[['ID','年龄','工资']])# 删除异常组合列df=df.drop('年龄工资比',axis=1)

5. 完整示例:销售数据异常检测

# 创建销售数据np.random.seed(42)sales=pd.DataFrame({'日期':pd.date_range('2024-01-01',periods=200,freq='D'),'销售额':np.random.normal(5000,1000,200).round(0),'订单量':np.random.poisson(50,200),'客单价':np.random.normal(100,20,200).round(0)})# 添加异常值sales.loc[15,'销售额']=30000# 异常高sales.loc[30,'订单量']=5# 异常低sales.loc[45,'客单价']=500# 异常高sales.loc[60,'销售额']=500# 异常低sales.loc[80,'订单量']=200# 异常高print("="*60)print("销售数据异常检测与处理")print("="*60)print("\n原始数据统计:")print(sales[['销售额','订单量','客单价']].describe())# 1. 使用 IQR 方法检测异常print("\n1. IQR 异常检测:")forcolin['销售额','订单量','客单价']:outliers,lower,upper=detect_outliers_iqr(sales,col)print(f"{col}: 正常范围 [{lower:.0f},{upper:.0f}], 异常值{len(outliers)}个")# 2. 可视化异常fig,axes=plt.subplots(1,3,figsize=(15,5))fori,colinenumerate(['销售额','订单量','客单价']):axes[i].boxplot(sales[col])axes[i].set_title(f'{col}箱线图(含异常)')axes[i].set_ylabel(col)plt.tight_layout()plt.show()# 3. 处理异常值print("\n3. 异常值处理:")sales_clean=sales.copy()forcolin['销售额','订单量','客单价']:Q1=sales_clean[col].quantile(0.25)Q3=sales_clean[col].quantile(0.75)IQR=Q3-Q1 lower=Q1-1.5*IQR upper=Q3+1.5*IQR# 截断处理sales_clean[col]=sales_clean[col].clip(lower,upper)print(f"处理前异常值:")print(f" 销售额:{len(sales[(sales['销售额']<lower)|(sales['销售额']>upper)])}")print(f"处理后异常值:{len(sales_clean[(sales_clean['销售额']<lower)|(sales_clean['销售额']>upper)])}")# 4. 处理前后对比print("\n4. 处理前后统计对比:")print("处理前:")print(sales[['销售额','订单量','客单价']].describe())print("\n处理后:")print(sales_clean[['销售额','订单量','客单价']].describe())

6. 异常值处理决策流程

发现异常值 │ ├─ 确认是数据错误 │ │ │ ├─ 能修正 → 根据正确值修正 │ ├─ 无法修正 → 删除 │ └─ 不确定 → 标记保留 │ ├─ 确认是真实极端值 │ │ │ ├─ 分析需要 → 保留 │ ├─ 模型敏感 → 截断/缩尾 │ └─ 需要平滑 → 替换(均值/中位数) │ └─ 无法判断 │ ├─ 敏感分析 → 对比删除/保留的影响 └─ 标记列 → 添加异常标记字段

7. 总结

方法原理适用场景
IQR 箱线图基于四分位距通用,对分布假设要求低
Z-Score基于标准差正态分布数据
业务规则领域知识有明确范围约束
可视化箱线图、散点图初步探索
删除直接移除确定是错误数据
截断/缩尾限制在百分位数内保留数据量
替换用统计量替换减少异常影响

http://www.jsqmd.com/news/813367/

相关文章:

  • 手把手教你用Vivado配置Xilinx ERNIC IP,实现FPGA上的RoCE v2硬件加速
  • 别只会改设置!Chrome/Edge浏览器主页被劫持的三种隐藏原因与根治方法
  • 深入GD32F407时钟树:对比STM32F4,聊聊国产MCU时钟设计的异同与调试技巧
  • wangEditor 粘贴 Word 图文混合内容的完整解决方案与避坑指南
  • OAuth 2.0与动态路由集成:构建安全、智能的API网关实践
  • LeetCode 70. 爬楼梯
  • PvZ Toolkit终极指南:如何快速上手植物大战僵尸PC版最强修改器
  • 2026年知名的全案设计/设计工作室/南充装修设计/南充别墅设计装修行业公司推荐 - 品牌宣传支持者
  • C++多线程编程:深入剖析std::thread的使用方法
  • 伺服系统高频啸叫故障排查:从机械共振到控制回路不稳定的诊断历程
  • 告别内存泄漏和数组越界:用CppCheck给你的C++项目做一次免费‘体检’
  • HS2-HF_Patch:Honey Select 2游戏增强补丁完整指南
  • 国产多模态大模型“刘知远”:技术原理、实战应用与未来展望
  • 量子计算连续门集:原理、实现与优化
  • 嵌入式系统自校准与自适应设计:从硬件映射到软件智能的实现
  • DAC 2013奥斯汀会议数据解读:技术会议选址如何影响参会质量与行业生态
  • AI Helpers:基于Kubernetes的AI/ML模型部署自动化工具集
  • PPT加密:保护PPT文件安全的两种加密方法
  • Claude Code Session 实战指南:AI 结对编程效能提升手册
  • 微信小程序 车牌号输入组件:从交互设计到代码实现的完整指南
  • 从TTP223到JL523:低成本电容触摸按钮的选型与实战
  • 2026年知名的精工装修施工/南充精工施工本地公司推荐 - 品牌宣传支持者
  • 基于LLM与OpenClaw的智能自动化:构建自然语言驱动的桌面脚本生成器
  • 把旧笔记本变成第二台电脑的“上网卡”:Win10/11网络共享实战指南
  • ChatGPT角色扮演调教指南:从提示词设计到沉浸式AI阿罗娜构建
  • LeetCode 287. 寻找重复数
  • 2026年口碑好的青岛镀锌风管/青岛除尘风管/青岛排烟风管/青岛角钢法兰风管优质厂家推荐榜 - 行业平台推荐
  • 2026年专业耐高温白钢管/201白钢管优质厂家汇总推荐 - 品牌宣传支持者
  • PX4开发环境搭建后,你的QGroundControl和MAVROS连接对了吗?
  • 如何快速实现语音转文字:AsrTools 零配置音频转字幕工具指南