4. 缺失值处理(下):删除、填充与插值
好,咱们接着聊缺失值处理。上一节我们讲了怎么识别和统计缺失值,这一节直接上硬菜——怎么处理它们。
我个人习惯把缺失值处理方法分成三大类:删除法、填充法和插值法。每种方法都有它的适用场景,选错了可能会让模型效果大打折扣。我在项目中就吃过这个亏,后面会跟大家细说。
4.1 删除缺失值:最简单,但别乱用
删除缺失值,说白了就是把有缺失的行或列直接扔掉。这个方法最粗暴,但也最危险。
什么时候适合删除?我总结了几种情况:
- 缺失比例极低(<5%):比如10000条数据里只有几十条有缺失,删了不影响大局
- 缺失完全随机:数据缺失跟其他变量没关系,删了不会引入偏差
- 样本量充足:删完还有足够的数据训练模型
代码实现很简单:
import pandas as pd
import numpy as np
# 创建示例数据
df = pd.DataFrame({
'订单量': [100, 200, np.nan, 150, 300],
'销售额': [5000, 8000, 6000, np.nan, 12000],
'库存天数': [30, 25, 28, 32, np.nan]
})
# 删除包含任何缺失值的行
df_drop_any = df.dropna()
print("删除任何缺失行后:")
print(df_drop_any)
# 删除全部缺失的行
df_drop_all = df.dropna(how='all')
# 删除缺失超过2个的行
df_drop_thresh = df.dropna(thresh=2)
嗯,这里要注意:dropna()默认是删除包含任何缺失值的行。如果你只想删除全部缺失的行,记得加how='all'参数。
4.2 均值/中位数/众数填充:最常用的方法
填充法比删除法温和多了。它不丢数据,而是用某个值把空缺补上。最常见的三种:均值、中位数、众数。
| 填充方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 均值填充 | 数据近似正态分布,无异常值 | 计算简单,保持均值不变 | 受异常值影响大,会降低方差 |
| 中位数填充 | 数据有偏态或存在异常值 | 鲁棒性强,不受极端值影响 | 可能改变数据分布形态 |
| 众数填充 | 分类变量或离散数据 | 保持类别分布 | 可能引入偏差 |
我曾经在一个供应链项目中,用均值填充了库存天数。结果发现填充后的数据方差变小了,模型对库存波动的预测能力直接下降。后来换成中位数填充,效果好了不少。你想想看,库存天数这种数据,经常会有极端值(比如某次大促),均值很容易被带偏。
# 均值填充
df['销售额_fill_mean'] = df['销售额'].fillna(df['销售额'].mean())
# 中位数填充
df['库存天数_fill_median'] = df['库存天数'].fillna(df['库存天数'].median())
# 众数填充(适用于分类变量)
df['品类_fill_mode'] = df['品类'].fillna(df['品类'].mode()[0])
4.3 前向/后向填充:时间序列的专属武器
如果你处理的是时间序列数据(比如每天的库存量、每周的销售额),前向填充和后向填充就派上用场了。
前向填充:用上一个非缺失值填充当前缺失值。说白了就是「沿用上一个值」。
后向填充:用下一个非缺失值填充当前缺失值。就是「用后面的值补前面的坑」。
我举个例子你就明白了:
# 时间序列数据
df_time = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=7, freq='D'),
'库存量': [100, np.nan, np.nan, 80, np.nan, 120, 110]
})
# 前向填充
df_time['库存_ffill'] = df_time['库存量'].fillna(method='ffill')
# 后向填充
df_time['库存_bfill'] = df_time['库存量'].fillna(method='bfill')
print(df_time)
输出结果:
日期 库存量 库存_ffill 库存_bfill
0 2024-01-01 100.0 100.0 100.0
1 2024-01-02 NaN 100.0 80.0
2 2024-01-03 NaN 100.0 80.0
3 2024-01-04 80.0 80.0 80.0
4 2024-01-05 NaN 80.0 120.0
5 2024-01-06 120.0 120.0 120.0
6 2024-01-07 110.0 110.0 110.0
你看,前向填充把1月2日和3日的库存都填成了100(沿用1月1日的值)。后向填充则把1月2日和3日填成了80(用1月4日的值补前面的)。
4.4 插值法填充:更智能的填充方式
插值法比前两种方法更「聪明」。它不是简单地复制粘贴,而是根据已有数据的趋势来估算缺失值。
常见的插值方法有:
- 线性插值:假设缺失值前后是线性关系,用直线连接两个已知点来估算
- 多项式插值:用多项式曲线拟合数据点,适合非线性趋势
- 样条插值:分段拟合,平滑度更好
# 线性插值
df_time['库存_linear'] = df_time['库存量'].interpolate(method='linear')
# 多项式插值(2阶)
df_time['库存_poly'] = df_time['库存量'].interpolate(method='polynomial', order=2)
# 时间插值(考虑时间间隔)
df_time['库存_time'] = df_time['库存量'].interpolate(method='time')
我记得有一次处理供应链的日销量数据,连续3天缺失。用前向填充的话,这3天销量都一样,明显不合理。后来用了线性插值,根据前后几天的趋势估算,效果自然多了。
4.5 如何选择?我的实战经验
说了这么多方法,到底该用哪个?我给大家一个决策思路:
- 先看缺失比例:<5%可以考虑删除,>30%必须用填充或插值
- 再看数据类型:时间序列优先考虑前向/后向填充或插值;非时间序列用均值/中位数/众数
- 最后看业务含义:比如库存数据,如果缺失是因为断货,用前向填充就不合理(断货期间库存应该是0)
我曾经犯过一个错:处理供应商交货周期数据时,直接用均值填充了所有缺失值。结果模型训练出来,预测的交货周期总是偏短。后来一查,原来缺失的数据大多是交货延迟的记录(供应商没及时上报),均值填充把延迟信息全抹掉了。从那以后,我处理缺失值前一定会先跟业务方聊一聊,搞清楚「为什么缺失」。
4.6 本章知识体系
下面这张图帮你梳理了缺失值处理的完整思路:
好了,缺失值处理的内容就到这里。记住,没有银弹,多试几种方法,选最适合你数据的那个。