4. 数据清洗基础:缺失值处理策略与异常值检测
数据清洗是特征工程中最基础也最关键的环节。原始数据几乎总是包含缺失值、异常值或噪声,这些瑕疵会严重干扰自动机器学习(AutoML)对照实验的公平性与模型泛化能力。本章聚焦于缺失值处理与异常值检测两大核心任务,提供可复用的策略与代码模板。
4.1 缺失值处理策略
缺失值产生的原因包括传感器故障、用户未填写、数据合并错位等。处理策略可分为三类:删除、统计填充与模型预测填充。选择哪种策略取决于缺失比例、数据分布与业务含义。
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 删除(行/列) | 缺失比例 < 5%,且缺失随机 | 简单、无偏差引入 | 丢失信息,样本量减少 |
| 均值/中位数填充 | 数值型特征,缺失比例 5%~20% | 计算快,保持样本量 | 降低方差,可能扭曲分布 |
| 众数填充 | 分类型特征 | 保留类别分布 | 可能引入偏差 |
| 模型预测填充 | 缺失比例 > 20%,特征间相关性强 | 利用特征关系,填充更准确 | 计算成本高,可能过拟合 |
对照实验提示: 在 AutoML 对照实验中,建议将缺失值处理作为预处理管道的一部分,而非在实验前单独处理。这样可以确保不同实验组使用相同的填充逻辑,避免因填充方式不同导致模型性能差异无法归因。
4.1.1 删除策略
当缺失值比例极低(通常 < 5%)且缺失机制为完全随机缺失(MCAR)时,直接删除缺失样本是最安全的选择。删除列(特征)适用于该特征缺失比例超过 70% 且无重要业务含义的情况。
import pandas as pd
import numpy as np
# 示例数据
df = pd.DataFrame({
'feature_a': [1, 2, np.nan, 4, 5],
'feature_b': [10, np.nan, 30, 40, 50],
'target': [0, 1, 0, 1, 0]
})
# 删除包含缺失值的行
df_drop_row = df.dropna()
# 删除缺失比例超过 70% 的列
threshold = 0.7
df_drop_col = df.loc[:, df.isnull().mean() < threshold]
print(f"原始行数: {len(df)}, 删除后行数: {len(df_drop_row)}")
4.1.2 均值/中位数填充
均值填充适用于近似正态分布的数据;中位数填充对异常值更鲁棒,适用于偏态分布。填充后建议添加“缺失指示列”以保留缺失模式信息。
from sklearn.impute import SimpleImputer
# 均值填充
imputer_mean = SimpleImputer(strategy='mean')
df['feature_a_filled'] = imputer_mean.fit_transform(df[['feature_a']])
# 中位数填充
imputer_median = SimpleImputer(strategy='median')
df['feature_b_filled'] = imputer_median.fit_transform(df[['feature_b']])
# 添加缺失指示列
df['feature_a_missing'] = df['feature_a'].isnull().astype(int)
df['feature_b_missing'] = df['feature_b'].isnull().astype(int)
print(df)
4.1.3 模型预测填充
当缺失值比例较高且特征间存在较强相关性时,使用其他特征作为输入,以缺失特征作为目标进行预测填充。常用模型包括 KNN、线性回归、随机森林等。这种方法能更好地保留特征间的联合分布。
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import KNNImputer
# 方法一:KNN 填充(基于样本相似度)
knn_imputer = KNNImputer(n_neighbors=3)
df_knn_filled = pd.DataFrame(
knn_imputer.fit_transform(df[['feature_a', 'feature_b']]),
columns=['feature_a', 'feature_b']
)
# 方法二:迭代模型填充(以 feature_a 为例)
# 将数据分为有缺失和无缺失两部分
df_train = df[df['feature_a'].notna()]
df_missing = df[df['feature_a'].isna()]
X_train = df_train[['feature_b']]
y_train = df_train['feature_a']
X_missing = df_missing[['feature_b']]
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
predicted_values = model.predict(X_missing)
df.loc[df['feature_a'].isna(), 'feature_a'] = predicted_values
注意: 模型预测填充在对照实验中容易引入数据泄露。务必在训练集上拟合填充模型,再转换验证集和测试集。使用
sklearn.pipeline.Pipeline 可以自动保证这一流程。
4.2 异常值检测与处理
异常值(Outliers)是指显著偏离数据集中其他观测值的样本。异常值可能由测量误差、数据录入错误或真实的稀有事件产生。在 AutoML 对照实验中,异常值会扭曲模型训练,尤其对线性模型、KNN 和神经网络影响显著。
4.2.1 异常值检测方法
| 方法 | 原理 | 适用场景 | 参数 |
|---|---|---|---|
| Z-Score | 基于均值和标准差,假设正态分布 | 单变量、近似正态分布 | 阈值通常 2~4 |
| IQR(四分位距) | 基于 Q1 和 Q3,不依赖分布假设 | 单变量、偏态分布 | 系数通常 1.5~3 |
| Isolation Forest | 随机划分,异常点更容易被孤立 | 多变量、高维数据 | contamination 参数 |
| DBSCAN | 基于密度聚类,低密度点为异常 | 多变量、非球形簇 | eps, min_samples |
4.2.2 异常值处理方法
- 删除异常样本:当异常值明显由错误产生且数量较少时。
- 截尾(Capping/Winsorizing):将异常值替换为阈值(如 1% 和 99% 分位数)。
- 转换:对特征进行对数、Box-Cox 或 Yeo-Johnson 变换,压缩极端值影响。
- 分箱:将连续特征离散化,消除异常值对距离度量的影响。
import numpy as np
import pandas as pd
from scipy import stats
# 示例数据
np.random.seed(42)
data = np.random.normal(50, 10, 100)
data = np.append(data, [120, 130, 5, 3]) # 加入异常值
df = pd.DataFrame({'feature': data})
# 方法一:Z-Score 检测
z_scores = np.abs(stats.zscore(df['feature']))
outliers_z = df[z_scores > 3]
print(f"Z-Score 检测到 {len(outliers_z)} 个异常值")
# 方法二:IQR 检测
Q1 = df['feature'].quantile(0.25)
Q3 = df['feature'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = df[(df['feature'] < lower_bound) | (df['feature'] > upper_bound)]
print(f"IQR 检测到 {len(outliers_iqr)} 个异常值")
# 截尾处理(Winsorizing)
from scipy.stats.mstats import winsorize
df['feature_winsorized'] = winsorize(df['feature'], limits=[0.01, 0.01])
# 对数变换
df['feature_log'] = np.log1p(df['feature'] - df['feature'].min() + 1)
对照实验关键点: 异常值检测的阈值(如 Z-Score 的 3 或 IQR 的 1.5)应在实验设计阶段固定,并在所有实验组中保持一致。建议在验证集上评估异常值处理对模型性能的影响,避免过度清洗导致信息丢失。
4.3 数据清洗在 AutoML 对照实验中的实践建议
- 管道化:将缺失值填充和异常值处理封装在
sklearn.pipeline.Pipeline中,确保训练/验证/测试集使用完全相同的转换逻辑。 - 记录转换参数:保存填充值(均值、中位数、模型参数)和异常值阈值,便于实验复现。
- 对比实验设计:设置“无清洗”、“仅缺失值处理”、“仅异常值处理”、“完整清洗”四个对照组,量化每个步骤对模型性能的贡献。
- 避免数据泄露:填充模型和异常值检测的拟合过程必须仅在训练集上执行,再应用到验证集和测试集。
效率技巧: 使用
sklearn.compose.ColumnTransformer 可以对不同特征列应用不同的填充策略(如数值列用中位数、分类列用众数),实现精细化清洗。