3. 数据清洗实战:缺失值处理与异常值检测

数据清洗是特征工程中最基础也最关键的环节。原始数据通常包含缺失值、异常值、重复值和格式不一致等问题,若不加以处理,后续的模型训练和MRM验证将产生严重偏差。本章聚焦于缺失值处理和异常值检测与处理两大核心任务,提供从理论到代码的完整实战方案。

3.1 缺失值处理

缺失值是指数据集中某个或某些特征的值未被记录或丢失。处理缺失值前,需先分析缺失机制:完全随机缺失(MCAR)、随机缺失(MAR)或非随机缺失(MNAR)。不同机制影响填充方法的选择。

3.1.1 缺失值可视化与统计

在填充前,应通过统计和可视化手段了解缺失情况:

  • 缺失比例:计算每列缺失值占比,决定是否删除或填充。
  • 缺失模式:使用热力图或矩阵图观察缺失是否集中在某些样本或特征上。
  • 缺失相关性:检查缺失值之间是否存在关联。
经验法则:缺失比例 < 5% 可直接删除或简单填充;5%-20% 需谨慎选择填充方法;> 20% 应考虑特征是否保留或使用模型预测填充。

3.1.2 常用填充方法对比

方法 适用场景 优点 缺点
均值填充 数值型特征,数据近似正态分布 简单快速,保持均值不变 降低方差,扭曲分布形态
中位数填充 数值型特征,存在偏态或异常值 对异常值鲁棒,保持中位数 忽略特征间关系
众数填充 分类型特征 保持类别分布 可能引入偏差
模型预测填充 缺失值较多,特征间关系复杂 利用特征间关联,填充更准确 计算成本高,可能过拟合

3.1.3 均值/中位数/众数填充实战

以下代码展示使用Pandas进行简单填充:

import pandas as pd
import numpy as np

# 示例数据
df = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40, np.nan, 28],
    'salary': [50000, 60000, 55000, np.nan, 70000, 65000, np.nan],
    'gender': ['M', 'F', 'M', np.nan, 'F', 'M', 'F']
})

# 均值填充(数值型)
df['age_fill_mean'] = df['age'].fillna(df['age'].mean())

# 中位数填充(数值型,偏态数据更稳健)
df['salary_fill_median'] = df['salary'].fillna(df['salary'].median())

# 众数填充(分类型)
df['gender_fill_mode'] = df['gender'].fillna(df['gender'].mode()[0])

print(df[['age', 'age_fill_mean', 'salary', 'salary_fill_median', 'gender', 'gender_fill_mode']])

3.1.4 模型预测填充实战

模型预测填充将缺失值视为目标变量,利用其他完整特征建立预测模型。常用算法包括KNN、随机森林、线性回归等。

from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import KNNImputer

# 方法一:KNN填充(基于样本相似度)
imputer_knn = KNNImputer(n_neighbors=3)
df_knn_imputed = pd.DataFrame(
    imputer_knn.fit_transform(df[['age', 'salary']]),
    columns=['age', 'salary']
)

# 方法二:迭代随机森林填充(IterativeImputer)
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

imputer_rf = IterativeImputer(
    estimator=RandomForestRegressor(n_estimators=100, random_state=42),
    max_iter=10,
    random_state=42
)
df_rf_imputed = pd.DataFrame(
    imputer_rf.fit_transform(df[['age', 'salary']]),
    columns=['age', 'salary']
)

print("KNN填充结果:\n", df_knn_imputed)
print("\n随机森林迭代填充结果:\n", df_rf_imputed)
实战建议:在MRM验证场景中,模型预测填充通常优于简单填充,但需注意避免数据泄露——填充模型应在训练集上拟合,再转换验证集和测试集。
缺失值处理流程决策图 原始数据含缺失值 缺失比例 < 5% ? 是 删除或简单填充 否 需更复杂填充方法 特征类型:数值型 / 分类型 选择填充方法 → 完成缺失值处理

3.2 异常值检测与处理

异常值(Outlier)是指显著偏离其他观测值的样本点。异常值可能由测量误差、数据录入错误或真实稀有事件引起。在MRM验证中,异常值会严重影响模型稳定性和泛化能力。

3.2.1 异常值检测方法对比

方法 原理 适用分布 阈值 优缺点
Z-score 基于标准差,衡量数据点与均值的偏离程度 近似正态分布 |Z| > 3 通常视为异常 简单直观,但对偏态数据敏感
IQR(四分位距) 基于分位数,不受极端值影响 任意分布(偏态稳健) Q1 - 1.5×IQR 或 Q3 + 1.5×IQR 对偏态数据鲁棒,不依赖正态假设
孤立森林 基于树模型,异常点更容易被孤立 高维数据 异常分数阈值 适合高维和大规模数据
DBSCAN 基于密度聚类,低密度区域为异常 非球形分布 邻域半径和最小点数 可发现任意形状异常簇

3.2.2 Z-score 方法实战

from scipy import stats
import numpy as np

# 生成示例数据(含异常值)
np.random.seed(42)
data = np.random.normal(loc=50, scale=10, size=100)
data = np.append(data, [120, 5, 130])  # 人为添加异常值

# 计算Z-score
z_scores = np.abs(stats.zscore(data))
threshold = 3
outliers_z = np.where(z_scores > threshold)[0]

print(f"Z-score方法检测到 {len(outliers_z)} 个异常值")
print(f"异常值索引: {outliers_z}")
print(f"异常值数据: {data[outliers_z]}")

3.2.3 IQR 方法实战

# IQR方法检测异常值
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_iqr = np.where((data < lower_bound) | (data > upper_bound))[0]

print(f"IQR方法检测到 {len(outliers_iqr)} 个异常值")
print(f"异常值索引: {outliers_iqr}")
print(f"异常值数据: {data[outliers_iqr]}")
print(f"下界: {lower_bound:.2f}, 上界: {upper_bound:.2f}")
注意:Z-score 对极端值敏感,若数据本身包含多个异常值,均值和标准差会被“拉偏”,导致部分异常值无法被检测(掩蔽效应)。此时 IQR 方法更为稳健。

3.2.4 异常值处理策略

  • 删除:当异常值比例很小(< 1%)且确认由错误引起时,直接删除。
  • 截尾(Winsorization):将异常值替换为边界值(如上下1%分位数)。
  • 视为缺失值:将异常值标记为缺失,再用缺失值处理方法填充。
  • 单独建模:当异常值代表重要稀有事件时,可单独建模或使用对异常值鲁棒的算法。
# 截尾处理示例
def winsorize_series(series, limits=(0.01, 0.01)):
    """对序列进行截尾处理"""
    lower = series.quantile(limits[0])
    upper = series.quantile(1 - limits[1])
    return series.clip(lower, upper)

# 应用截尾
data_clean = winsorize_series(pd.Series(data), limits=(0.05, 0.05))
print("截尾后数据统计:")
print(f"均值: {data_clean.mean():.2f}, 标准差: {data_clean.std():.2f}")
异常值检测方法对比 Z-score 方法 基于正态分布假设 Z = (x - μ) / σ 阈值:|Z| > 3 适合:近似正态分布数据 ⚠ 对偏态数据敏感 IQR 方法 基于分位数,无分布假设 IQR = Q3 - Q1 阈值:Q1-1.5×IQR / Q3+1.5×IQR 适合:偏态、非正态分布数据 ✓ 对异常值更鲁棒 决策建议 数据近似正态 → 优先Z-score | 数据偏态或未知分布 → 优先IQR | 高维数据 → 孤立森林 异常值处理策略 删除(确认错误) | 截尾(替换为边界值) | 视为缺失值(再填充) | 单独建模(稀有事件) 💡 MRM验证中建议先截尾或视为缺失值,保留样本量

3.3 缺失值与异常值协同处理流程

在实际项目中,缺失值和异常值往往同时存在,且相互影响。推荐的处理顺序为:

  1. 初步探索:统计缺失比例、异常值比例,可视化分布。
  2. 异常值检测:先使用IQR或Z-score标记异常值(不立即处理)。
  3. 缺失值填充:使用模型预测填充或简单填充,注意异常值可能影响填充模型。
  4. 异常值处理:对填充后的数据再次检测异常值,进行截尾或替换。
  5. 验证:对比处理前后的分布变化,确保不引入系统性偏差。
MRM验证协同要点:在MRM(多模态回归模型)验证中,缺失值和异常值处理应在训练集上定义规则(如填充值、截尾边界),并一致地应用到验证集和测试集,避免数据泄露。

3.4 本章小结

  • 缺失值处理需根据缺失比例、特征类型和数据分布选择合适方法,模型预测填充在复杂场景下效果更优。
  • 异常值检测中,IQR方法对偏态数据更稳健,Z-score适用于正态分布数据。
  • 异常值处理策略包括删除、截尾、视为缺失值和单独建模,推荐优先使用截尾或视为缺失值以保留样本量。
  • 缺失值与异常值应协同处理,遵循“先检测异常值→填充缺失值→再处理异常值”的流程。
  • 所有处理规则必须在训练集上拟合,并一致应用到验证集和测试集,确保MRM验证的公平性。