数据预处理:风电功率预测的基石
大家好,我是老张。在风电功率预测这个行当摸爬滚打了十来年,我越来越觉得:数据预处理做得好,模型就成功了一半。这话一点都不夸张。
你想想看,风机采集到的原始数据,那叫一个「脏」。传感器故障、通信中断、极端天气干扰……各种问题层出不穷。如果直接拿这些数据去训练模型,结果可想而知——预测值忽高忽低,调度部门天天找你喝茶。
今天这一章,我们就来聊聊数据预处理的三个核心环节:缺失值处理、异常值检测与剔除、时间戳对齐与重采样。这些都是我每天在项目中反复操练的内容,希望能帮你少走些弯路。
核心观点:数据预处理不是「洗菜」,而是「炼钢」。好的预处理能提升模型精度5%-15%,这个提升幅度,有时候比换一个更复杂的模型还要大。
3.1 缺失值处理:别让「空」数据毁了你的模型
风电数据里,缺失值是最常见的问题。传感器偶尔抽风、通信链路不稳定、维护期间停机……都会导致数据出现空洞。我个人习惯,拿到数据第一件事就是统计缺失率。
小技巧:用 df.isnull().sum() / len(df) 快速查看每列的缺失比例。如果某列缺失超过50%,我通常会直接扔掉——补出来的数据也不靠谱。
3.1.1 向前填充(Forward Fill)
向前填充,说白了就是用上一个有效值来填补当前的缺失值。这个方法在风电场景下特别实用——因为风速、功率这些物理量,短时间内变化不会太剧烈。
我在项目中遇到过这样的情况:某台风机因为通信故障,连续缺失了5分钟的数据。用向前填充补上后,模型预测误差只增加了不到2%。但如果缺失时间超过15分钟,我就不建议用这个方法了。
import pandas as pd
# 向前填充
df['power'].fillna(method='ffill', inplace=True)
# 限制最大填充步数(防止过度填充)
df['power'].fillna(method='ffill', limit=3, inplace=True)
注意:如果数据开头就是缺失值,向前填充会失效。这时候我一般会结合向后填充一起用,或者直接用插值法。
3.1.2 插值法(Interpolation)
插值法比向前填充更「聪明」一些。它会根据缺失点前后的数据趋势,估算出一个合理的值。常用的有线性插值和样条插值。
嗯,这里要注意:插值法不是万能的。如果缺失段前后数据波动很大,插出来的值可能反而会引入噪声。我曾经在一个风速剧烈变化的场景里用过样条插值,结果补出来的数据比真实值偏高了20%。后来我改用了线性插值,效果反而更好。
# 线性插值
df['wind_speed'].interpolate(method='linear', inplace=True)
# 时间插值(考虑时间间隔)
df['wind_speed'].interpolate(method='time', inplace=True)
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 向前填充 | 短时间缺失(≤3个时间点) | 简单、快速、物理意义明确 | 长时间缺失会引入偏差 |
| 线性插值 | 数据变化平缓 | 计算量小、结果平滑 | 对突变不敏感 |
| 样条插值 | 数据有周期性或趋势 | 拟合效果好 | 容易过拟合、计算量大 |
3.2 异常值检测与剔除:把「坏数据」揪出来
风电数据里的异常值,有时候比缺失值更可怕。缺失值至少你知道它缺失了,但异常值会「伪装」成正常数据,悄悄地把模型带偏。
你想想看,如果风速传感器被冻住了,输出值一直卡在0.5m/s,模型会以为「哦,原来风速一直这么小」,然后预测功率也跟着偏低。等到实际风速起来的时候,预测就完全跟不上节奏了。
3.2.1 3-sigma原则
3-sigma原则,就是假设数据服从正态分布,把超出均值±3倍标准差的数据视为异常。这个方法简单粗暴,但很有效。
我个人习惯,先用这个方法做一轮快速筛查。不过要注意——风电功率数据往往不是严格的正态分布,尤其是功率曲线两端,数据分布会偏。所以3-sigma只能作为参考,不能完全依赖。
import numpy as np
# 计算均值和标准差
mean = df['power'].mean()
std = df['power'].std()
# 标记异常值
df['is_outlier_3sigma'] = (np.abs(df['power'] - mean) > 3 * std)
# 剔除异常值
df_clean = df[~df['is_outlier_3sigma']]
经验之谈:3-sigma的阈值可以适当调整。在风电场景下,我经常用2.5倍标准差作为阈值,因为风速的波动本身就比较大,3倍可能会漏掉一些明显的异常。
3.2.2 箱线图法
箱线图法不依赖正态分布假设,它用四分位数来定义异常。具体来说,把低于Q1-1.5×IQR或高于Q3+1.5×IQR的数据视为异常。
我曾经在一个海上风电项目里,用箱线图法发现了一批「幽灵数据」——某台风机在夜间功率输出异常偏高,但风速数据却很正常。后来排查发现,是SCADA系统的时钟出了问题,把白天的数据错误地打上了夜间的时间戳。箱线图法帮我省了好几天的排查时间。
# 计算四分位数
Q1 = df['power'].quantile(0.25)
Q3 = df['power'].quantile(0.75)
IQR = Q3 - Q1
# 定义异常阈值
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记并剔除异常值
df['is_outlier_box'] = (df['power'] < lower_bound) | (df['power'] > upper_bound)
df_clean = df[~df['is_outlier_box']]
建议:两种方法结合使用。先用3-sigma做快速过滤,再用箱线图法做精细筛查。我一般会保留被两种方法同时标记为异常的数据,单独分析后再决定是否剔除。
3.3 时间戳对齐与重采样:让数据「步调一致」
风电场的设备来自不同厂家,采样频率也不一样。风速仪可能每1秒采一次,功率表每5秒采一次,温度传感器每10秒采一次。如果不做对齐,模型根本没法用。
我记得刚入行的时候,有一次直接把不同频率的数据丢进模型,结果训练出来的模型预测误差高达30%。后来才发现,是时间戳没对齐,模型学到的全是「错位」的关系。
3.3.1 时间戳对齐
对齐的核心思路是:选定一个基准时间频率,把所有数据都映射到这个频率上。我一般会用10分钟作为基准——这是风电行业的标准时间粒度,也是电网调度常用的单位。
# 将时间列设为索引
df.set_index('timestamp', inplace=True)
# 对齐到10分钟频率
df_aligned = df.resample('10T').mean()
3.3.2 重采样策略
重采样不是简单地取平均。不同场景要用不同的策略:
- 风速数据:用均值重采样。风速是连续变量,均值能反映整体趋势。
- 风向数据:用中位数或角度均值。风向是环形变量,普通均值会出问题。
- 功率数据:用均值或总和。看你是要平均功率还是总发电量。
- 状态信号:用众数或最大值。比如风机是否运行,取众数更合理。
# 不同列用不同重采样策略
df_resampled = df.resample('10T').agg({
'wind_speed': 'mean',
'wind_direction': lambda x: np.median(x), # 风向用中位数
'power': 'mean',
'status': lambda x: x.mode()[0] if not x.mode().empty else np.nan
})
避坑指南:重采样后一定要检查数据量。如果原始数据是1秒频率,重采样到10分钟,数据量会减少到原来的1/600。如果原始数据本身就有大量缺失,重采样后可能会产生新的缺失值——这时候需要回到第一步,重新做缺失值处理。
好了,数据预处理的三个核心环节就讲到这里。这些方法看起来简单,但真正用好需要大量的实践积累。我建议你找一份真实的风电数据,亲手跑一遍上面的代码,感受一下不同方法的效果差异。
记住一句话:数据预处理不是一次性工作,而是一个迭代优化的过程。每次跑完模型,回头看看预处理环节有没有可以改进的地方,慢慢你就会形成自己的「预处理心法」。
公众号:蓝海资料掘金营,微信deep3321