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)
这里要注意:固定值填充只适用于你非常确定的情况。比如某个传感器坏了,但你知道正常值应该是多少。否则,别乱用。
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 方法选择:一张图说清楚
说了这么多,你可能会问:到底该用哪种?我画了一张流程图,帮你快速决策。
这张图的核心逻辑很简单:缺失少用简单方法,缺失多用复杂方法。但记住,没有银弹。我每次做数据清洗,都会先跑一遍所有方法,对比结果再决定。
4.6 实战建议:我的“三板斧”
最后,分享一个我自己的实战流程。我管它叫“三板斧”:
- 第一板斧:快速填充。先用前向填充+限制长度,处理掉大部分零散缺失。这一步能解决80%的问题。
- 第二板斧:插值修正。对连续缺失但长度不大的,用时间插值。这一步能提升精度。
- 第三板斧:模型兜底。对剩下的顽固缺失,用随机森林或XGBoost预测。这一步是最后防线。
你可能会问:为什么不一开始就用模型?因为成本高啊。模型训练、调参、验证,一套下来半天就没了。而前向填充一行代码就搞定。所以,我的原则是:能用简单方法,就别折腾。
好了,这一节的内容就到这儿。缺失值填充,说白了就是“猜”。但怎么猜得准,靠的是对业务的理解和对数据的敬畏。下一节,咱们聊聊重复值处理——那些让你头疼的重复数据,该怎么收拾。