缺失值处理:缺失值识别、均值/中位数填充、前向/后向填充、插值法

做风电数据分析,最头疼的问题之一就是缺失值。

我刚开始接触风电数据时,拿到一个风场三年的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())
)
我的经验: 千万别全局填充。不同机组的工况不同,A机组的平均风速可能是8m/s,B机组可能是6m/s。混在一起填充,等于引入噪声。

我曾经在一个项目中,用均值填充了某台风机的功率缺失值,结果模型训练出来,那台风机的预测误差比其他机组高了30%。后来一查,那台风机长期处于限电状态,功率本身就偏低,用全场均值去填,等于硬生生把它的功率「拔高」了。

避坑指南: 如果缺失值占比超过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个点) 变化平缓时效果好
时间插值 采样间隔不均匀 需要时间索引
多项式插值 有一定趋势的数据 阶数过高易震荡
样条插值 平滑变化的数据 计算量较大
避坑指南: 我曾经用3阶多项式插值去补一段连续缺失20个点的风速数据,结果插出来的值出现了明显的「过冲」——风速突然飙到25m/s,而前后数据只有8m/s。后来查资料才知道,多项式插值在缺失段较长时,边界处容易震荡。所以,缺失段越长,插值方法越要保守。

知识体系总览

下面这张图,是我自己总结的缺失值处理决策流程。每次做项目前,我都会过一遍:

缺失值处理决策流程 识别缺失值 缺失比例 < 5%? 直接删除或简单填充 判断缺失模式 随机缺失 → 插值法 连续缺失 → 前/后向填充 非随机缺失 → 标记+建模 验证填充效果

这张图的逻辑很简单:先看缺失比例,再看缺失模式,最后选方法。我每次做项目都会把这个流程贴在工位旁边,省得自己凭感觉乱选。

个人习惯: 不管用什么方法填充,我都会保留一个「是否填充」的标记列。这样模型训练时,可以把这个标记作为特征输入,让模型自己学习「填充值」和「真实值」之间的差异。

好了,缺失值处理这块,核心就这些。说白了,没有万能的方法,关键是要理解你的数据是怎么缺失的。下次拿到风电数据,先别急着跑模型,花10分钟看看缺失值的「长相」,你会少走很多弯路。


公众号:蓝海资料掘金营,微信deep3321