4、数据预处理(下):特征工程(风速、风向、温度、气压等)、数据标准化与归一化
好,咱们接着聊。上一节我们把数据清洗干净了,但原始数据还不能直接喂给模型。为什么?你想想看,风速是每秒几米到几十米,温度是零下到零上几十度,气压是几百到上千百帕——这些数值的量级差太多了。
我刚开始做风电预测那会儿,直接把原始数据扔进神经网络,结果模型训练半天不收敛。后来才发现,特征工程这步没做好,模型根本学不明白。说白了,特征工程就是帮模型「翻译」数据,让它能听懂风电场在说什么。
4.1 风速特征:不只是数值那么简单
风速是最核心的特征,但直接拿原始风速值用,效果往往一般。我个人习惯做以下几件事:
- 风速的平方/立方:风功率与风速的立方成正比,这个物理关系不能丢。我一般会生成
wind_speed^2和wind_speed^3两个特征。 - 风速的滞后项:当前时刻的风速与过去几小时的风速强相关。我常用的是滞后1小时、3小时、6小时的风速值。
- 风速的滚动统计量:过去1小时的平均风速、最大风速、风速标准差。这些能反映风的「脾气」。
经验之谈:我在内蒙古一个风电场做过项目,那里的风有个特点——下午风速波动特别大。单纯用瞬时风速做预测,误差能到30%以上。后来加了风速的滚动标准差特征,误差直接降到15%以内。
# 风速特征工程示例
import pandas as pd
import numpy as np
def create_wind_features(df):
# 风速平方和立方
df['ws2'] = df['wind_speed'] ** 2
df['ws3'] = df['wind_speed'] ** 3
# 滞后特征
df['ws_lag1'] = df['wind_speed'].shift(1)
df['ws_lag3'] = df['wind_speed'].shift(3)
df['ws_lag6'] = df['wind_speed'].shift(6)
# 滚动统计量(过去6小时窗口)
df['ws_roll_mean'] = df['wind_speed'].rolling(6).mean()
df['ws_roll_std'] = df['wind_speed'].rolling(6).std()
df['ws_roll_max'] = df['wind_speed'].rolling(6).max()
return df
4.2 风向特征:别把它当普通数值
风向是个环形变量,0度和360度其实是一个方向。如果你直接拿数值去算,模型会以为0和360差很远,这显然不对。
我处理风向的常用方法:
- 分解为正弦和余弦:这是最经典的做法。把风向角度拆成
sin(θ)和cos(θ),这样就能保留方向的连续性。 - 风向的类别特征:把360度分成16个扇区(每个22.5度),做one-hot编码。这个在传统机器学习模型里效果不错。
- 风向与风速的交互:某个方向来的风,风速和功率的关系可能不一样。我习惯把风向扇区和风速做交叉特征。
小技巧:风向分解时记得把角度转成弧度。我见过有人直接用角度算sin和cos,结果完全不对——sin(360°)和sin(0°)确实一样,但中间的计算过程会出问题。
# 风向特征工程
def create_wind_direction_features(df):
# 角度转弧度
rad = np.deg2rad(df['wind_direction'])
# 正弦和余弦
df['wd_sin'] = np.sin(rad)
df['wd_cos'] = np.cos(rad)
# 风向扇区(16个方向)
df['wd_sector'] = (df['wind_direction'] // 22.5).astype(int)
df['wd_sector'] = df['wd_sector'] % 16
# 风向与风速交互
df['wd_ws_interact'] = df['wd_sin'] * df['wind_speed']
return df
4.3 温度与气压特征:别小看它们
温度和气压对空气密度有直接影响,而空气密度又影响风能转换效率。我记得在云南一个高海拔风电场,空气密度比平原低20%左右,同样的风速,发电量差一大截。
我常用的处理方式:
- 温度:直接使用,但建议做差分(当前温度减去前一小时温度),能反映温度变化趋势。
- 气压:同样直接使用,气压变化往往预示着天气系统变化,对风速有前兆意义。
- 空气密度:用温度和气压计算空气密度,这是一个物理特征,比单独用温度或气压更有解释力。
# 温度和气压特征
def create_temp_pressure_features(df):
# 温度差分
df['temp_diff'] = df['temperature'].diff()
# 气压差分
df['press_diff'] = df['pressure'].diff()
# 计算空气密度(理想气体公式)
# ρ = P / (R * T),R为气体常数287.058 J/(kg·K)
R = 287.058
df['air_density'] = df['pressure'] * 100 / (R * (df['temperature'] + 273.15))
return df
4.4 数据标准化与归一化
这一步很多人容易搞混。标准化和归一化是两回事,我简单说下区别:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| 归一化(Min-Max) | (x - min) / (max - min) | [0, 1] | 数据有明确边界,如风向角度 |
| 标准化(Z-score) | (x - μ) / σ | 均值为0,标准差为1 | 数据分布近似正态,如风速 |
| 鲁棒缩放 | (x - median) / IQR | 无固定范围 | 数据有异常值 |
注意:标准化和归一化的参数(均值、标准差、最小值、最大值)必须用训练集计算,然后应用到验证集和测试集。我曾经见过有人对整个数据集做标准化,结果模型在线上部署时完全失效——因为新数据的分布和整体数据不一样。
# 标准化与归一化
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化(推荐用于风速、温度等)
scaler_std = StandardScaler()
X_train_scaled = scaler_std.fit_transform(X_train)
X_val_scaled = scaler_std.transform(X_val)
X_test_scaled = scaler_std.transform(X_test)
# 归一化(推荐用于风向的sin/cos)
scaler_mm = MinMaxScaler(feature_range=(-1, 1))
X_train_mm = scaler_mm.fit_transform(X_train)
X_val_mm = scaler_mm.transform(X_val)
X_test_mm = scaler_mm.transform(X_test)
4.5 特征工程整体流程
下面这张图是我做特征工程时常用的流程,你可以参考一下:
嗯,到这里特征工程的核心内容就讲完了。你可能会问,这些特征都要用吗?我的建议是:先全部生成,然后用特征重要性分析(比如随机森林的特征重要性)筛选一遍。别怕特征多,就怕特征不够。
避坑指南:我曾经在一个项目里,把所有特征一股脑全用上,结果模型过拟合得一塌糊涂。后来用L1正则化做特征选择,才把有效特征从50个压缩到15个。记住,特征不是越多越好,关键是每个特征都要有物理意义。