数据清洗必修课:异常值检测与处理全攻略

异常值(Outlier)简介

异常值是那些与数据集其他观测值显著不同的异常观测。它们可能由于实验误差、测量误差,或仅仅是数据本身存在的变异性而出现。这些异常值会严重影响模型的表现,导致结果出现偏差——就像大学相对评分中顶尖学生能拉高平均分并影响评分标准一样。处理异常值是数据清洗过程中至关重要的一环。

How to Handle Outliers in Dataset with Pandas

本文将分享如何识别异常值,以及在数据集中应对异常值的不同方法。


检测异常值

检测异常值有多种方法。如果要对这些方法进行分类,主要包括:

1. 基于可视化的方法:
通过绘制散点图或箱线图来观察数据分布,并检查是否存在异常数据点。

2. 基于统计的方法:
如z分数和IQR(四分位距)法。这些方法更可靠,但可能不如可视化直观。

本文将重点介绍IQR方法,并在最后附上其他方法的参考资料供进一步学习。

IQR方法简介

IQR(四分位距)= Q3(第75百分位数) - Q1(第25百分位数)

IQR方法认为,任何低于 Q1 - 1.5 × IQR 或高于 Q3 + 1.5 × IQR 的数据点都被标记为异常值。下面通过生成一些随机数据,并用此方法检测异常值。

代码示例:生成数据并检测异常值

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 生成随机数据
np.random.seed(42)
data = pd.DataFrame({
    'value': np.random.normal(0, 1, 1000)
})

# IQR方法检测异常值
def detect_outliers_iqr(data):
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return (data < lower_bound) | (data > upper_bound)

outliers = detect_outliers_iqr(data['value'])

print(f"检测到的异常值数量:{sum(outliers)}")

输出:
检测到的异常值数量:8


可视化异常值:散点图与箱线图

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# 散点图
ax1.scatter(range(len(data)), data['value'], c=['blue' if not x else 'red' for x in outliers])
ax1.set_title('突出显示异常值的数据集(散点图)')
ax1.set_xlabel('索引')
ax1.set_ylabel('数值')

# 箱线图
sns.boxplot(x=data['value'], ax=ax2)
ax2.set_title('包含异常值的数据集(箱线图)')
ax2.set_xlabel('数值')

plt.tight_layout()
plt.show()

异常值处理方法

1. 删除异常值

这是最简单的处理方式,但并不总是最佳选择。需要考虑以下因素:如果删除异常值会显著减少数据集规模,或这些异常值包含重要信息,则不宜随意删除。但如果异常值是测量误差且数量少,这种方法是合适的。下面演示如何应用这一方法:

# 删除异常值
data_cleaned = data[~outliers]

print(f"原始数据集大小:{len(data)}")
print(f"清洗后数据集大小:{len(data_cleaned)}")

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# 散点图
ax1.scatter(range(len(data_cleaned)), data_cleaned['value'])
ax1.set_title('去除异常值后的数据集(散点图)')
ax1.set_xlabel('索引')
ax1.set_ylabel('数值')

# 箱线图
sns.boxplot(x=data_cleaned['value'], ax=ax2)
ax2.set_title('去除异常值后的数据集(箱线图)')
ax2.set_xlabel('数值')

plt.tight_layout()
plt.show()

注意:
去除异常值后,数据分布可能发生变化。最初的异常值被移除后,新的分布下,原本正常的点可能变成新的异常值。


2. 限制(Capping)异常值

如果你不想丢弃极端数据点,但保留它们会影响分析,可以设置最大值和最小值阈值,把异常值“拉回”合理范围。这里以5%和95%分位数为阈值进行处理:

def cap_outliers(data, lower_percentile=5, upper_percentile=95):
    lower_limit = np.percentile(data, lower_percentile)
    upper_limit = np.percentile(data, upper_percentile)
    return np.clip(data, lower_limit, upper_limit)

data['value_capped'] = cap_outliers(data['value'])

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# 散点图
ax1.scatter(range(len(data)), data['value_capped'])
ax1.set_title('限制异常值后的数据集(散点图)')
ax1.set_xlabel('索引')
ax1.set_ylabel('数值')

# 箱线图
sns.boxplot(x=data['value_capped'], ax=ax2)
ax2.set_title('限制异常值后的数据集(箱线图)')
ax2.set_xlabel('数值')

plt.tight_layout()
plt.show()

说明:
此时极端值被“压缩”到指定范围,散点图顶端与底端的点呈现一条直线。


3. 异常值填补(Imputing)

有时不能删除异常值,也不想像限制法一样将其设为最大或最小值。这时可以用更有意义的值(如均值、中位数或众数)替换异常值。以中位数为例:

data['value_imputed'] = data['value'].copy()
median_value = data['value'].median()
data.loc[outliers, 'value_imputed'] = median_value

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# 散点图
ax1.scatter(range(len(data)), data['value_imputed'])
ax1.set_title('填补异常值后的数据集(散点图)')
ax1.set_xlabel('索引')
ax1.set_ylabel('数值')

# 箱线图
sns.boxplot(x=data['value_imputed'], ax=ax2)
ax2.set_title('填补异常值后的数据集(箱线图)')
ax2.set_xlabel('数值')

plt.tight_layout()
plt.show()

提示:
填补后不一定完全消除异常值,因为IQR也可能发生变化。建议结合实际情况多次尝试。


4. 数据变换(Transformation)

通过对整个数据集做变换,可以减少异常值的影响。常见的变换包括对数变换、平方根变换、Box-Cox变换、Z-score标准化、Yeo-Johnson变换、最小-最大缩放等。变换方法的选择取决于数据特性和分析目标。

选择技巧:

  • 右偏分布:用对数、平方根或Box-Cox变换

  • 左偏分布:先反转数据,再用右偏的技术

  • 稳定方差:用Box-Cox或Yeo-Johnson(后者能处理零和负值)

  • 均值居中与标准化:用z-score标准化

  • 固定范围缩放:用min-max缩放

示例:对右偏数据做对数变换

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 生成右偏数据
np.random.seed(42)
data = np.random.exponential(scale=2, size=1000)
df = pd.DataFrame(data, columns=['value'])

# 对数变换(避免log(0),使用log1p)
df['log_value'] = np.log1p(df['value'])

fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# 原始数据 - 散点图
axes[0, 0].scatter(range(len(df)), df['value'], alpha=0.5)
axes[0, 0].set_title('原始数据(散点图)')
axes[0, 0].set_xlabel('索引')
axes[0, 0].set_ylabel('数值')

# 原始数据 - 箱线图
sns.boxplot(x=df['value'], ax=axes[0, 1])
axes[0, 1].set_title('原始数据(箱线图)')
axes[0, 1].set_xlabel('数值')

# 对数变换后 - 散点图
axes[1, 0].scatter(range(len(df)), df['log_value'], alpha=0.5)
axes[1, 0].set_title('对数变换后数据(散点图)')
axes[1, 0].set_xlabel('索引')
axes[1, 0].set_ylabel('Log(数值)')

# 对数变换后 - 箱线图
sns.boxplot(x=df['log_value'], ax=axes[1, 1])
axes[1, 1].set_title('对数变换后数据(箱线图)')
axes[1, 1].set_xlabel('Log(数值)')

plt.tight_layout()
plt.show()

说明:
对数变换后,绝大多数异常值已被消除,仅剩下极少数异常点。选择合适的变换要充分了解数据,否则可能带来新的问题。


总结

本文介绍了异常值的概念、常见检测方法及应对策略。希望你能根据实际业务需求,灵活选择最适合的数据清洗方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值