21·数据处理进阶

异常值检测与处理方法

data-cleaningstatistics

第 21 课 - 异常值检测与处理方法

1. 学习目标

完成本课学习后,你将能够:

  1. 理解异常值:明确什么是异常值(离群点)及其对模型可能产生的负面影响。
  2. 掌握常用检测方法:熟练运用 Z-score、IQR(四分位距)等统计方法识别数据集中的异常值。
  3. 学会合理处理策略:了解删除、替换、转换等处理异常值的策略,并能根据数据背景做出选择。
  4. 代码实践应用:使用 Python (pandas, numpy) 完成对数据集中异常值的检测与处理。

2. 核心概念

在数据清洗的“家族”中,异常值处理是紧跟缺失值处理的另一个关键成员。

什么是异常值? 想象一下,我们调查一个班级同学的身高,大家普遍在160-185厘米之间。突然,数据里出现了“230厘米”或“95厘米”这样的记录。这些明显偏离“正常”范围的数据点,就是异常值(Outliers)。它们可能是由于测量错误、数据录入失误、或是真实存在的极端情况(如巨人症)产生的。

为什么要处理异常值? 许多机器学习算法(尤其是线性模型)对异常值非常敏感。异常值会像磁铁一样,把模型的拟合线(或超平面)拉向自己,从而导致:

  • 模型参数估计不准确。
  • 模型泛化能力下降。
  • 基于统计量的分析(如均值、方差)产生误导。

常用检测方法:

  1. 简单统计法:通过观察数据的最大值、最小值来初步判断。结合业务知识,如果某个值明显不合理(如人的年龄为200岁),即可认定。
  2. Z-score 法:基于数据的均值和标准差。公式为:Z = (x - μ) / σ
    • 直觉理解:一个点距离平均值有多少个“标准差”的距离。通常,如果一个数据的 Z-score 的绝对值大于 3(或 2),就被视为异常值。这适用于近似正态分布的数据。
  3. IQR(四分位距)法:一种更稳健的方法,对偏斜数据不敏感。
    • 计算:首先找到第一四分位数(Q1,25%分位点)和第三四分位数(Q3,75%分位点)。IQR = Q3 - Q1
    • 判定:通常认为,低于 Q1 - 1.5*IQR 或高于 Q3 + 1.5*IQR 的数据点是异常值。这个方法也叫箱线图法

3. 代码示例

以下是一个完整的示例,演示如何使用 Pandas 和 NumPy 进行异常值的检测和处理。

import pandas as pd
import numpy as np

# 1. 创建一个包含异常值的示例数据集
np.random.seed(42)
# 生成 100 个正常数据,均值 50,标准差 10
data = 50 + 10 * np.random.randn(100)
# 故意插入一些异常值
outliers = np.array([120, -20, 150, -30])
data_with_outliers = np.concatenate([data, outliers])
# 创建一个 DataFrame
df = pd.DataFrame({'value': data_with_outliers})

print("原始数据统计描述:")
print(df['value'].describe())
print("-" * 40)

# ==================== 检测异常值 ====================

# 方法一:Z-score 法
def detect_outliers_zscore(data, threshold=3):
    """
    使用 Z-score 检测异常值
    :param data: pandas Series 或 numpy array
    :param threshold: 阈值,默认为 3
    :return: 布尔掩码,True 表示是异常值
    """
    mean = np.mean(data)
    std = np.std(data)
    z_scores = [(x - mean) / std for x in data]
    return np.abs(z_scores) > threshold

# 方法二:IQR 法
def detect_outliers_iqr(data, factor=1.5):
    """
    使用 IQR 检测异常值
    :param data: pandas Series 或 numpy array
    :param factor: IQR 的倍数,默认为 1.5
    :return: 布尔掩码,True 表示是异常值
    """
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    lower_bound = Q1 - factor * IQR
    upper_bound = Q3 + factor * IQR
    return (data < lower_bound) | (data > upper_bound)

# 应用检测方法
df['is_outlier_zscore'] = detect_outliers_zscore(df['value'])
df['is_outlier_iqr'] = detect_outliers_iqr(df['value'])

print("使用 Z-score (阈值=3) 检测到的异常值:")
print(df[df['is_outlier_zscore'] == True][['value']])
print("\n使用 IQR (倍数=1.5) 检测到的异常值:")
print(df[df['is_outlier_iqr'] == True][['value']])
print("-" * 40)

# ==================== 处理异常值 ====================
# 选择 IQR 方法的结果进行处理演示
outliers_mask = df['is_outlier_iqr']
print(f"共发现 {outliers_mask.sum()} 个异常值。")

# 策略一:删除
# df_cleaned = df[~outliers_mask].copy() # ~ 表示取反,即保留非异常值

# 策略二:盖帽法(替换为边界值)
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 将低于下限的值替换为下限,高于上限的值替换为上限
df['value_capped'] = df['value'].clip(lower=lower_bound, upper=upper_bound)

# 策略三:替换为中位数或均值
# median_value = df['value'].median()
# df['value_filled_median'] = df['value'].where(~outliers_mask, median_value)

print("\n原始数据(前5个):", df['value'].head().values)
print("盖帽处理后数据(前5个):", df['value_capped'].head().values)
# 查看被处理掉的异常值原值和新值
print("\n被盖帽处理的异常值对比:")
print(df[outliers_mask][['value', 'value_capped']])

# 更新 DataFrame,使用处理后的数据
df_processed = df[['value_capped']].copy()
df_processed.columns = ['value'] # 恢复列名
print("\n处理后数据的统计描述:")
print(df_processed['value'].describe())

4. 实践练习

练习 1:基础检测 给定数据:[15, 18, 20, 22, 25, 28, 30, 32, 100]。请使用 IQR 法(默认1.5倍)编写函数检测其中的异常值。 要求:输出检测到的异常值列表。 预期输出[100]

练习 2:处理应用 继续使用练习 1 的数据。请编写代码,使用 盖帽法 处理被检测出的异常值。 要求:打印处理后的完整数据列表。 预期输出[15, 18, 20, 22, 25, 28, 30, 32, 32.5] (注:32.5 是根据 IQR 计算出的上限值,具体值取决于计算过程)

练习 3:综合挑战 现在有一个模拟的“员工月薪”数据列:[5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 50000, 60000]

  • 步骤 1:分别用 Z-score (阈值=2) 和 IQR (倍数=1.5) 检测异常值。
  • 步骤 2:观察两种方法结果是否一致,并思考哪个可能更合理。
  • 步骤 3:选择你认为更合理的方法,用 替换为中位数 的策略处理异常值,并打印处理前后的均值对比。 要求:输出两种方法的检测结果、你的选择理由、以及处理前后的均值。 预期输出(示例)
Z-score (t=2) 检测到的异常值: [50000, 60000]
IQR (k=1.5) 检测到的异常值: [50000, 60000]
我选择 IQR 法,因为它对极端值不敏感,更稳健。
处理前均值: 15650.0
处理后均值: 6750.0

5. 常见错误

  1. 滥用检测方法:不考虑数据分布就使用 Z-score。如果数据严重偏斜,Z-score 可能失效,此时 IQR 法更可靠。
  2. 处理过于武断:发现异常值就直接删除。这可能会丢失重要信息。一定要结合业务背景判断。例如,在反欺诈场景中,异常交易恰恰是我们要关注的重点。
  3. 阈值/倍数选择死板:总是固定使用“3”或“1.5”。应根据实际数据和业务需求调整。对金融风控数据,阈值可能设为2以捕捉更多可疑行为。
  4. 忽略异常值产生原因:如果是数据录入错误,应尝试修正;如果是真实罕见事件,处理策略就完全不同。“先调查,后处理” 是原则。

6. 小结

本课我们学习了数据清洗中的重要一环——异常值处理。

  • 认识异常值:它们是显著偏离整体分布的观测点,可能扭曲模型训练。
  • 检测是关键:掌握了 Z-score(基于均值和标准差,适合正态数据)和 IQR(基于四分位数,更稳健)这两种核心检测方法。
  • 处理需谨慎:常见策略有删除盖帽(替换为边界值)替换(如用均值、中位数)。没有绝对最优的方法,选择取决于数据分布业务背景
  • 代码实践:通过编写函数,我们将统计方法转化为实际可用的数据处理工具。

记住,异常值处理不是机械的数学操作,它需要你作为数据科学家的判断力。在动手处理前,多问一句“这个异常值从哪里来?意味着什么?”,这将帮助你做出更明智的决策。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「类别特征编码:One-Hot、Label 与 Target Encoding」 以巩固所学知识。