4. 数据预处理:缺失值处理、异常值检测、时间对齐与重采样
各位同学,咱们今天聊点实在的。
数据预处理这事儿,说白了就是「把脏数据洗干净」。我在风电行业摸爬滚打这些年,见过太多因为预处理没做好,后面模型怎么调都调不好的案例。你想想看,气象预报数据本身就有误差,再加上采集设备偶尔抽风、通讯中断、传感器漂移……嗯,这数据要是直接扔进模型,结果可想而知。
4.1 缺失值处理:别让空值毁了你的模型
先说缺失值。我遇到过最夸张的一次,某个测风塔连续三天没传数据,中间全是NaN。当时项目组有人提议直接删掉那三天,我说别急,咱们先看看原因。
缺失值一般分三种:
- 完全随机缺失:比如设备偶尔死机重启,这种最简单,直接删或者插值都行
- 随机缺失:比如风速大的时候传感器容易坏,这种就有偏了,得小心处理
- 非随机缺失:比如人为关闭设备,这种最麻烦,我建议先查日志
我个人习惯的做法是:
- 先看缺失比例。低于5%的,直接线性插值就够用了
- 5%-20%的,用前后时间窗口的平均值,或者用相邻测风塔的数据做回归填补
- 超过20%的,我建议你重新审视这段数据是否可用
核心原则:不要盲目删除缺失值。风电数据的时间连续性很重要,你删掉一段,可能就把一个完整的天气过程给切断了。
# 我个人常用的缺失值处理方法
import pandas as pd
import numpy as np
# 线性插值
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')
# 时间加权插值(更适合风速这种变化较快的变量)
df['wind_speed'] = df['wind_speed'].interpolate(method='time')
# 前向填充 + 后向填充组合(适合短时间缺失)
df['wind_speed'] = df['wind_speed'].fillna(method='ffill', limit=3)
df['wind_speed'] = df['wind_speed'].fillna(method='bfill', limit=3)
小技巧:对于风向这种环形变量,千万别用普通线性插值。我吃过这个亏——0度和360度其实是一个方向,但线性插值会给你算出180度来。用角度插值或者向量分解法更靠谱。
4.2 异常值检测:揪出那些「离谱」的数据点
异常值检测,说白了就是找那些「看着就不对」的数据。
我记得有一次,某台风机的功率曲线图上,风速12m/s的时候功率突然飙到3000kW,而额定功率才2000kW。一看原始数据,原来是风速传感器被鸟屎糊住了,读数偏小,导致功率风速比异常。
常用的检测方法:
- 3σ原则:简单粗暴,适合正态分布的数据。风速、温度这些可以用
- IQR方法:四分位距法,对异常值不那么敏感,我比较推荐
- 物理约束法:这个最靠谱。风速不可能超过60m/s,功率不可能超过额定值,这些硬性条件直接卡死
# IQR方法检测异常值
def detect_outliers_iqr(data, column, factor=1.5):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
return data[(data[column] < lower_bound) | (data[column] > upper_bound)]
# 物理约束法
def physical_constraint_check(df):
# 风速不可能为负
df = df[df['wind_speed'] >= 0]
# 功率不能超过额定值1.2倍(留点余量)
df = df[df['power'] <= rated_power * 1.2]
# 风向在0-360度之间
df = df[(df['wind_direction'] >= 0) & (df['wind_direction'] <= 360)]
return df
注意:异常值不一定是错误值。比如台风过境时,风速突然飙到40m/s,这在物理上是可能的。我曾经犯过把极端天气事件当异常值删掉的错误,结果模型在极端工况下完全失效。所以,先判断是「测量错误」还是「真实极端事件」。
4.3 时间对齐:让气象数据和风机数据「对上话」
这个问题我当年刚入行时踩过坑。
气象预报数据通常是整点发布的,比如每3小时一次。而风机SCADA数据是每10分钟甚至每分钟一条。两个数据源的时间戳对不上,怎么办?
我的做法是:
- 确定一个基准时间分辨率。做短期预测我一般用15分钟,做中期用1小时
- 把气象数据插值到基准时间上。注意,气象变量是空间连续的,时间上也可以用插值
- 把风机数据聚合到基准时间上。比如15分钟的平均风速、最大风速、最小风速
# 时间对齐示例
# 假设气象数据是3小时间隔,风机数据是10分钟间隔
# 1. 重采样气象数据到15分钟
weather_15min = weather_data.resample('15T').interpolate(method='cubic')
# 2. 聚合风机数据到15分钟
wind_turbine_15min = wind_turbine_data.resample('15T').agg({
'wind_speed': 'mean',
'power': 'mean',
'wind_direction': lambda x: np.mean(np.radians(x)) # 风向用角度平均
})
# 3. 合并两个数据集
merged_data = pd.merge_asof(
wind_turbine_15min,
weather_15min,
left_index=True,
right_index=True,
direction='nearest'
)
避坑指南:我曾经直接用merge函数按时间戳精确匹配,结果发现匹配上的数据不到一半。后来改用merge_asof,允许一定的时间偏差,匹配率直接拉到95%以上。记住,气象预报和实际观测之间本来就有时间偏差,别卡太死。
4.4 重采样:找到最适合你的时间粒度
重采样这事儿,说白了就是「改变数据的时间颗粒度」。
你想想看,做超短期预测(未来4小时),用15分钟的数据就够了。但做中期预测(未来3天),用1小时的数据更合适。数据太密了计算量大,太疏了又丢失细节。
我一般这样选:
| 预测时长 | 推荐时间分辨率 | 说明 |
|---|---|---|
| 0-4小时(超短期) | 10-15分钟 | 需要捕捉风速的快速变化 |
| 4-24小时(短期) | 30分钟-1小时 | 平衡精度和计算效率 |
| 1-7天(中期) | 1-3小时 | 关注天气系统演变,细节可以牺牲 |
# 重采样示例
# 从10分钟重采样到1小时
# 降采样(聚合)
hourly_data = df.resample('1H').agg({
'wind_speed': ['mean', 'max', 'min', 'std'],
'power': ['mean', 'max', 'min'],
'wind_direction': 'mean'
})
# 升采样(插值)
# 从1小时插值到15分钟
finer_data = hourly_data.resample('15T').interpolate(method='quadratic')
我的经验:重采样时别只用平均值。风速的波动性很重要,我习惯同时保留均值、最大值、最小值和标准差。这样模型能学到更多信息。比如,平均风速5m/s但标准差很大,说明风在剧烈波动,这对功率预测很关键。
知识体系总览
说了这么多,咱们用一张图把整个数据预处理的流程串起来:
嗯,数据预处理这块儿就讲这么多。说白了,就是要把原始数据「伺候」好了,后面的模型才能好好干活。我见过太多人急着调模型参数,结果数据预处理没做好,白费功夫。记住一句话:垃圾进,垃圾出。数据质量决定了预测的天花板。