Scikit-learn 缺失值处理:SimpleImputer 完整指南

一、为什么需要缺失值处理

真实数据集普遍存在缺失值(NaN)。缺失值会导致:

  • 机器学习模型训练报错(大部分 estimator 不接受 NaN)
  • 统计分析结果偏差(缺失值不参与计算)
  • 数据质量下降(影响决策准确性)

处理策略对比:

策略适用类型优点缺点
删除法任意简单直接丢失数据量,小数据集不可用
均值/中位数填充数值型保留样本量引入偏差,扭曲分布
众数填充类别型保留样本量可能加剧类别不平衡
常数填充任意灵活可控需人工选择填充值
模型预测填充任意最精确计算成本高,实现复杂

SimpleImputer覆盖了均值、中位数、众数、常数四种策略,是工程实践中的首选方案。

二、数据准备

import pandas as pd import numpy as np # 构造含缺失值的测试数据集 data = { 'size': ['XL', 'L', 'M', np.nan, 'M', 'M'], # 类别型,有缺失 'color': ['red', 'green', 'blue', 'green', 'red', 'green'], 'gender': ['female', 'male', np.nan, 'female', 'female', 'male'], # 类别型,有缺失 'price': [199.0, 89.0, np.nan, 129.0, 79.0, 89.0], # 数值型,有缺失 'weight': [500, 450, 300, np.nan, 410, np.nan], # 数值型,有缺失 'bought': ['yes', 'no', 'yes', 'no', 'yes', 'no'] } df = pd.DataFrame(data)

数据概览:

索引sizecolorgenderpriceweightbought
0XLredfemale199.0500.0yes
1Lgreenmale89.0450.0no
2MblueNaNNaN300.0yes
3NaNgreenfemale129.0NaNno
4Mredfemale79.0410.0yes
5Mgreenmale89.0NaNno

缺失值检测:

# 统计每列缺失值数量 df.isnull().sum() # size 1 # color 0 # gender 1 # price 1 # weight 2 # bought 0

三、四种填充策略详解

策略 1:均值填充(mean)

适用场景:数值型特征,数据分布近似正态。

from sklearn.impute import SimpleImputer 创建填充器:strategy='mean' 使用列均值填充 imputer = SimpleImputer(missing_values=np.nan, strategy='mean') fit_transform 两步合一: fit() 计算 weight 列均值 = (500+450+300+410)/4 = 415.0 transform() 用 415.0 填充缺失位置 df[["weight"]] = imputer.fit_transform(df[["weight"]])

填充结果:第 3 行和第 5 行的 weight 被 415.0 填充。

查看填充统计值:

imputer.statistics_ # array([415.]) imputer.statistics_[0] array([415.]) imputer.statistics_[0] np.float64(415.0)

关键点:

  • 输入必须是二维结构df[["weight"]],不能是df["weight"](一维 Series)
  • 均值仅基于非缺失值计算
  • 如果列全为 NaN,fit_transform 会报错

策略 2:常数填充(constant)

适用场景:需要用特定业务含义值填充时,如用 0 表示"未测量"、用 "unknown" 表示"未知"。

# strategy='constant' + fill_value 指定填充值 imputer = SimpleImputer( missing_values=np.nan, strategy='constant', fill_value=99.0 # 用 99.0 填充 price 列缺失值 ) df[["price"]] = imputer.fit_transform(df[["price"]])

填充结果:第 2 行 price 被 99.0 填充。

imputer.statistics_ # array([99.0], dtype=object)

关键点:

  • fill_value可以是任意类型(数值、字符串)
  • 数值型用数值填充,类别型用字符串填充
  • statistics_返回dtype=object,与均值策略的float64不同

策略 3:众数填充(most_frequent)

适用场景:类别型特征,或数值型离散值。

# strategy='most_frequent' 使用列中出现次数最多的值 imputer = SimpleImputer(missing_values=np.nan, strategy='most_frequent') size 列中 'M' 出现 3 次(最多),NaN 被替换为 'M' df[["size"]] = imputer.fit_transform(df[["size"]])

填充结果:第 3 行 size 被 'M' 填充。

imputer.statistics_ # array(['M'], dtype=object)

关键点:

  • 如果有多个值出现次数相同,选第一个
  • 适用于类别型,也可用于数值型离散值
  • 可能加剧类别不平衡(已有 'M' 3 个,填充后变 4 个)

策略 4:批量填充字符串列

适用场景:一次性填充所有类别型列。

# 重建数据集 df = pd.DataFrame(data) 创建常量填充器,用 "empty" 填充 imputer = SimpleImputer( missing_values=np.nan, strategy='constant', fill_value="empty" ) 自动筛选所有 object 类型的列 columns = df.select_dtypes(include=['object']).columns 选中: size, color, gender, bought 批量填充 df.loc[:, columns] = imputer.fit_transform(df[columns])

填充结果:size 和 gender 列的 NaN 被替换为 "empty"。

索引sizecolorgenderpriceweightbought
0XLredfemale199.0500.0yes
1Lgreenmale89.0450.0no
2MblueemptyNaN300.0yes
3emptygreenfemale129.0NaNno
4Mredfemale79.0410.0yes
5Mgreenmale89.0NaNno

关键点:

  • select_dtypes(include=['object'])选中所有字符串列
  • df.loc[:, columns]确保 inplace 赋值不触发 SettingWithCopyWarning
  • 一次 fit_transform 处理多列,各列用同一个 fill_value

四、进阶技巧

技巧 1:按非缺失子集计算统计量

场景:需要基于某列的非缺失值计算其他列的统计量。

df = pd.DataFrame(data) 筛选 weight 非缺失的行,计算数值列均值 df[~df["weight"].isnull()].select_dtypes(include=['float']).mean() price 122.333333 weight 415.000000

拆解说明:

# 步骤 1: df["weight"].isnull() → 布尔序列,True 表示缺失 # [False, False, False, True, False, True] 步骤 2: ~ 取反,True 变 False,筛选非缺失行 [True, True, True, False, True, False] 步骤 3: select_dtypes(include=['float']) 选中 price 和 weight 列 步骤 4: .mean() 计算均值 price = (199+89+129+79+89)/5 = 122.33(注意 price 第2行也缺失,被排除) weight = (500+450+300+410)/4 = 415.0

技巧 2:fit 与 transform 分离

场景:训练集 fit,测试集用训练集的统计量 transform,避免数据泄露。

from sklearn.model_selection import train_test_split # 划分训练集和测试集 train_df, test_df = train_test_split(df, test_size=0.3, random_state=42) 训练集 fit:计算均值 imputer = SimpleImputer(strategy='mean') imputer.fit(train_df[["weight"]]) 测试集 transform:用训练集均值填充 避免测试集信息泄露到训练过程 test_df["weight"] = imputer.transform(test_df[["weight"]])

技巧 3:Pipeline 集成

场景:将缺失值填充与模型训练组合成流水线。

from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression 构建 Pipeline:先填充缺失值,再训练模型 pipeline = Pipeline([ ('imputer', SimpleImputer(strategy='mean')), # 第一步:均值填充 ('classifier', LogisticRegression()) # 第二步:训练模型 ]) 直接 fit/predict,填充过程自动执行 pipeline.fit(X_train, y_train) predictions = pipeline.predict(X_test)

五、策略选型决策树

缺失值类型? ├── 数值型 │ ├── 分布近似正态 → 均值填充(mean) │ ├── 有离群值 → 中位数填充(median) │ └── 离散值/业务含义明确 → 常数填充(constant) └── 类别型 ├── 分布均匀 → 众数填充(most_frequent) ├── 需要区分缺失与有效值 → 常数填充(constant,如 "unknown") └── 缺失比例高(>50%)→ 考虑删除该列

缺失比例阈值参考:

缺失比例推荐策略
< 5%均值/众数填充
5% - 30%模型预测填充或常数填充
> 50%考虑删除该列或转为二值指示特征

六、常见陷阱

陷阱 1:一维输入报错

# ❌ 错误:传入一维 Series imputer.fit_transform(df["weight"]) # ValueError: Expected 2D array, got 1D array instead ✅ 正确:传入二维 DataFrame imputer.fit_transform(df[["weight"]])

陷阱 2:fit_transform 混用导致数据泄露

# ❌ 错误:测试集重新 fit imputer.fit(test_df[["weight"]]) # 测试集信息泄露 # ✅ 正确:用训练集的统计量 imputer.fit(train_df[["weight"]]) imputer.transform(test_df[["weight"]])

陷阱 3:众数填充加剧类别不平衡

# size 列原始分布:M=3, L=1, XL=1 # 众数填充后:M=4, L=1, XL=1 # 'M' 占比从 50% 升至 67%

应对:缺失比例高时,改用常数填充并标记为独立类别。

陷阱 4:SettingWithCopyWarning

# ❌ 可能触发警告 df[columns] = imputer.fit_transform(df[columns]) ✅ 使用 loc 显式赋值 df.loc[:, columns] = imputer.fit_transform(df[columns])

七、速查表

SimpleImputer 参数速查

参数类型可选值默认值说明
missing_values

申明:本文主要内容来源于ant-exercises-sklearn: scikit-learn 编程练习 100例,是个人进行sklearn学习时的产物,如有不适、请告之。