机器学习特征工程中的缺失值处理方法

FreeGuideOnline 9阅读 2026-07-08

机器学习特征工程:缺失值处理方法完全指南

概述

在真实世界的数据集中,缺失值是普遍存在的现象。如何妥善处理缺失值,是特征工程中最基础也最关键的环节之一。不当的处理可能引入偏差、丢失信息甚至导致模型性能显著下降。本教程将从零开始,系统地讲解缺失值处理的核心方法论与 Python 实践,帮助你建立完整、实用的知识体系。

理解缺失值

缺失值的产生机制

在动手处理之前,必须先理解数据“为何缺失”。根据缺失机制,我们可以将缺失分为三类:

  • 完全随机缺失 (MCAR):缺失的发生与数据本身完全无关。例如,实验设备随机故障导致某次测量未记录。这种情况下,直接删除缺失样本通常不会引入明显偏差。
  • 随机缺失 (MAR):缺失的发生与数据中其他已观测到的变量有关,但与缺失值本身无关。例如,男性更不愿意填写收入调查中的“收入”字段,但收入缺失的概率仅依赖于已观测的“性别”,而非收入值本身。
  • 非随机缺失 (MNAR):缺失的发生与缺失值本身有关。例如,高收入人群更倾向于隐藏自己的收入,此时缺失值与收入的高低直接相关。这类情况最为复杂,需要谨慎处理。

理解缺失机制有助于选择更合理的处理方法。

识别缺失值

在 Python 中,我们使用 pandas 快速发现缺失值。常见的缺失标记包括 NoneNaN、空字符串等。

import pandas as pd
import numpy as np

# 示例数据
df = pd.DataFrame({
    'age': [25, np.nan, 35, 40, np.nan],
    'income': [50000, 60000, np.nan, 80000, 92000],
    'gender': ['M', 'F', None, 'F', 'M']
})

# 查看每个特征的缺失数量
print(df.isnull().sum())

# 全局缺失比例
print(df.isnull().sum() / len(df))

缺失值处理方法分类

处理缺失值的方法大致可分为以下四类:

  1. 删除法:直接移除含缺失值的样本或特征。
  2. 单变量填充法:利用该特征自身的统计量(均值、中位数、众数等)进行填充。
  3. 多变量填充法:利用其他特征的信息预测缺失值(KNN、回归、多重插补等)。
  4. 标记与模型融合:保留缺失状态作为新特征,结合填充值共同进入模型。

下面逐一展开。

删除法

删除含缺失值的样本

  • 操作df.dropna()df.dropna(axis=0)
  • 适用场景:缺失比例极小(如 < 5%),且缺失机制为 MCAR,删除后样本量仍足够充裕。
  • 风险:若缺失非完全随机,会导致样本分布偏差;大量删除会损失宝贵信息。

删除含缺失值的特征

  • 操作df.dropna(axis=1)
  • 适用场景:某一特征的缺失比例过高(如 > 50%),且该特征对模型贡献有限。
  • 风险:可能丢弃有潜在价值的特征。
# 删除任何包含缺失值的行
df_dropped_rows = df.dropna()

# 删除缺失值超过一定阈值的列
threshold = len(df) * 0.5  # 保留缺失比例不超过50%的列
df_cleaned = df.dropna(axis=1, thresh=threshold)

单变量填充法

简单统计填充

利用特征的集中趋势进行填充:

  • 均值填充:适用于近似正态分布的连续特征。
  • 中位数填充:适用于存在离群值的连续特征,更具稳健性。
  • 众数填充:适用于离散/分类特征。
from sklearn.impute import SimpleImputer

# 均值填充数值特征
imputer_mean = SimpleImputer(strategy='mean')
df['age'] = imputer_mean.fit_transform(df[['age']])

# 中位数填充
imputer_median = SimpleImputer(strategy='median')
df['income'] = imputer_median.fit_transform(df[['income']])

# 众数填充分类特征(需转换为数值型)
imputer_mode = SimpleImputer(strategy='most_frequent')
df['gender'] = imputer_mode.fit_transform(df[['gender']])

常数值填充

使用自定义的常数(如 -1"unknown")填充所有缺失值。这种方法可以显式标记缺失状态,但可能引入错误默认值。

df['age'].fillna(-1, inplace=True)

多变量填充法

KNN 填充

基于 K 近邻算法,用与缺失样本最相似的 K 个邻居的加权平均来估计缺失值。该方法充分利用了特征间的关系,但计算量较大,且需要对数据进行标准化。

from sklearn.impute import KNNImputer
from sklearn.preprocessing import StandardScaler

# 假设 df 只包含数值特征
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df)

imputer_knn = KNNImputer(n_neighbors=5)
df_imputed = imputer_knn.fit_transform(df_scaled)
# 记得逆标准化以便解释
df_imputed = scaler.inverse_transform(df_imputed)

插值法

用于时间序列或有序数据。通过相邻观测值进行线性、多项式或样条插值。

# 假设 df 具有时间索引且按时间排序
df['sensor'] = df['sensor'].interpolate(method='linear', limit_direction='both')

多重插补

多重插补是处理复杂缺失(尤其是 MNAR)的黄金标准。它通过多次迭代创建多组完整数据,分析时综合考虑填充的不确定性。IterativeImputer 是 sklearn 提供的便捷实现。

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

imputer_iter = IterativeImputer(max_iter=10, random_state=0)
df_imputed_iter = imputer_iter.fit_transform(df)

标记缺失与模型融合

有时缺失本身也含有信息。我们可以创建一个新的二值特征来标记原特征是否缺失,然后对原特征进行填充(如均值填充)。这样既保留了“缺失”这个信息,又保证了特征完整。

# 为 'age' 创建缺失指示器
df['age_missing'] = df['age'].isnull().astype(int)

# 对原始特征进行均值填充
df['age'].fillna(df['age'].mean(), inplace=True)

在一些模型(如 XGBoost、LightGBM)中,甚至可以不做填充,由模型自动处理缺失值。但进行标记后填充的方式对传统模型(如线性模型、N N)更为友好。

方法选择的核心原则

方法 优点 缺点 适用场景
删除 简单、不引入人工偏见 损失信息,易偏差 缺失极少、MCAR
均值/中位数 简单、计算快 扭曲分布,低方方差 快速基线、数据量大
KNN 利用特征关系,精度较高 计算开销大,需标准化 特征相关性较强
多重插补 统计合理,处理不确定性 调试复杂,计算慢 严谨分析、高价值决策
标记缺失 保留缺失状态信息 增加特征数量 缺失本身具有意义

实战建议:始终保留一份原始数据,在一个科学划分的训练/测试集上验证不同处理方法对模型性能的影响,选择交叉验证得分最优的策略。避免在未见过的数据上使用填充统计量计算时发生数据泄露——填充所用的均值、中位数等必须从训练集计算,并应用到测试集。

完整流程示例

以下是一个端到端的处理流水线,使用 Pipeline 确保填充逻辑不与测试数据泄露。

from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

# 假定 X_train, X_test, y_train, y_test 已划分
num_cols = ['age', 'income']
cat_cols = ['gender']

# 数值特征流水线
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    # 可继续添加标准化等步骤
])

# 分类特征流水线
cat_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    # 可继续添加编码步骤
])

from sklearn.compose import ColumnTransformer
preprocessor = ColumnTransformer([
    ('num', num_pipeline, num_cols),
    ('cat', cat_pipeline, cat_cols)
])

# 完整模型流水线
model = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier())
])

# 训练与评估
model.fit(X_train, y_train)
print(model.score(X_test, y_test))

总结

缺失值处理并非“补上就完事”,而是一个需要结合数据理解、业务场景和模型特性的系统工程。从简单的删除与统计填充,到复杂的多重插补与缺失标记,没有银弹。核心原则是:不要盲目填充,先分析缺失机制;在交叉验证指导下选择方法;始终防范数据泄露。掌握这些方法后,你的特征工程能力将向上迈出扎实一步。