【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

前言

本文聚焦数据预处理的关键环节——缺失值处理。如果处理不当,缺失数据会显著降低模型的准确性和可靠性。本文将介绍多种插补方法,并展示如何将其嵌入数据处理流水线中。

通过这一方式,我们能够重新纳入先前被排除的特征,进一步提升预测精度,充分挖掘数据集的价值。

使用 SimpleImputer 重构手动插补

在本文第一部分中,我们将重温并重构手动插补方法,并借助 SimpleImputer 实现自动化。

此前在 Ames Housing 数据集的探索中,我们基于数据字典设计了缺失值处理策略。例如,某些分类变量的缺失往往意味着特征不存在(如缺失的 PoolQC 代表没有游泳池),因此我们以 “None” 填补以保持数据集完整性。而对于数值型变量,则采用均值插补等方式。

通过 scikit-learn 的 SimpleImputer 自动化这些步骤,可以提升复现性和效率。结合流水线方法,我们不仅能实现插补,还能完成缩放与编码,为后续基于 Lasso、Ridge、ElasticNet 的回归分析做好准备:

# 导入必要库
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, FunctionTransformer
from sklearn.linear_model import Lasso, Ridge, ElasticNet
from sklearn.model_selection import cross_val_score

# 读取数据集
Ames = pd.read_csv('Ames.csv')

# 特征划分(去掉 PID 和 SalePrice),并单独处理 Electrical 列
numeric_features = Ames.select_dtypes(include=['int64', 'float64']).drop(columns=['PID', 'SalePrice']).columns
categorical_features = Ames.select_dtypes(include=['object']).columns.difference(['Electrical'])
electrical_feature = ['Electrical']  # 单独处理 Electrical 列

# 填充 None 的辅助函数
def fill_none(X):
    return X.fillna("None")

# 数值特征流水线:均值插补 + 标准化
numeric_transformer = Pipeline(steps=[
    ('impute_mean', SimpleImputer(strategy='mean')),
    ('scaler', StandardScaler())
])

# 一般分类特征流水线:填充 None + 独热编码
categorical_transformer = Pipeline(steps=[
    ('fill_none', FunctionTransformer(fill_none, validate=False)),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# Electrical 专用流水线:众数插补 + 独热编码
electrical_transformer = Pipeline(steps=[
    ('impute_electrical', SimpleImputer(strategy='most_frequent')),
    ('onehot_electrical', OneHotEncoder(handle_unknown='ignore'))
])

# 合并预处理器
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features),
        ('electrical', electrical_transformer, electrical_feature)
    ])

# 目标变量
y = Ames['SalePrice']

# 所有特征
X = Ames[numeric_features.tolist() + categorical_features.tolist() + electrical_feature]

# 模型流水线
models = {
    'Lasso': Lasso(max_iter=20000),
    'Ridge': Ridge(),
    'ElasticNet': ElasticNet()
}

results = {}
for name, model in models.items():
    pipeline = Pipeline(steps=[
        ('preprocessor', preprocessor),
        ('regressor', model)
    ])
    # 交叉验证
    scores = cross_val_score(pipeline, X, y)
    results[name] = round(scores.mean(), 4)

# 输出结果
print("Cross-validation scores with Simple Imputer:", results)

运行结果如下,展示了简单插补对模型精度的影响,并为后续更复杂方法提供了基准:

Cross-validation scores with Simple Imputer: {'Lasso': 0.9138, 'Ridge': 0.9134, 'ElasticNet': 0.8752}

将手动处理转为基于 scikit-learn 流水线的实现,优势主要体现在:

  • 效率与错误减少:
    手动插补耗时且易出错,尤其在数据复杂度提高时。流水线能自动化并确保一致性,减少人为失误。

  • 复用与集成:
    手动方法可复用性低,而流水线能完整封装预处理与建模步骤,方便重复使用并无缝集成训练流程。

  • 防止数据泄漏:
    手动插补可能在计算过程中泄漏测试集信息,而流水线采用 fit/transform 流程,确保仅基于训练集进行计算。

这一基于 SimpleImputer 的框架展示了灵活的预处理方式,可轻松扩展为其他插补策略。在接下来的部分,我们将继续探讨更多方法,并比较它们对模型表现的影响。

使用 IterativeImputer 提升插补效果

在本文第二部分中,我们将尝试更为先进的 IterativeImputer 方法。

与简单的均值或中位数插补不同,IterativeImputer 将含缺失值的每个特征视作回归中的因变量,并利用其他特征进行建模,采用循环迭代的方式逐步优化缺失值估计。

通过多轮迭代,它能够利用所有特征间的交互信息,捕捉更细微的数据模式和依赖关系,从而可能得到比简单方法更合理的结果:

from sklearn.experimental import enable_iterative_imputer  # 必须先启用
from sklearn.impute import IterativeImputer
...
# 数值特征流水线:迭代插补 + 标准化
numeric_transformer_advanced = Pipeline(steps=[
    ('impute_iterative', IterativeImputer(random_state=42)),
    ('scaler', StandardScaler())
])
...
print("Cross-validation scores with Iterative Imputer:", results_advanced)

输出结果:

Cross-validation scores with Iterative Imputer: {'Lasso': 0.9142, 'Ridge': 0.9135, 'ElasticNet': 0.8746}

与 SimpleImputer 相比,提升有限。这说明数据集的复杂依赖关系并不总能带来明显收益,但 IterativeImputer 的确能细化缺失值估计。

利用 KNNImputer 的邻域信息

在最后一部分中,我们测试了 KNNImputer。该方法基于特征空间中最近的 k 个邻居,使用邻居的均值填补缺失值。其假设是:相似的数据点在特征或响应上也相似。因此,在数据具有“局部相似性”结构时,KNN 插补尤其有效:

from sklearn.impute import KNNImputer
...
# 数值特征流水线:KNN 插补 + 标准化
numeric_transformer_knn = Pipeline(steps=[
    ('impute_knn', KNNImputer(n_neighbors=5)),
    ('scaler', StandardScaler())
])
...
print("Cross-validation scores with KNN Imputer:", results_knn)

输出结果:

Cross-validation scores with KNN Imputer: {'Lasso': 0.9146, 'Ridge': 0.9138, 'ElasticNet': 0.8748}

提升幅度依旧有限,但相比 IterativeImputer 更接近 SimpleImputer 的表现。其基于邻近点的特性在某些数据集上可能更好地保留了内在结构。

总结

本文从手动插补出发,逐步过渡到基于流水线的自动化插补,并依次比较了三种方法:

1.SimpleImputer:复现并自动化手动处理,建立基准。
2.IterativeImputer:通过回归迭代细化插补结果,但提升有限。
3.KNNImputer:利用邻域相似性补全缺失值,效果与迭代插补相当。

整体结果表明,更复杂的插补方法并不总能显著优于简单方法。其有效性取决于数据集的特征结构与缺失机制。在实际应用中,应结合任务需求和数据特性选择合适的插补方案。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐