首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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

异常值(Outlier)简介

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

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

检测异常值

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

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()

说明:

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

总结

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

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Oahe_8S5wrTG6U0iQjgwxMOA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券