4. 缺失值处理策略:别让数据缺口毁了你的模型

做风电数据清洗这么多年,我最大的感触就是——数据永远不会完美。你从SCADA系统拉下来的数据,十有八九带着各种“窟窿”。今天我们就聊聊怎么补这些窟窿。

我个人习惯把缺失值分成三类,处理方式完全不同。你想想看,要是把停机时段的数据和传感器故障的数据混在一起处理,那模型不跑偏才怪。

4.1 缺失值类型:先搞清楚“为什么缺”

我刚开始做风电项目时,看到缺失值就一股脑用均值填充。结果呢?模型在限电时段预测得一塌糊涂。后来才明白,缺失的原因决定了处理的方法

4.1.1 随机缺失(MCAR)

说白了就是“纯属意外”。比如传感器偶尔抽风,或者通信丢包。这种缺失跟数据本身没关系,处理起来最简单。

  • 特征:缺失位置没有规律,像随机撒点
  • 常见场景:单个传感器瞬时故障、网络波动
  • 处理方式:直接删除或简单插值都行
我的经验:随机缺失占比小于5%时,我一般直接删掉。超过这个比例,就得考虑插值了。

4.1.2 非随机缺失(MNAR)

这才是真正头疼的。缺失的原因跟数据值本身有关。比如风速太高导致传感器损坏,或者温度过低设备停摆。

  • 特征:缺失集中在某些特定工况下
  • 常见场景:极端天气下传感器失效、设备老化导致数据丢失
  • 处理方式:需要结合工况信息,不能简单插值
注意:非随机缺失如果处理不当,会引入系统性偏差。我曾经在一个项目中,因为忽略了低温工况下的缺失,导致模型在冬季预测偏差达到15%。

4.2 插值方法:怎么补才靠谱?

搞清楚了缺失类型,接下来就是动手补数据。我常用的方法有三种,各有各的适用场景。

4.2.1 线性插值

最简单也最直观。用缺失点前后两个有效值,拉一条直线算中间值。

import pandas as pd
import numpy as np

# 线性插值示例
df['power_linear'] = df['active_power'].interpolate(method='linear')

# 我个人习惯限制最大插值间隔
df['power_limited'] = df['active_power'].interpolate(
    method='linear', 
    limit_area='inside', 
    limit=5  # 最多连续插5个点
)

适用场景:短时间内的随机缺失,比如1-2个采样点。时间太长就不准了,毕竟风速变化不是线性的。

4.2.2 前向填充

用上一个有效值填充后面的缺失。说白了就是“假设数据没变”。

# 前向填充
df['power_ffill'] = df['active_power'].fillna(method='ffill')

# 限电时段我常用这个
df.loc[df['curtailment_flag'] == 1, 'power'] = df['power'].fillna(method='ffill')

适用场景:停机时段、限电时段。因为在这些时段里,功率确实应该保持在一个稳定值(比如0或者限电上限)。

避坑指南:我曾经在限电时段用了线性插值,结果补出来的功率值忽高忽低,完全不符合实际。后来改成前向填充,效果立竿见影。

4.2.3 KNN插值

这个方法比较高级。找跟缺失点最相似的K个邻居,用它们的值来估算。

from sklearn.impute import KNNImputer

# 注意:KNN插值需要先标准化
imputer = KNNImputer(n_neighbors=5, weights='distance')

# 选取相关特征进行插值
features = ['wind_speed', 'rotor_speed', 'pitch_angle', 'temp_ambient']
df_imputed = pd.DataFrame(
    imputer.fit_transform(df[features]),
    columns=features
)

适用场景:复杂工况下的缺失,尤其是多个传感器同时失效时。KNN能综合考虑风速、转速、桨距角等多个维度的信息。

方法 优点 缺点 推荐场景
线性插值 简单快速 假设线性变化 短时随机缺失
前向填充 保持稳态 忽略变化趋势 停机/限电时段
KNN插值 多维信息利用 计算量大 复杂工况缺失

4.3 风电场景下的特殊处理

嗯,这里要注意。风电数据有两个特殊场景,处理方式跟常规工业数据完全不同。

4.3.1 停机时段

风机停机时,功率、转速这些值就是0。但你不能简单地把缺失值填0,因为停机本身就是一个状态。

# 我的处理流程
# 1. 先识别停机时段
df['is_stopped'] = (df['active_power'] < 0.01) & (df['rotor_speed'] < 0.1)

# 2. 停机时段内的缺失,填0
df.loc[df['is_stopped'], 'active_power'] = df.loc[df['is_stopped'], 'active_power'].fillna(0)

# 3. 但要注意:停机前后的数据不要插值跨越
# 我曾经犯过这个错,把停机前后的数据连起来插值,结果补出了负功率
关键点:停机时段和正常运行时段要分开处理。千万别把停机前后的数据连起来做插值,那会补出完全不符合物理规律的值。

4.3.2 限电时段

限电比停机更麻烦。风机还在转,但功率被人为限制在一个上限以下。这时候的缺失值,不能简单填0,也不能用正常时段的插值。

# 限电时段的处理
# 1. 标记限电时段
df['is_curtailed'] = (df['available_power'] - df['active_power']) > 0.05 * df['rated_power']

# 2. 限电时段内,用前向填充更合理
df.loc[df['is_curtailed'], 'active_power'] = df.loc[
    df['is_curtailed'], 'active_power'
].fillna(method='ffill')

# 3. 如果限电时段开始时有缺失,用限电上限填充
limit = df.loc[df['is_curtailed'], 'active_power'].max()
df.loc[df['is_curtailed'], 'active_power'] = df.loc[
    df['is_curtailed'], 'active_power'
].fillna(limit)

我的经验:限电时段的缺失,用前向填充效果最好。因为限电指令一旦下发,功率会稳定在一个值附近,不会剧烈波动。

4.4 知识体系总览

下面这张图,是我梳理的缺失值处理整体流程。你照着这个思路走,基本不会出大问题。

缺失值处理策略知识体系 随机缺失 (MCAR) 非随机缺失 (MNAR) 线性插值 前向填充 KNN插值 停机时段处理 限电时段处理 先分类 → 选方法 → 分场景处理 避免跨越工况插值,保留状态标记
核心原则:先判断缺失类型,再选插值方法,最后结合风电场景做特殊处理。三步走,缺一不可。

最后说一句,缺失值处理没有银弹。我见过太多人拿着一两种方法走天下,结果在不同项目里反复踩坑。你最好在自己的数据集上多试几种方法,对比效果再定。

嗯,今天就聊到这儿。记住:数据清洗不是体力活,是技术活。把缺失值处理好了,后面的建模工作能省一半力气。


蓝海数据掘金营,专注资料整理