4、数据预处理(下):特征工程(风速、风向、温度、气压等)、数据标准化与归一化

好,咱们接着聊。上一节我们把数据清洗干净了,但原始数据还不能直接喂给模型。为什么?你想想看,风速是每秒几米到几十米,温度是零下到零上几十度,气压是几百到上千百帕——这些数值的量级差太多了。

我刚开始做风电预测那会儿,直接把原始数据扔进神经网络,结果模型训练半天不收敛。后来才发现,特征工程这步没做好,模型根本学不明白。说白了,特征工程就是帮模型「翻译」数据,让它能听懂风电场在说什么。

4.1 风速特征:不只是数值那么简单

风速是最核心的特征,但直接拿原始风速值用,效果往往一般。我个人习惯做以下几件事:

  • 风速的平方/立方:风功率与风速的立方成正比,这个物理关系不能丢。我一般会生成 wind_speed^2wind_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 特征工程整体流程

下面这张图是我做特征工程时常用的流程,你可以参考一下:

特征工程整体流程 原始数据 风速特征 平方/立方、滞后项 滚动统计量 风向特征 sin/cos分解 扇区编码、交互特征 温压特征 差分、空气密度 趋势变化 特征合并 标准化 / 归一化

嗯,到这里特征工程的核心内容就讲完了。你可能会问,这些特征都要用吗?我的建议是:先全部生成,然后用特征重要性分析(比如随机森林的特征重要性)筛选一遍。别怕特征多,就怕特征不够。

避坑指南:我曾经在一个项目里,把所有特征一股脑全用上,结果模型过拟合得一塌糊涂。后来用L1正则化做特征选择,才把有效特征从50个压缩到15个。记住,特征不是越多越好,关键是每个特征都要有物理意义。

专注资料整理