scikit-learn 缺失值插补完全指南:SimpleImputer / IterativeImputer / KNNImputer 与 MissingIndicator 实战与原理
2026/9/18 23:12:21 网站建设 项目流程

scikit-learn 缺失值插补完全指南:SimpleImputer / IterativeImputer / KNNImputer 与 MissingIndicator 实战与原理

【免费下载链接】scikit-learnscikit-learn: machine learning in Python项目地址: https://gitcode.com/gh_mirrors/sc/scikit-learn

本文基于 scikit-learn 官方用户指南 doc/modules/impute.rst 编写,并结合作者所在仓库(sklearn/impute/目录)的源码实现、测试用例与示例进行深度佐证。读完本文,你将掌握:为什么不该轻易丢弃含缺失值的样本、三种内置插补器(单变量的SimpleImputer、多变量的IterativeImputerKNNImputer)各自的工作原理与完整参数含义、如何用MissingIndicator/add_indicator保留"缺失模式"信息、如何保持特征数量不变(keep_empty_features),以及哪些估算器可以原生处理 NaN 而完全无需插补。

缺失值问题与处理的基本原则

现实世界中的数据集几乎必然包含缺失值,它们可能以空值、NaN或其他占位符的形式出现,来源包括设备测量故障、问卷未作答、或某些信息从未被记录。缺失值既可能出现在特征矩阵X中,也可能出现在目标y中。scikit-learn 绝大多数估算器都假设数组中的每个元素都是数值且有实际含义,因此无法直接在不完整的数据上训练

处理缺失值最朴素的想法是直接丢弃包含缺失值的行或列,但这种做法在两方面是有害的:

  • 丢失信息:这些样本中其他完整的特征信息被一并丢弃;
  • 引入偏差:剩余样本很少能代表原始总体,除非缺失是完全随机的(MCAR,Missing Completely At Random)。

同样的告诫也适用于目标变量y:静默丢弃结果未知的样本同样会使分析产生偏差。特别是当某个结果尚未被观测到时,这属于删失(censoring)问题,应当使用生存分析(survival analysis)领域的专门方法处理,而不是简单地删除数据。

官方指南在文首给出了三个关键要点:

  1. 避免丢弃含缺失值的行,否则有引入偏差的风险;
  2. 部分监督学习方法(通常是基于树的模型)可以原生处理含缺失值的数据,无需任何额外成本即可取得不错的效果(见下文"支持 NaN 的估算器"一节);
  3. 插补可能计算成本很高,且对后续预测性能的提升很快会触及收益递减(diminishing returns)。

预测还是重建:插补投入的取舍

用户指南(引用 Le Morvan 与 Varoquaux 的研究)强调了一个容易被忽视的结论:只有当你以"重建数据本身"为目标时,才值得在插补质量上大举投入;如果目标是预测,复杂插补带来的收益往往很有限。为此,官方给出了几条高层选择建议:

  • 从简单开始:用SimpleImputer做常数 / 均值 / 众数插补是强大且廉价的基线,更精细的插补通常只会带来边际的预测性能提升;
  • 标记缺失条目:增加缺失指示器(imputer 的add_indicator选项,或独立的MissingIndicator)往往有助于预测,即使缺失是完全随机的;
  • 在监督学习环节优先选择表达能力强的模型:灵活的估算器从复杂插补中获益更少,有些甚至能原生处理缺失值而完全不需要插补;
  • 复杂插补可能帮助预测,但计算开销很大(随数据规模扩展性差);
  • 主要把插补质量投入在"重建数据"这一目标上

单变量插补与多变量插补

按算法类型,插补可分为两大类:

  • 单变量(Univariate)插补:仅使用第 i 个特征维度的非缺失值来填补该维度的缺失值,代表是SimpleImputer
  • 多变量(Multivariate)插补:利用所有可用特征维度来估计缺失值,代表是IterativeImputerKNNImputer

SimpleImputer:单变量特征插补

SimpleImputer提供最基本的插补策略:可以用给定的常数值,也可以用缺失值所在**每一列的描述性统计量(均值、中位数或众数)**来填充。该类还允许指定不同的缺失值编码(missing_values参数)。

参数速查

从 sklearn/impute/_base.py 中SimpleImputer类的定义(约 L171 起)与_parameter_constraints可以确认如下参数:

参数默认值说明
missing_valuesnp.nan缺失值占位符,可以是 int、float、str、np.nanNonepd.NA;所有等于该值的位置都会被插补。pandas 可空整数类型(nullable integer dtype)的数据可设np.nanpd.NA
strategy'mean'插补策略:'mean'/'median'仅限数值数据;'most_frequent'支持字符串或数值(并列时返回最小者);'constant'使用fill_value1.5 版本起还支持传入 Callable,对该列非缺失值的稠密一维数组运行并返回标量统计量作为填充值
fill_valueNonestrategy='constant'时使用;None时数值数据默认填0,字符串/object 数据默认填"missing_value"
copyTrue是否复制XFalse时尽量原地插补。注意:非浮点数组、CSR 矩阵、或add_indicator=True时仍会强制复制
add_indicatorFalseTrue时把MissingIndicator的输出堆叠到插补结果之后(见"标记插补值"一节)
keep_empty_featuresFalseTrue时保留拟合阶段全是缺失值的列(1.2 版本新增),见"保持特征数量不变"一节

用均值填充缺失值

下面的示例演示了如何用包含缺失值的列(axis 0)的均值替换以np.nan编码的缺失值:

>>> import numpy as np >>> from sklearn.impute import SimpleImputer >>> imp = SimpleImputer(missing_values=np.nan, strategy='mean') >>> imp.fit([[1, 2], [np.nan, 3], [7, 6]]) SimpleImputer() >>> X = [[np.nan, 2], [6, np.nan], [7, 6]] >>> print(imp.transform(X)) [[4. 2. ] [6. 3.666] [7. 6. ]]

从源码看,fit阶段会把每列统计量存入statistics_属性(sklearn/impute/_base.py 中_dense_fit使用np.ma.mean/np.ma.median等掩码数组运算),transform阶段再按掩码把缺失位置替换为对应统计量;statistics_中出现np.nan的特征会在 transform 时被丢弃并给出警告。

支持稀疏矩阵

SimpleImputer也支持稀疏矩阵输入:

>>> import scipy.sparse as sp >>> X = sp.csc_array([[1, 2], [0, -1], [8, 4]]) >>> imp = SimpleImputer(missing_values=-1, strategy='mean') >>> imp.fit(X) SimpleImputer(missing_values=-1) >>> X_test = sp.csc_array([[-1, 2], [6, -1], [7, 6]]) >>> print(imp.transform(X_test).toarray()) [[3. 2.] [6. 3.] [7. 6.]]

需要特别注意的是:这种稀疏格式不应用于隐式存储缺失值,否则会在 transform 时被迫稠密化。如果缺失值以 0 编码,必须使用稠密输入——源码_validate_input中明确检查了"稀疏数据 +missing_values == 0"的组合并抛出ValueError,因为这会强制稠密化(sklearn/impute/_base.py L392-L399)。

支持分类数据

当使用'most_frequent''constant'策略时,SimpleImputer还支持以字符串或 pandas categorical 表示的分类数据:

>>> import pandas as pd >>> df = pd.DataFrame([["a", "x"], ... [np.nan, "y"], ... ["a", np.nan], ... ["b", "y"]], dtype="category") ... >>> imp = SimpleImputer(strategy="most_frequent") >>> print(imp.fit_transform(df)) [['a' 'x'] ['a' 'y'] ['a' 'y'] ['b' 'y']]

注意:'mean'/'median'策略配合非数值数据会报错(源码会将"could not convert"类错误改写为更明确的提示Cannot use {strategy} strategy with non-numeric data)。完整示例可参考 examples/impute/plot_missing_values.py,测试覆盖见 sklearn/impute/tests/test_impute.py。

IterativeImputer:多变量特征插补

更复杂的做法是使用IterativeImputer:它把每个含缺失值的特征建模为其他特征的函数,并利用该模型进行估计。整个过程以**循环轮转(round-robin)**方式迭代:

  1. 每一步指定一个特征列作为输出y,其余特征列作为输入X
  2. y已知的样本上对(X, y)拟合一个回归器;
  3. 用该回归器预测y的缺失值;
  4. 对每个特征重复上述过程,如此迭代max_iter轮,返回最后一轮的插补结果。
>>> import numpy as np >>> from sklearn.impute import IterativeImputer >>> imp = IterativeImputer(max_iter=10, random_state=0) >>> imp.fit([[1, 2], [3, 6], [4, 8], [np.nan, 3], [7, np.nan]]) IterativeImputer(random_state=0) >>> X_test = [[np.nan, 2], [6, np.nan], [np.nan, 6]] >>> # 模型学会了"第二个特征是第一个特征的两倍" >>> print(np.round(imp.transform(X_test))) [[ 1. 2.] [ 6. 12.] [ 3. 6.]]

SimpleImputerIterativeImputer都可以放进Pipeline中,构成支持插补的复合估算器(见 examples/impute/plot_missing_values.py)。

参数速查

根据 sklearn/impute/_iterative.py(类定义约 L58 起),核心参数如下:

参数默认值说明
estimatorBayesianRidge()每轮轮转插补使用的回归器;sample_posterior=True时其predict必须支持return_std
sample_posteriorFalse是否从拟合估计器的(高斯)预测后验中采样;做多重插补时应设为True
max_iter10最大插补轮数;早停条件为max(abs(X_t - X_{t-1})) / max(abs(X[known_vals])) < tol,仅在sample_posterior=False时生效
tol1e-3早停容差
n_nearest_featuresNone每个特征仅使用其他多少个特征来估计;特征间距离用初始插补后两两特征的绝对相关系数衡量,并按与相关性成正比的概率抽样以确保覆盖;特征数巨大时可显著加速;None表示使用全部特征
initial_strategy'mean'初始化缺失值的策略,与SimpleImputer.strategy相同('mean'/'median'/'most_frequent'/'constant'
fill_valueNone配合initial_strategy="constant"使用,规则同SimpleImputer
imputation_order'ascending'插补顺序:'ascending'(按缺失量升序)、'descending'(降序)、'roman'(从左到右)、'arabic'(从右到左)、'random'(随机)
skip_completeFalseTrue时跳过训练阶段没有缺失值的特征,可加速
min_value/max_value-np.inf/np.inf插补值的裁剪范围,支持数组形式的逐特征上下限
verbose0打印进度详情
random_stateNone控制n_nearest_features特征抽样、imputation_order='random'sample_posterior=True的后验采样,传入整数可保证确定性
add_indicator/keep_empty_featuresFalse/FalseSimpleImputer同义

收敛性与收益递减

IterativeImputer会循环max_iter轮,并在sample_posterior=False时,若相邻两轮之间的变化低于tol则提前停止。但实践表明:插补值常常并不真正收敛——轮转方案并不能保证达到不动点,因此迭代轮数最好被视为"与可用时间预算之间的权衡",而不是必须达成的目标。

当目标是预测时,这几乎不是问题。Le Morvan 与 Varoquaux 的研究报告了强烈的收益递减效应:更精确的插补对下游预测性能只能带来微小的提升,尤其是配合表达能力强的模型和缺失指示器(add_indicator)时。

官方给出的务实建议:

  • 做预测时:优先使用小的固定max_iter(例如max_iter=10),配合缺失指示器与表达能力强的下游模型,而不是在收敛性上投入;
  • 做数据重建时:迭代轮数(例如max_iter>50)和插补器的选择才更重要,应在具体任务上评估。

灵活性:一个类实现多种插补算法

R 语言数据科学生态中有许多成熟的插补包:Amelia、mi、mice、missForest 等。其中 missForest 非常流行,而它其实是多种序贯插补算法的一个特例——只需给IterativeImputer传入不同的回归器即可实现这些算法。对 missForest 而言,这个回归器就是随机森林(Random Forest)。参见 examples/impute/plot_iterative_imputer_variants_comparison.py 中的对比示例。

多重插补 vs 单次插补

统计学界普遍采用多重插补(multiple imputation):对同一特征矩阵生成m份独立的插补结果,每份都跑一遍后续分析流程(特征工程、聚类、回归、分类),最后比较m份分析结果(如留出验证误差),从而了解缺失值固有不确定性对分析结果的影响。

本仓库的IterativeImputer实现受 R 语言 MICE 包(Multivariate Imputation by Chained Equations,Van Buuren 与 Groothuis-Oudshoorn 2011 年发表于 Journal of Statistical Software)启发,但与之不同:它默认只返回单次插补而不是多次插补。不过,当sample_posterior=True时,可以反复以不同随机种子对同一数据集应用IterativeImputer,从而实现多重插补(更多讨论见 Little & Rubin 1986《Statistical Analysis with Missing Data》第 4 章)。

需要注意:IterativeImputer.transform不允许改变样本数量,因此无法通过单次调用transform得到多重插补,必须多次调用。

另外,从源码(sklearn/impute/_iterative.py 的版本注释)可以确认:自1.10 版本起IterativeImputer不再是实验性功能,可以直接从sklearn.impute导入,无需再通过sklearn.experimental启用。sample_posterior=True的实现细节(预测后验采样、truncnorm裁剪等)在 sklearn/impute/tests/test_impute.py 中有大量测试覆盖。

KNNImputer:最近邻插补

KNNImputer使用 k-最近邻方法填充缺失值。默认采用一种支持缺失值的欧氏距离度量sklearn.metrics.pairwise.nan_euclidean_distances来寻找最近邻:两个样本的距离只在其都不缺失的特征上计算(并对缺失维做加权校正)。每个缺失特征用n_neighbors个"在该特征上有值"的最近邻的值来插补,邻居特征值被均匀平均,或按到各邻居的距离加权平均。

关键行为(官方文档与 sklearn/impute/_knn.py 中_calc_impute实现一致):

  • 若一个样本有多个特征缺失,不同特征的邻居集合可能不同;
  • 当可用邻居数少于n_neighbors,且与训练集不存在任何有定义的距离时,使用该特征在训练集中的平均值
  • 若至少存在一个距离有定义的邻居,则使用其余邻居的加权或非加权平均;
  • 若某特征在训练中始终缺失,则会在transform时被移除。

方法学出处为 Troyanskaya 等人 2001 年发表于 Bioinformatics 的 DNA 微阵列缺失值估计论文。

参数速查

参数默认值说明
missing_valuesnp.nan缺失值占位符(int、float、str、np.nanNone);pandas 可空整型需设np.nan
n_neighbors5用于插补的邻居样本数(>= 1的整数)
weights'uniform''uniform'等权平均;'distance'按距离倒数加权;或自定义 callable(接收距离数组,返回同形状权重数组)
metric'nan_euclidean'邻居搜索的距离度量;'nan_euclidean'或符合func_metric(x, y, *, missing_values=np.nan)签名的 callable
copyTrue是否复制X
add_indicator/keep_empty_featuresFalse/False同前

示例

用两个最近邻特征值的均值替换np.nan编码的缺失值:

>>> import numpy as np >>> from sklearn.impute import KNNImputer >>> nan = np.nan >>> X = [[1, 2, nan], [3, 4, 3], [nan, 6, 5], [8, 8, 7]] >>> imputer = KNNImputer(n_neighbors=2, weights="uniform") >>> imputer.fit_transform(X) array([[1. , 2. , 4. ], [3. , 4. , 3. ], [5.5, 6. , 5. ], [8. , 8. , 7. ]])

从实现看,_calc_imputenp.argpartition高效选取前n_neighbors个候选捐赠者,再用掩码数组对捐赠者列做带权平均,权重矩阵中的NaN被置 0(sklearn/impute/_knn.py L163-L211);transform阶段按块(chunk)计算距离矩阵以控制内存,测试覆盖见 sklearn/impute/tests/test_knn.py。

保持特征数量不变:keep_empty_features

默认情况下,scikit-learn 的插补器会丢弃完全为空的特征,即整列都是缺失值的列。例如:

>>> imputer = SimpleImputer() >>> X = np.array([[np.nan, 1], [np.nan, 2], [np.nan, 3]]) >>> imputer.fit_transform(X) array([[1.], [2.], [3.]])

X中第一列全为np.nan,插补后被丢弃。虽然这类特征对预测没有帮助,但丢弃列会改变X的形状,在更复杂的机器学习管道中可能引发问题(例如与ColumnTransformer或特征名称映射配合时)。参数keep_empty_features提供了保留空特征的选项——此时会用常数值填充,大多数情况下这个常数值是 0:

>>> imputer.set_params(keep_empty_features=True) SimpleImputer(keep_empty_features=True) >>> imputer.fit_transform(X) array([[0., 1.], [0., 2.], [0., 3.]])

源码层面:当keep_empty_features=True时,_dense_fit/_sparse_fit会为全空列填充0(而非np.nan)到statistics_transform时也不再走"过滤无效统计量"分支(sklearn/impute/_base.py L508-L510、L630-L652)。IterativeImputerKNNImputer同样支持该参数(strategy='constant'时用fill_value而非 0),测试见 sklearn/impute/tests/test_impute.py 中的test_*_keep_empty_features系列。

MissingIndicator:标记插补值

MissingIndicator转换器把数据集变换为对应的二值矩阵,指示每个位置是否为缺失值。该变换常与插补配合使用:插补之后,"哪些值原本是缺失的"这一信息仍然具有预测价值。

与 add_indicator 的关系

SimpleImputerIterativeImputer都有布尔参数add_indicator(默认False)。设为True时,它会自动把MissingIndicator的输出堆叠(stack)到插补结果之后——等价于手动拼接,但更方便。内部实现上,_BaseImputer._fit_indicator会创建并拟合一个MissingIndicatorerror_on_new=False),_concatenate_indicator再负责将指示矩阵与原数据水平拼接(sklearn/impute/_base.py L114-L163)。

自定义缺失占位符与 features 参数

NaN通常是缺失值占位符,但它强制数据类型为 float。参数missing_values允许指定其他占位符,例如整数-1

>>> from sklearn.impute import MissingIndicator >>> X = np.array([[-1, -1, 1, 3], ... [4, -1, 0, -1], ... [8, -1, 1, 0]]) >>> indicator = MissingIndicator(missing_values=-1) >>> mask_missing_values_only = indicator.fit_transform(X) >>> mask_missing_values_only array([[ True, True, False], [False, True, True], [False, True, False]])

features参数用于选择为哪些特征构造掩码。默认值为'missing-only',只返回在 fit 时包含缺失值的特征的掩码:

>>> indicator.features_ array([0, 1, 3])

设为'all'则返回所有特征(无论是否含缺失值):

>>> indicator = MissingIndicator(missing_values=-1, features="all") >>> mask_all = indicator.fit_transform(X) >>> mask_all array([[ True, True, False, False], [False, True, False, True], [False, True, False, False]]) >>> indicator.features_ array([0, 1, 2, 3])

其他参数:sparse(默认'auto',是否输出稀疏矩阵)与error_on_new(默认True,transform 时若出现 fit 未见过的缺失特征列是否报错;imputer 内部调用时设为False)。

在 Pipeline 中组合使用

Pipeline中使用MissingIndicator时,务必借助FeatureUnionColumnTransformer把指示特征与常规特征合并。下面用 iris 数据集演示完整流程——先给数据注入随机缺失:

>>> from sklearn.datasets import load_iris >>> from sklearn.impute import SimpleImputer, MissingIndicator >>> from sklearn.model_selection import train_test_split >>> from sklearn.pipeline import FeatureUnion, make_pipeline >>> from sklearn.tree import DecisionTreeClassifier >>> X, y = load_iris(return_X_y=True) >>> mask = np.random.randint(0, 2, size=X.shape).astype(bool) >>> X[mask] = np.nan >>> X_train, X_test, y_train, _ = train_test_split(X, y, test_size=100, ... random_state=0)

构造FeatureUnion:所有特征先用SimpleImputer插补以支持分类器,同时附加MissingIndicator的指示变量:

>>> transformer = FeatureUnion( ... transformer_list=[ ... ('features', SimpleImputer(strategy='mean')), ... ('indicators', MissingIndicator())]) >>> transformer = transformer.fit(X_train, y_train) >>> results = transformer.transform(X_test) >>> results.shape (100, 8)

注意:上面的transformer本身不能做预测,还需要包一层带分类器的Pipeline

>>> clf = make_pipeline(transformer, DecisionTreeClassifier()) >>> clf = clf.fit(X_train, y_train) >>> results = clf.predict(X_test) >>> results.shape (100,)

iris 有 4 个特征,插补后 4 列 + 指示器(fit 时含缺失的特征)4 列,共 8 列,与输出形状(100, 8)吻合。

支持 NaN 的估算器(可原生处理缺失值的模型)

并非所有估算器都需要先插补。官方文档的"Estimators that handle NaN values"一节会按类型(cluster、regressor、classifier、transformer)列出无需预处理即可处理 NaN 的估算器列表。这个列表是在文档构建时动态生成的:doc/sphinxext/allow_nan_estimators.py中的AllowNanEstimators指令遍历sklearn.utils.all_estimators,对每个估算器实例化并检查其__sklearn_tags__()中的input_tags.allow_nan标签(该标签定义于 sklearn/utils/_tags.py)来决定是否收录。

几个值得注意的事实:

  • 判定是按实例进行的,因此会包含"只有在特定超参数组合下才支持 NaN"的估算器(例如SplineTransformer仅在handle_missing="zeros"时才允许 NaN,这一点已写入指令实现注释);
  • 典型代表是基于树的模型(决策树、随机森林、梯度提升等),它们可以在分裂时把缺失值路由到最佳分支;
  • 从源码结构看,SimpleImputer__sklearn_tags__allow_nan = is_scalar_nan(self.missing_values)(sklearn/impute/_base.py L165-L168),即只有当缺失值占位符是标量NaN时插补器才声明允许 NaN 输入。

完整的、随版本变化的估算器清单以构建后的 API 文档(doc/modules/impute.rst.. allow_nan_estimators::指令渲染结果)为准。

实战选型建议

综合官方指南与源码实现,可以总结如下选型路径:

  1. 先判断目标:预测任务优先追求简单与稳定;数据重建任务才需要在插补质量上投入;
  2. 优先考虑原生支持 NaN 的模型(如树模型),可能完全省去插补步骤;
  3. 需要插补时,从SimpleImputer起步(均值 / 中位数 / 众数 / 常数),并配合add_indicator=True保留缺失模式;
  4. 特征间存在强关联且数据集规模可接受时,尝试IterativeImputer(默认BayesianRidge,固定max_iter=10即可)或KNNImputer(注意其距离计算与邻居加权开销);
  5. 关心缺失带来的不确定性时,用IterativeImputer(sample_posterior=True)配多个随机种子做多重插补;
  6. 在复杂管道中使用时,设置keep_empty_features=True以避免全空列导致的特征形状变化,并用FeatureUnion/ColumnTransformer正确接入MissingIndicator

参考与延伸阅读

  • 官方用户指南:doc/modules/impute.rst
  • 核心源码:sklearn/impute/_base.py(SimpleImputer/MissingIndicator)、sklearn/impute/_iterative.py(IterativeImputer)、sklearn/impute/_knn.py(KNNImputer
  • 端到端示例:examples/impute/plot_missing_values.py、examples/impute/plot_iterative_imputer_variants_comparison.py
  • 测试用例:sklearn/impute/tests/test_impute.py、sklearn/impute/tests/test_knn.py、sklearn/impute/tests/test_common.py
  • 文档生成逻辑:doc/sphinxext/allow_nan_estimators.py
  • 关键文献(标题与出处,见官方指南引用):Van Buuren & Groothuis-Oudshoorn(2011)"mice: Multivariate Imputation by Chained Equations in R",Journal of Statistical Software;Little & Rubin(1986)《Statistical Analysis with Missing Data》;Le Morvan & Varoquaux(2025)"Imputation for prediction: beware of diminishing returns",ICLR;Troyanskaya 等(2001)"Missing value estimation methods for DNA microarrays",Bioinformatics。

【免费下载链接】scikit-learnscikit-learn: machine learning in Python项目地址: https://gitcode.com/gh_mirrors/sc/scikit-learn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询