4. 特征工程基础:时间特征提取与统计特征构建
各位同学,咱们今天聊聊特征工程里最基础、也最容易被忽视的一块——时间特征。说实话,我刚入行那会儿,总觉得把历史负荷数据扔进模型就能出结果。结果呢?模型表现平平,调参调到怀疑人生。后来我才明白,特征工程才是决定模型上限的关键,而时间特征就是其中的地基。
核心观点:负荷预测的本质是挖掘时间序列中的周期性规律。没有好的时间特征,再牛的模型也白搭。
4.1 时间特征提取:小时、星期、节假日
时间特征说白了,就是把时间戳拆解成模型能理解的数值信息。我个人习惯把时间特征分成三类:周期性特征、日历特征、特殊事件特征。
4.1.1 小时特征
小时特征是最直观的。但这里有个坑——小时是循环变量。你想想看,23点和0点其实只差1小时,但如果直接用数值表示,23和0的欧氏距离是23,这显然不合理。
我在项目中遇到过这个问题,后来用了正弦-余弦编码来解决:
import numpy as np
import pandas as pd
def encode_hour(hour):
"""将小时编码为正弦-余弦特征"""
hour_rad = 2 * np.pi * hour / 24
return np.sin(hour_rad), np.cos(hour_rad)
# 示例
df['hour_sin'], df['hour_cos'] = zip(*df['hour'].apply(encode_hour))
这样处理后,23点和0点在二维空间中的距离就很近了。嗯,这里要注意,正弦-余弦编码适用于所有循环特征,比如月份、星期等。
4.1.2 星期特征
星期特征相对简单。我一般用两种方式:
- 数值编码:周一=1,周二=2,...,周日=7
- 独热编码:将星期展开为7个二进制特征
我个人更推荐独热编码,尤其是当数据量不大的时候。为什么?因为星期几和负荷之间往往是非线性关系,比如周一和周五的负荷模式可能完全不同,但数值编码会让模型误以为它们之间有线性关系。
小技巧:如果你用树模型(如LightGBM、XGBoost),数值编码就够了。树模型对特征顺序不敏感,独热编码反而会增加特征维度。
4.1.3 节假日特征
节假日是负荷预测的难点。我曾经踩过一个坑——直接把节假日当成普通工作日处理,结果预测误差飙升。后来我总结了一套处理方案:
- 构建节假日标签:0表示非节假日,1表示节假日
- 区分节假日类型:春节、国庆等长假和普通周末的负荷模式完全不同
- 添加节前/节后特征:比如节前1天、节后1天的负荷往往有特殊模式
def create_holiday_features(df, holiday_dates):
"""构建节假日特征"""
df['is_holiday'] = df['date'].isin(holiday_dates).astype(int)
df['days_to_holiday'] = (df['date'] - df['date'].shift(-1)).dt.days
df['days_after_holiday'] = (df['date'] - df['date'].shift(1)).dt.days
return df
4.2 滞后特征:让模型记住历史
滞后特征,说白了就是用过去的数据预测未来。这是时间序列预测最核心的特征之一。
我记得刚开始做负荷预测时,只用了前1小时的负荷作为滞后特征。结果模型在负荷突变时完全跟不上。后来我意识到,不同时间尺度的滞后特征都很重要:
| 滞后类型 | 说明 | 典型值 |
|---|---|---|
| 短期滞后 | 前1-6小时的负荷 | lag_1, lag_2, ..., lag_6 |
| 日滞后 | 前1-7天同一时刻的负荷 | lag_24, lag_48, ..., lag_168 |
| 周滞后 | 前1-4周同一时刻的负荷 | lag_168, lag_336, ..., lag_672 |
def create_lag_features(df, target_col, lags):
"""创建滞后特征"""
for lag in lags:
df[f'lag_{lag}'] = df[target_col].shift(lag)
return df
# 示例:创建前24小时和前168小时的滞后特征
lags = [1, 2, 3, 6, 12, 24, 48, 168]
df = create_lag_features(df, 'load', lags)
注意:创建滞后特征时会产生NaN值。记得用dropna()或fillna()处理,否则模型会报错。
4.3 滑动窗口统计特征:捕捉趋势与波动
滑动窗口特征,就是在某个时间窗口内计算统计量。它能帮模型捕捉负荷的短期趋势和波动情况。
我常用的滑动窗口特征包括:
- 均值:反映窗口内的平均水平
- 标准差:反映窗口内的波动程度
- 最大值/最小值:反映窗口内的极端值
- 中位数:对异常值更鲁棒
- 偏度/峰度:反映分布形态
def create_rolling_features(df, target_col, windows):
"""创建滑动窗口统计特征"""
for window in windows:
df[f'rolling_mean_{window}'] = df[target_col].rolling(window=window).mean()
df[f'rolling_std_{window}'] = df[target_col].rolling(window=window).std()
df[f'rolling_max_{window}'] = df[target_col].rolling(window=window).max()
df[f'rolling_min_{window}'] = df[target_col].rolling(window=window).min()
return df
# 示例:创建3小时、6小时、12小时的滑动窗口特征
windows = [3, 6, 12]
df = create_rolling_features(df, 'load', windows)
这里有个经验之谈——窗口大小的选择很关键。窗口太小,特征噪声大;窗口太大,特征反应迟钝。我一般会结合业务场景来选择:
- 短期预测(1-6小时):用3-12小时的窗口
- 中期预测(1-7天):用24-168小时的窗口
- 长期预测(1-4周):用168-672小时的窗口
4.4 知识体系总览
说了这么多,咱们用一张图来总结一下本章的核心内容:
总结一下:时间特征提取是负荷预测的基石。小时、星期、节假日特征帮模型理解时间规律;滞后特征让模型记住历史;滑动窗口特征捕捉趋势和波动。三者缺一不可。
好了,这一章的内容就到这里。特征工程是个细活,需要反复尝试和调整。我建议你在实际项目中,先构建基础特征,再逐步添加复杂特征,观察模型效果的变化。这样既能避免过拟合,也能找到真正有用的特征。