4. 数据清洗基础:缺失值处理、异常值检测与重复数据去重

数据清洗是数据科学流程中最耗时但最关键的环节。原始数据通常包含缺失值、异常值和重复记录,若不处理,将直接影响模型训练效果与业务决策的准确性。本章聚焦三大核心清洗任务:缺失值处理、异常值检测与重复数据去重,提供从理论到代码的完整实战方案。

核心原则:清洗策略需结合业务场景与数据分布。盲目删除或填充可能导致信息丢失或引入偏差。建议先探索数据质量报告,再制定针对性方案。

4.1 缺失值处理

缺失值指数据集中某个或某些字段为空(NaN、None、空字符串等)。处理方式主要分为三类:删除、填充与插值。

4.1.1 删除缺失值

当缺失比例极低(如 < 5%)且缺失完全随机时,可直接删除含缺失值的行或列。但需注意:删除会损失样本量,若缺失非随机,可能引入偏差。

方法 适用场景 代码示例(Pandas)
删除含缺失的行 缺失比例低,样本充足 df.dropna(axis=0)
删除含缺失的列 某列缺失率极高(如 > 80%) df.dropna(axis=1, thresh=len(df)*0.8)
删除全部缺失的行 整行无有效数据 df.dropna(how='all')

4.1.2 填充缺失值

填充是更常用的策略,通过统计量或模型预测填补空缺。常见填充方式包括:

  • 固定值填充:适用于分类变量(如用“未知”填充)或业务已知默认值。
  • 统计量填充:数值型常用均值、中位数、众数。均值对异常值敏感,中位数更稳健。
  • 前向/后向填充:适用于时间序列数据,用前一个或后一个有效值填充。
  • KNN 填充:基于近邻样本的均值或众数填充,保留数据分布结构。
# 统计量填充示例
import pandas as pd
import numpy as np

df = pd.DataFrame({'A': [1, 2, np.nan, 4, 5],
                   'B': [10, np.nan, 30, 40, 50]})

# 均值填充
df['A'].fillna(df['A'].mean(), inplace=True)

# 中位数填充
df['B'].fillna(df['B'].median(), inplace=True)

# 前向填充(时间序列)
df['C'] = df['A'].fillna(method='ffill')

4.1.3 插值法

插值利用已知数据点之间的数学关系估算缺失值,适用于连续型数据。常用方法包括线性插值、多项式插值和样条插值。

# 线性插值示例
df['D'] = [1, np.nan, 3, np.nan, 5]
df['D'].interpolate(method='linear', inplace=True)
print(df['D'])
# 输出: [1.0, 2.0, 3.0, 4.0, 5.0]
实战建议:对于时间序列数据,优先使用插值或前向填充;对于分类数据,优先使用众数填充或单独编码“缺失”类别。避免在缺失比例超过 50% 的列上使用均值填充,以免扭曲分布。
缺失值处理决策流程 缺失值处理决策流程 检测缺失值 缺失比例? 低 (<5%):删除行 中 (5%-50%):填充/插值 高 (>50%):删除列 选择填充策略:统计量 / 插值 / KNN 生成清洗后数据集

4.2 异常值检测

异常值(离群点)是显著偏离其他观测值的样本点。异常值可能由测量误差、数据录入错误或真实稀有事件引起。常用检测方法包括 3σ 原则、IQR 法和 Z-score 法。

4.2.1 3σ 原则(拉依达准则)

假设数据服从正态分布,约 99.7% 的数据落在均值 ±3 个标准差范围内。超出此范围的值视为异常。

import numpy as np

def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower_bound = mean - 3 * std
    upper_bound = mean + 3 * std
    outliers = [x for x in data if x < lower_bound or x > upper_bound]
    return outliers, lower_bound, upper_bound

data = [10, 12, 11, 13, 100, 9, 11, 12, 10, 11]
outliers, lb, ub = detect_outliers_3sigma(data)
print(f"异常值: {outliers}, 下界: {lb:.2f}, 上界: {ub:.2f}")

4.2.2 IQR 法(四分位距法)

IQR 法不依赖正态分布假设,适用于偏态分布。定义 Q1(25% 分位数)和 Q3(75% 分位数),IQR = Q3 - Q1。异常值定义为小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的值。

def detect_outliers_iqr(data):
    q1 = np.percentile(data, 25)
    q3 = np.percentile(data, 75)
    iqr = q3 - q1
    lower_bound = q1 - 1.5 * iqr
    upper_bound = q3 + 1.5 * iqr
    outliers = [x for x in data if x < lower_bound or x > upper_bound]
    return outliers, lower_bound, upper_bound

outliers, lb, ub = detect_outliers_iqr(data)
print(f"异常值: {outliers}, 下界: {lb:.2f}, 上界: {ub:.2f}")

4.2.3 Z-score 法

Z-score 衡量数据点与均值的偏离程度,以标准差为单位。通常 |Z| > 3 视为异常。Z-score 对极端值敏感,适合数据量较大且近似正态的场景。

from scipy import stats

def detect_outliers_zscore(data, threshold=3):
    z_scores = np.abs(stats.zscore(data))
    outliers = [data[i] for i, z in enumerate(z_scores) if z > threshold]
    return outliers, z_scores

outliers, z_scores = detect_outliers_zscore(data)
print(f"异常值: {outliers}, Z-scores: {z_scores}")
方法 假设条件 优点 缺点
3σ 原则 数据服从正态分布 简单直观,计算快 对非正态分布效果差
IQR 法 无分布假设 稳健,不受极端值影响 对样本量敏感
Z-score 法 近似正态分布 标准化后易于比较 小样本时不可靠
注意:异常值不一定是“错误”。在欺诈检测、故障诊断等场景中,异常值可能是关键信号。建议先标记异常值,再结合业务判断是否剔除或单独建模。
异常值检测方法对比 异常值检测方法对比 3σ 原则 正态分布假设 均值 ± 3σ 为正常范围 超出范围视为异常 覆盖 99.7% 数据 ⚠ 对偏态分布不适用 IQR 法 无分布假设 Q1 - 1.5×IQR 为下界 Q3 + 1.5×IQR 为上界 稳健,抗极端值 ⚠ 小样本不稳定 Z-score 法 近似正态分布 |Z| > 3 视为异常 标准化后易比较 适合大数据量 ⚠ 对极端值敏感 建议:先可视化数据分布,再选择合适方法;可组合多种方法交叉验证

4.3 重复数据去重

重复数据指数据集中完全一致或部分关键字段相同的记录。重复数据会导致统计偏差、模型过拟合和存储浪费。去重是清洗流程中成本最低但收益最高的步骤。

4.3.1 完全重复去重

所有字段值完全相同的记录。使用 Pandas 的 duplicated() 和 drop_duplicates() 即可处理。

# 完全重复去重
df = pd.DataFrame({
    'id': [1, 2, 2, 3, 4],
    'name': ['A', 'B', 'B', 'C', 'D'],
    'value': [10, 20, 20, 30, 40]
})

# 检测重复
print(df.duplicated())  # 返回布尔序列

# 删除重复(保留第一个)
df_unique = df.drop_duplicates()
print(df_unique)

4.3.2 部分重复去重

基于指定字段判断重复。例如,同一用户 ID 出现多次但其他字段不同,可能只需保留最新记录。

# 基于 'id' 字段去重,保留最后一条记录
df_dedup = df.drop_duplicates(subset=['id'], keep='last')
print(df_dedup)

4.3.3 模糊匹配去重

当数据存在拼写差异、空格、大小写不一致时,需先标准化再匹配。常用方法包括:

  • 字符串标准化:去除空格、统一大小写、替换特殊字符。
  • 编辑距离(Levenshtein):计算字符串相似度,设定阈值判定重复。
  • 基于库的匹配:使用 fuzzywuzzy、recordlinkage 等库进行近似匹配。
# 字符串标准化示例
df['name_clean'] = df['name'].str.strip().str.lower()

# 模糊匹配(需安装 fuzzywuzzy)
# from fuzzywuzzy import fuzz
# df['similarity'] = df.apply(lambda row: fuzz.ratio(row['name'], row['name_clean']), axis=1)
去重最佳实践:
  1. 先做完全重复去重,再做部分重复去重。
  2. 定义去重字段时,优先选择业务唯一标识(如用户 ID、订单号)。
  3. 模糊匹配前先做字符串标准化,减少计算量。
  4. 保留去重日志,记录删除的记录数,便于审计。

4.4 本章小结

数据清洗是数据科学流程的基石。本章覆盖了三大核心任务:

  • 缺失值处理:根据缺失比例和数据类型选择删除、填充或插值。
  • 异常值检测:3σ 原则适用于正态分布,IQR 法更稳健,Z-score 法适合大数据量。
  • 重复数据去重:从完全重复到模糊匹配,逐步提升数据质量。