在机器学习 pipeline 中,fillna() 和 dropna() 必须配合训练/预测隔离逻辑,否则引发数据泄露;fit_transform 不能直接用于整列缺失值,因全局统计会偷看测试集信息,分组填充须用 transform 而非 apply,线性回归填充需标准化并避免循环依赖,插值法要求 datetimeindex 且分类列需单独处理,所有策略必须封装为可复用的 fit/transform 接口。

直接说结论:在机器学习 pipeline 中,fillna() 和 dropna() 不能单独用,必须配合训练/预测阶段的隔离逻辑,否则会引入数据泄露——这是绝大多数人踩坑的根源。
为什么 fit_transform 不能直接用在整列缺失值上?
很多教程教你在训练集上用 df['col'].fillna(df['col'].median()),再原样套到测试集。这看似合理,但实际埋了雷:
- 如果测试集某列的分布偏移(比如新用户年龄普遍更小),用训练集中位数填充会导致偏差放大
-
df['col'].median()在训练集含缺失时会自动忽略 NaN 计算,但如果你没显式切分 train/test 就调用,median()实际用了全部数据——等于把测试信息“偷看”进来了 - 时间序列或分组场景下(如按地区填收入),直接全局统计会抹掉结构信息
分组填充必须用 transform 而不是 apply
业务中常见“按城市填收入”“按产品类目填退货率”,这时必须用 groupby().transform(),而不是 groupby().apply() 或手动循环:
-
transform保证返回与原 Series 等长的结果,能直接赋值给列;apply返回的是分组聚合结果,长度不匹配,强行赋值会报ValueError: cannot assign a groupby result with length X to a Series with length Y - 错误写法:
df['income'] = df.groupby('city')['income'].apply(lambda x: x.fillna(x.median()))→ 崩溃 - 正确写法:
df['income'] = df.groupby('city')['income'].transform(lambda x: x.fillna(x.median())) - 注意:
transform内部对每组分别计算 median,且只用该组非缺失样本,天然隔离不同组干扰
线性回归填充要防住两个硬伤
用 LinearRegression 预测缺失值听起来智能,但实战中极易翻车:
- 特征量纲差异大时(比如年龄 0–100、收入 3000–50000),不标准化直接拟合,模型权重全被高量纲特征主导,
StandardScaler不是可选项,是必选项 - 目标列本身有缺失,但你又拿它当特征去填其他列——形成循环依赖。必须按缺失比例升序处理:先填缺失最少的列,再用已填列作为特征去填缺失更多的列
- 别在原始 DataFrame 上原地 fill,先
df_train = df.dropna()拿到完整样本训模型,再用model.predict(X_test)填充测试集对应位置,严格保持 train/test 边界
插值法只适用于有序索引,且 method='time' 有隐藏前提
interpolate(method='time') 看起来很适合时间序列,但它真正生效的前提常被忽略:
- 索引必须是
DatetimeIndex,不能只是字符串日期列;否则method='time'退化为method='linear',失去时间间隔加权能力 - 如果缺失连续多天(比如断采样 3 天),线性插值会拉直线,但真实业务中可能有周期性(如周末低活),这时应先用
resample('D').asfreq()对齐频率,再考虑interpolate(method='spline')或外部周期模型 - 别对分类列(如
status)用插值——interpolate默认只作用于数值列,但如果你强制df.select_dtypes(include='number').interpolate(),记得后续要把非数值列单独处理,否则会丢列
最易被忽略的一点:所有填充策略都必须封装成可复用的 fit()/transform() 接口,哪怕不用 sklearn Pipeline,也要确保训练时保存的统计量(如各组中位数、回归系数、标准化参数)能原样复用于预测阶段。临时变量一扔,下次跑 inference 就全乱套。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











