4. 缺失值处理(下):填充缺失值

好,咱们接着聊。上一节我们把缺失值揪了出来,也分析了它们为啥会出现。这一节,咱们要动真格的了——怎么把这些坑填上。

说实话,填充缺失值这事儿,没有标准答案。我在电力行业摸爬滚打这些年,见过太多人一上来就用均值填充,结果模型跑出来一塌糊涂。为什么?因为电价数据太特殊了,它有明显的周期性、趋势性,还有突发性。你随便填,等于往数据里掺沙子。

下面我按自己的经验,把常用的填充方法排了个序。从最简单的开始,到最复杂的收尾。你根据实际情况选就行。

4.1 固定值填充:最直接的办法

有时候,缺失值本身就有意义。比如某个时刻电价没采集到,但你知道那个时段是低谷期,电价应该接近0。这时候直接填0,反而比瞎猜更准。

我习惯用 fillna() 配合一个固定值。举个例子:

import pandas as pd

# 假设df是电价数据,'price'列有缺失
# 我知道凌晨2点到5点电价基本为0
df.loc[df['hour'].isin([2,3,4,5]), 'price'] = df['price'].fillna(0)

这里要注意:固定值填充只适用于你非常确定的情况。比如某个传感器坏了,但你知道正常值应该是多少。否则,别乱用。

⚠️ 避坑指南: 我曾经在一个项目中,直接用0填充了所有缺失值。结果模型训练出来,预测的电价全是0附近。后来才发现,那些缺失值其实是高峰期的数据,填0等于把真实信息全丢了。所以,固定值填充一定要有业务依据。

4.2 前向/后向填充:时间序列的“就近原则”

电价数据是时间序列,相邻时刻的值往往很接近。这时候,前向填充(forward fill)和后向填充(backward fill)就派上用场了。

说白了,就是用上一个时刻的值填当前缺失,或者用下一个时刻的值填回来。

# 前向填充:用上一个非缺失值填充
df['price'] = df['price'].fillna(method='ffill')

# 后向填充:用下一个非缺失值填充
df['price'] = df['price'].fillna(method='bfill')

我个人习惯,在电价波动平缓的时段用前向填充。比如凌晨到早上,电价变化不大,前向填充基本够用。但遇到电价突变的时候,比如中午突然拉闸限电,前向填充就会滞后。这时候后向填充反而更准。

你想想看,如果缺失值连续出现好几个,前向填充会把同一个值一直往后推,形成一段“平路”。这在电价数据里是不合理的。所以,我一般会限制连续填充的长度:

# 只填充连续缺失不超过3个的情况
df['price'] = df['price'].fillna(method='ffill', limit=3)
💡 小技巧: 我在做实时电价预测时,经常用前向填充。因为实时数据里,最新的值往往最有参考价值。但如果是历史数据清洗,我会先用后向填充,再用前向填充,取两者的平均值。这样能减少偏差。

4.3 插值法填充:让数据“平滑过渡”

插值法,说白了就是根据已知点,画一条线穿过缺失点。这条线可以是直线,也可以是曲线。

我最常用的是线性插值。它假设缺失值前后是线性变化的。对于电价这种有趋势的数据,线性插值比前向填充更合理。

# 线性插值
df['price'] = df['price'].interpolate(method='linear')

但线性插值有个问题——它太“老实”了。电价波动经常是非线性的,比如早上6点到9点,电价会快速爬升。这时候线性插值会低估峰值。

所以,我还会用时间插值:

# 基于时间索引的插值
df['price'] = df['price'].interpolate(method='time')

时间插值会考虑时间间隔。比如缺失了1小时和缺失了5分钟,插出来的值是不一样的。这个在电价数据里特别实用,因为电价变化跟时间强相关。

嗯,这里要注意:插值法对数据密度有要求。如果缺失值太多,比如连续缺失十几个小时,插值出来的结果基本就是一条直线,没什么参考价值。我一般建议,连续缺失超过总数据长度5%的,就别用插值了。

4.4 模型预测填充:高阶玩法

如果前面的方法都满足不了你,那就得上模型了。模型预测填充,就是用其他特征来预测缺失值。说白了,就是把缺失值当成一个预测问题。

我在项目中遇到过这种情况:某个电表连续一周没数据,前向填充和插值都不靠谱。最后我用同一时段其他电表的数据,加上天气、节假日特征,训练了一个随机森林模型来预测缺失值。

from sklearn.ensemble import RandomForestRegressor

# 假设X_train是完整数据的特征,y_train是对应的电价
# X_missing是缺失值所在行的特征
model = RandomForestRegressor(n_estimators=100)
model.fit(X_train, y_train)
predicted_values = model.predict(X_missing)

# 填充到原数据
df.loc[df['price'].isna(), 'price'] = predicted_values

模型预测填充的优点是精度高,能捕捉复杂关系。但缺点也很明显——计算量大,而且容易过拟合。我一般只在缺失值比例超过10%的时候才用。

📌 核心要点: 模型预测填充不是万能的。它依赖特征工程,如果特征选得不好,预测出来的值可能比均值填充还差。我建议先用简单方法试试,不行再上模型。

4.5 方法选择:一张图说清楚

说了这么多,你可能会问:到底该用哪种?我画了一张流程图,帮你快速决策。

缺失值填充方法选择流程 发现缺失值 缺失比例 < 5%? 是 → 简单方法 连续缺失 < 3个? 是 → 前向/后向填充 有相关特征? 是 → 模型预测填充 否 → 插值法填充

这张图的核心逻辑很简单:缺失少用简单方法,缺失多用复杂方法。但记住,没有银弹。我每次做数据清洗,都会先跑一遍所有方法,对比结果再决定。

4.6 实战建议:我的“三板斧”

最后,分享一个我自己的实战流程。我管它叫“三板斧”:

  1. 第一板斧:快速填充。先用前向填充+限制长度,处理掉大部分零散缺失。这一步能解决80%的问题。
  2. 第二板斧:插值修正。对连续缺失但长度不大的,用时间插值。这一步能提升精度。
  3. 第三板斧:模型兜底。对剩下的顽固缺失,用随机森林或XGBoost预测。这一步是最后防线。

你可能会问:为什么不一开始就用模型?因为成本高啊。模型训练、调参、验证,一套下来半天就没了。而前向填充一行代码就搞定。所以,我的原则是:能用简单方法,就别折腾。

💡 我的习惯: 每次填充完,我都会画一张填充前后的对比图。看看填充的值是不是符合电价的变化规律。如果发现填充值明显异常,比如深夜填了个高峰价,那就得换方法了。数据清洗不是一次性工作,需要反复迭代。

好了,这一节的内容就到这儿。缺失值填充,说白了就是“猜”。但怎么猜得准,靠的是对业务的理解和对数据的敬畏。下一节,咱们聊聊重复值处理——那些让你头疼的重复数据,该怎么收拾。


专注资料整理