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 三种方法怎么选?一张图说清楚
下面这张图是我自己总结的决策流程,帮你快速判断该用哪种方法:
4.5 实战建议:别死磕一种方法
在实际项目中,我很少只用一种方法。通常的做法是:
- 先分析缺失模式:是随机缺失还是非随机缺失?
- 对不同特征用不同方法:数值型用均值/中位数,分类型用众数,时间序列用插值
- 做敏感性分析:试试不同的填充方法,看看对模型结果影响大不大
- 记录处理过程:把缺失比例、填充方法、填充值都记下来。方便复盘
我的习惯:我会创建一个missing_report字典,记录每个特征的缺失比例和处理方法。这样别人看我的代码,一眼就知道我对缺失值做了什么。
最后说一句:没有最好的方法,只有最合适的方法。多试试,多对比,你慢慢就会有自己的判断。
公众号:蓝海资料掘金营,微信deep3321