4. 缺失值处理(下):删除、填充与插值

上一节我们聊了怎么发现缺失值,这一节咱们来动真格的——怎么处理它们。

说实话,我刚入行那会儿,遇到缺失值第一反应就是删。简单粗暴,省事。后来在一个金融风控项目里吃了大亏——删掉了几行带缺失值的关键数据,结果模型AUC直接掉了0.15。嗯,从那以后我再也不敢无脑删了。

处理缺失值,说白了就三条路:删掉它、填上它、猜一个值插进去。咱们一个一个说。

4.1 删除缺失值:dropna()

删除是最直接的方法。但你要想清楚:删了之后,你的样本量够不够?删掉的是不是随机缺失?

我个人习惯是:缺失比例低于5%,而且确认是随机缺失,才考虑删除。

核心原则:删除前先问自己三个问题——

  • 删掉后样本量还够建模吗?
  • 缺失是随机的还是系统性的?
  • 删掉会不会引入偏差?

来看代码:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 2, 3, 4, 5],
    'C': [1, 2, 3, np.nan, 5]
})

# 删除任何包含缺失值的行
df_drop_any = df.dropna()
print(df_drop_any)
# 输出:只剩第1行和第4行(索引从0开始)

# 删除全部缺失的行
df_drop_all = df.dropna(how='all')

# 指定阈值:至少要有3个非空值才保留
df_drop_thresh = df.dropna(thresh=3)

# 按列删除:删除某列缺失超过2个的列
df_drop_col = df.dropna(axis=1, thresh=3)

这里有个坑——inplace参数。我曾经在项目里用了df.dropna(inplace=True),结果后面想回溯数据发现原数据已经被改了。现在我建议:永远不要用inplace,赋值给新变量更安全。

避坑指南:我曾经在客户现场调试代码,发现数据莫名其妙少了十几行。查了半天,原来是同事在notebook里用了inplace=True,而且执行了两次。嗯,从那以后我团队里就禁止用inplace了。

4.2 填充缺失值:fillna()

填充比删除更常用。你想想看,数据来之不易,能保留尽量保留。

填充方法有很多,我按使用频率排个序:

方法 适用场景 代码示例
固定值填充 知道缺失值应该是什么 df.fillna(0)
均值/中位数填充 数值型特征,分布较对称 df.fillna(df.mean())
众数填充 分类特征 df.fillna(df.mode().iloc[0])
前向/后向填充 时间序列数据 df.fillna(method='ffill')

我建议你记住一个原则:填充的值要尽量接近真实值。比如用户年龄缺失,用中位数比用均值更稳健——因为年龄分布可能有偏。

# 固定值填充
df_filled = df.fillna(0)

# 均值填充
df_filled_mean = df.fillna(df.mean())

# 前向填充(用上一个值填充)
df_ffill = df.fillna(method='ffill')

# 后向填充(用下一个值填充)
df_bfill = df.fillna(method='bfill')

# 限制填充个数:最多填充2个连续缺失
df_limited = df.fillna(method='ffill', limit=2)

小技巧:处理时间序列时,我习惯先用ffill,再用bfill兜底。这样能保证首尾的缺失也被处理到。

4.3 插值法:interpolate()

插值法比简单填充更聪明。它不是填一个固定值,而是根据周围的数据点「猜」一个合理的值。

为什么会用到插值?举个例子:传感器每隔1秒采集一次温度数据,中间有3秒没采到。你用均值填充,等于忽略了温度变化的趋势。但用插值法,它会根据前后数据点画一条线,然后算出中间点的值。

# 线性插值(默认)
df_interp_linear = df.interpolate()

# 时间插值(适用于时间序列)
df_interp_time = df.interpolate(method='time')

# 多项式插值(阶数越高越灵活,但也越容易过拟合)
df_interp_poly = df.interpolate(method='polynomial', order=2)

# 样条插值
df_interp_spline = df.interpolate(method='spline', order=3)

我常用的插值方法就三种:

  • 线性插值:默认方法,简单够用。适合变化平缓的数据。
  • 时间插值:考虑时间间隔。如果数据采集时间不均匀,这个比线性插值准。
  • 多项式插值:适合有明显趋势的数据。但阶数别设太高,我一般不超过3阶。

注意:插值法不是万能的。如果连续缺失太多(比如连续缺失超过5个),插出来的值可能偏差很大。我曾经在一个气象数据集里连续缺失了10个点,用3阶多项式插值,结果插出了负数的降雨量——嗯,这显然不合理。

4.4 三种方法怎么选?一张图说清楚

下面这张图是我自己总结的决策流程,帮你快速判断该用哪种方法:

缺失值处理方法决策流程图 发现缺失值 缺失比例 < 5%? 直接删除 时间序列? 插值法 分布对称? 均值填充 中位数填充

4.5 实战建议:别死磕一种方法

在实际项目中,我很少只用一种方法。通常的做法是:

  1. 先分析缺失模式:是随机缺失还是非随机缺失?
  2. 对不同特征用不同方法:数值型用均值/中位数,分类型用众数,时间序列用插值
  3. 做敏感性分析:试试不同的填充方法,看看对模型结果影响大不大
  4. 记录处理过程:把缺失比例、填充方法、填充值都记下来。方便复盘

我的习惯:我会创建一个missing_report字典,记录每个特征的缺失比例和处理方法。这样别人看我的代码,一眼就知道我对缺失值做了什么。

最后说一句:没有最好的方法,只有最合适的方法。多试试,多对比,你慢慢就会有自己的判断。


公众号:蓝海资料掘金营,微信deep3321