缺失值处理:缺失值识别、均值/中位数填充、前向/后向填充、插值法
做风电数据分析,最头疼的问题之一就是缺失值。
我刚开始接触风电数据时,拿到一个风场三年的SCADA数据,兴冲冲地跑了个统计,结果发现风速、功率列里全是空洞。当时我第一反应是「这数据废了」,后来才知道——缺失值处理,恰恰是体现数据分析师功力的地方。
缺失值识别:先别急着填,看看它长什么样
我个人习惯,拿到数据第一步不是填充,而是先搞清楚缺失值是怎么产生的。
风电数据缺失的原因五花八门:传感器故障、通讯中断、停机维护、极端天气导致设备离线……不同原因,处理方式完全不同。
怎么识别?用Pandas几行代码就能搞定:
import pandas as pd
import numpy as np
# 读取风电数据
df = pd.read_csv('wind_farm_data.csv')
# 查看每列的缺失值数量
print(df.isnull().sum())
# 查看缺失值占比
print(df.isnull().mean() * 100)
# 可视化缺失值位置
import missingno as msno
msno.matrix(df)
嗯,这里要注意——isnull().sum()只能告诉你「有多少缺失」,但缺失值的位置分布同样重要。我遇到过一种情况:某台风机的功率数据连续缺失了72小时,一看日志,原来是那次台风天设备主动降载保护了。这种「连续缺失」和「随机缺失」,处理逻辑完全不同。
| 缺失类型 | 典型场景 | 处理建议 |
|---|---|---|
| 完全随机缺失 | 传感器偶发故障 | 可直接填充或删除 |
| 随机缺失 | 通讯不稳定 | 需结合其他特征估计 |
| 非随机缺失 | 停机维护、限电 | 不能随意填充,需标记 |
均值/中位数填充:简单但别乱用
均值填充,说白了就是用该列的平均值去补空缺。中位数填充类似,只是用中位数代替。
为什么会有两种?因为风电数据经常有异常值。比如某台风速传感器偶尔抽风,飙到60m/s,这时候均值会被拉高,中位数反而更稳健。
# 均值填充
df['wind_speed'].fillna(df['wind_speed'].mean(), inplace=True)
# 中位数填充
df['wind_speed'].fillna(df['wind_speed'].median(), inplace=True)
# 按机组分组填充(更合理)
df['wind_speed'] = df.groupby('turbine_id')['wind_speed'].transform(
lambda x: x.fillna(x.median())
)
我曾经在一个项目中,用均值填充了某台风机的功率缺失值,结果模型训练出来,那台风机的预测误差比其他机组高了30%。后来一查,那台风机长期处于限电状态,功率本身就偏低,用全场均值去填,等于硬生生把它的功率「拔高」了。
前向/后向填充:时序数据的「就近原则」
风电数据是典型的时间序列。前一秒风速10m/s,下一秒突然变成NaN,你想想看,最合理的猜测是什么?
对,大概率还是10m/s左右。这就是前向填充(forward fill)的核心思想——用上一个有效值填充当前缺失。
# 前向填充
df['wind_speed'].fillna(method='ffill', inplace=True)
# 后向填充
df['wind_speed'].fillna(method='bfill', inplace=True)
# 限制填充步长(最多向前填充3个点)
df['wind_speed'].fillna(method='ffill', limit=3, inplace=True)
后向填充正好相反,用下一个有效值来补。什么时候用后向?比如设备重启后,数据从某个时间点开始恢复,那重启之前的那段缺失,用恢复后的第一个值来填充,反而更合理。
我记得有一次处理一个海上风场的数据,通讯中断了2小时,恢复后数据一切正常。我用前向填充补了那2小时,结果发现功率曲线出现了一段「平直段」——因为前向填充把中断前的最后一个值一直往后推,导致那段数据看起来像停机了一样。后来改成后向填充,效果就好多了。
limit参数一定要设。连续缺失100个点还用前向填充,那最后几十个点基本就是「复制粘贴」,毫无意义。
插值法:让缺失值「平滑过渡」
均值填充太粗暴,前向填充太简单。那有没有更「聪明」的办法?
插值法就是干这个的。它利用缺失值前后的数据点,拟合出一条曲线,然后估算缺失位置的值。
# 线性插值
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')
# 时间插值(考虑时间间隔)
df['wind_speed'] = df['wind_speed'].interpolate(method='time')
# 多项式插值(阶数越高越灵活,但也越容易过拟合)
df['wind_speed'] = df['wind_speed'].interpolate(method='polynomial', order=2)
# 样条插值
df['wind_speed'] = df['wind_speed'].interpolate(method='spline', order=3)
线性插值是最常用的。它假设两个点之间的变化是线性的,对于风速这种连续变化量,效果不错。
但要注意——如果缺失段前后数据波动剧烈,线性插值会「削平」这些波动。我做过一个对比实验:在风速剧烈变化的时段,线性插值的RMSE比样条插值高了将近15%。
| 插值方法 | 适用场景 | 注意事项 |
|---|---|---|
| 线性插值 | 短段缺失(1-5个点) | 变化平缓时效果好 |
| 时间插值 | 采样间隔不均匀 | 需要时间索引 |
| 多项式插值 | 有一定趋势的数据 | 阶数过高易震荡 |
| 样条插值 | 平滑变化的数据 | 计算量较大 |
知识体系总览
下面这张图,是我自己总结的缺失值处理决策流程。每次做项目前,我都会过一遍:
这张图的逻辑很简单:先看缺失比例,再看缺失模式,最后选方法。我每次做项目都会把这个流程贴在工位旁边,省得自己凭感觉乱选。
好了,缺失值处理这块,核心就这些。说白了,没有万能的方法,关键是要理解你的数据是怎么缺失的。下次拿到风电数据,先别急着跑模型,花10分钟看看缺失值的「长相」,你会少走很多弯路。
公众号:蓝海资料掘金营,微信deep3321