数值机器学习特征出现NaN时,可以用SimpleImputer确定一套可复核的填补规则。关键是先看缺的是特征还是业务事实,再检查训练统计量和变换后的列数;单纯让fit不报错,还不足以说明数据处理正确。
先规定填补对象和缺失含义
本例只填补模型输入特征的缺失值,按训练集每列中位数填入;不填补目标标签,也不把预测用的填充值写回原始业务记录。

示例的两列叫age和amount,只用于说明数值特征接口,没有对应真实客户。0是一个已经观测到的数值,不等于NaN;若输入用空字符串、−999或“未知”表示缺失,应先按字段规则解析,不能无依据把所有0都当缺失。
本文示例已在 Python 3.11、scikit-learn 1.7.2、NumPy 2.4.6、pandas 2.3.3、SciPy 1.17.1 的本地 CPU 环境运行;输入是人为生成的教学数据,不是客户案例。
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
python -m pip install scikit-learn==1.7.2 pandas==2.3.3 numpy==2.4.6 scipy==1.17.1
拟合填补器,检查值与缺失指示列
将代码保存为 missing_features.py 并运行 python missing_features.py;先核对statistics_、输出列名和测试特征,再检查全空列示例的形状。
前半段将填补器和逻辑回归串进Pipeline。训练8行、测试2行仅用于检验处理链,不能提供可信的模型效果评估。测试里的10000故意大于训练金额,检查它不会影响已经学到的300。
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
X_train = np.array([[20, 100], [np.nan, 200], [40, np.nan],
[25, 250], [35, 350], [30, 300],
[45, 450], [np.nan, 400]], dtype=float)
y_train = np.array([0, 0, 1, 0, 1, 0, 1, 1])
X_test = np.array([[np.nan, 10000], [50, np.nan]], dtype=float)
pipe = Pipeline([
("fill", SimpleImputer(strategy="median", add_indicator=True)),
("model", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train)
fill = pipe.named_steps["fill"]
before = fill.statistics_.copy()
transformed = fill.transform(X_test)
print("训练中位数:", fill.statistics_)
print("输出列:", fill.get_feature_names_out(["age", "amount"]))
print("测试特征:", transformed)
print("预测:", pipe.predict(X_test))
assert np.allclose(fill.statistics_, [32.5, 300])
assert np.allclose(transformed, [[32.5, 10000, 1, 0], [50, 300, 0, 1]])
assert np.array_equal(before, fill.statistics_)
all_empty = np.array([[1, np.nan], [2, np.nan], [3, np.nan]])
empty_test = np.array([[4, 8]])
drop = SimpleImputer(strategy="median").fit(all_empty)
keep = SimpleImputer(strategy="median", keep_empty_features=True).fit(all_empty)
print("默认空列输出:", drop.transform(empty_test))
print("保留空列输出:", keep.transform(empty_test))
print("保留时统计量:", keep.statistics_)
assert drop.transform(empty_test).shape == (1, 1)
assert keep.transform(empty_test).shape == (1, 2)
print("保留时缺失填补:", keep.transform([[4, np.nan]]))
核对实际输出的四列含义
训练填充值应为32.5和300,两条测试特征补完后分别是[32.5,10000,1,0]和[50,300,0,1];末尾两列记录原特征是否缺失。
| 列名 | 来自哪里 | 本例检查点 |
|---|---|---|
| age | 原年龄特征或训练中位数 | 第一条NaN变成32.5 |
| amount | 原金额特征或训练中位数 | 第二条NaN变成300 |
| missingindicator_age | age缺失状态 | 第一条1、第二条0 |
| missingindicator_amount | amount缺失状态 | 第一条0、第二条1 |
本地示例输出预测[1,1],它只证明两条输入可以经过同一处理链,不表示这两个标签可靠。statistics_在transform之后仍等于之前的副本,测试金额10000没有进入填补统计量;不要对测试集重新fit。
add_indicator=True只为拟合阶段已经出现缺失的特征添加指示列。若某列训练时从未缺失,预测时才第一次缺失,这个选项不会临时增加一个新的指示列;应在上线前用输入契约覆盖这种情况。
空列必须单独验收,不能只看程序能否运行
训练时整列为空就没有中位数可学;默认会丢弃该列,设置keep_empty_features=True可保留列,但本例缺失填补值为0,并不表示已获得有意义的统计量。
第二段代码的训练第2列全是NaN。默认填补器输出[[4.]],shape为(1,1),同时发出跳过无观测特征的UserWarning。保留空列的填补器输出[[4.,8.]];它保留预测时真实提供的8,而预测时仍缺失则变成[[4.,0.]]。这两种结果的列数不同,应连同模型一起检查。
不要为保持维度就把所有全空列机械填0。先找上游是否漏采或字段错名;若字段确实无法观测,决定训练时是否删除。保存预处理器与模型时一并记录输出列名,避免把丢列之后的矩阵继续按旧列含义解释。
替换真实数据后的下一步
按你的样本关系先划分训练、验证和测试集合,再在训练部分拟合。若用交叉验证评估填补方案,把填补器留在Pipeline里,让每一折重新从该折训练数据学习统计量;不能先对全表fit_transform再切分。
中位数是便于解释的基线,不保证补回真实值。缺失原因与业务结果相关、缺失比例很高或目标本身缺失时,需要先查数据采集过程,并在独立数据上比较方案。预测用填补不能替代账务、健康记录等原始事实修复。
官方资料与适用版本
本文于2026年10月1日核对下列官方资料,接口说明对应 scikit-learn 1.7;运行示例使用1.7.2。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30722.html