第4章 特征工程基础:时间特征提取与滑动窗口统计
各位同学好,我是老张。今天咱们聊聊特征工程里最基础、也最容易被忽视的一块——时间特征提取和滑动窗口统计。说实话,我见过不少做功率预测的同行,模型选得挺花哨,结果特征没做好,预测精度就是上不去。嗯,这章咱们就把这块地基打牢。
核心观点:时间序列预测,特征工程比模型更重要。好的特征能让简单模型跑出好结果,烂的特征再牛的模型也救不了。
4.1 时间特征提取:把时钟变成数字
时间本身不是数字,但我们可以从时间戳里提取出各种有用的数值特征。我个人习惯把时间特征分成三类:周期性特征、趋势性特征、事件性特征。
4.1.1 小时特征
小时特征是最直接的。但有个坑——小时是循环的,23点和0点其实只差1小时,但数值上差了23。直接扔给模型,它会觉得23和0差很远,这不对。
我常用的做法是双编码:
# 小时的双编码
hour_sin = np.sin(2 * np.pi * hour / 24)
hour_cos = np.cos(2 * np.pi * hour / 24)
这样就把小时映射到了单位圆上。23点和0点在圆上的距离就很近了。我在做光伏功率预测时,这个技巧让模型精度提升了大概3%。
4.1.2 星期特征
星期几对功率预测影响很大。周一和周末的用电模式完全不同。我建议把星期也做双编码,周期是7天:
day_sin = np.sin(2 * np.pi * dayofweek / 7)
day_cos = np.cos(2 * np.pi * dayofweek / 7)
另外,我习惯加一个is_weekend的0/1特征。这个特征在风电预测里特别管用——周末工厂停工,用电负荷下降,风机可能被限功率。
4.1.3 季节特征
季节对新能源功率的影响太大了。夏天光照强但可能多云,冬天风大但日照短。我一般用月份来编码季节:
# 月份的双编码(周期12个月)
month_sin = np.sin(2 * np.pi * month / 12)
month_cos = np.cos(2 * np.pi * month / 12)
小技巧:如果你有历史数据,可以算一下每个季节的平均功率,然后做一个"季节基准值"特征。这比单纯用月份编码更直接。
4.2 滑动窗口统计特征:让历史说话
滑动窗口统计,说白了就是看过去一段时间的数据表现。我刚开始做预测时,只用了当前时刻的数据,结果模型像个瞎子——它不知道趋势是往上还是往下。
4.2.1 均值特征
过去1小时、3小时、6小时的平均功率,能反映近期趋势。我一般会设置多个窗口:
# 多个滑动窗口均值
df['mean_1h'] = df['power'].rolling(window=12).mean() # 5分钟粒度,12个点=1小时
df['mean_3h'] = df['power'].rolling(window=36).mean()
df['mean_6h'] = df['power'].rolling(window=72).mean()
为什么要多个窗口?因为不同时间尺度反映的信息不同。短窗口捕捉快速变化,长窗口反映整体趋势。
4.2.2 方差特征
方差反映波动程度。风电功率波动大,方差特征特别重要。我记得有一次做风电场预测,加了方差特征后,模型对突变点的预测准确率提高了15%。
df['var_1h'] = df['power'].rolling(window=12).var()
df['var_3h'] = df['power'].rolling(window=36).var()
注意:方差对异常值很敏感。如果数据采集偶尔有跳变,建议先做异常值处理,再算方差。我曾经因为一个传感器故障,方差特征直接爆表,模型全乱了。
4.2.3 最大值与最小值
最大值和最小值能告诉你波动的范围。比如过去1小时的最大功率,可以暗示当前时刻是否接近峰值:
df['max_1h'] = df['power'].rolling(window=12).max()
df['min_1h'] = df['power'].rolling(window=12).min()
我个人还喜欢加一个range特征(最大值减最小值),它能直观反映波动幅度。
4.3 滞后特征构建:让模型记住过去
滞后特征,就是把过去某个时刻的值直接拿过来用。比如预测t时刻的功率,把t-1、t-2、t-3时刻的功率也作为特征。
为什么需要滞后特征?因为功率变化是有惯性的。光伏功率不会突然从100跳到0,风电功率也不会瞬间从50飙到200。滞后特征能捕捉这种连续性。
# 构建滞后特征
df['lag_1'] = df['power'].shift(1) # 前1个时刻
df['lag_2'] = df['power'].shift(2) # 前2个时刻
df['lag_3'] = df['power'].shift(3) # 前3个时刻
df['lag_6'] = df['power'].shift(6) # 前6个时刻(半小时前)
关键点:滞后阶数的选择很重要。太少了信息不够,太多了引入噪声。我一般用偏自相关函数(PACF)来辅助选择滞后阶数。
4.4 知识体系总览
下面这张图总结了本章的核心内容,大家可以对照着梳理思路:
4.5 实战避坑指南
讲几个我踩过的坑,你们以后遇到了能少走弯路:
- 数据泄露问题:滑动窗口和滞后特征只能用历史数据,不能用未来数据。我见过有人不小心用了未来窗口的均值,结果模型在测试集上表现完美,一上线就崩。
- 窗口边界处理:前几个时间点没有足够的历史数据,会生成NaN。我习惯用
fillna(method='bfill')或者直接丢弃前几个样本。 - 特征冗余:不要一股脑把所有窗口都加上。窗口太多会导致特征高度相关,模型容易过拟合。我一般先用相关性分析筛选一遍。
- 滞后阶数选择:不是越多越好。我曾经试过加20个滞后特征,结果模型训练时间翻倍,精度反而下降了。用PACF图辅助选择,一般3-6个就够了。
我的经验:对于超短期功率预测(未来15分钟到1小时),滞后特征比滑动窗口特征更重要。对于中长期预测(未来6小时以上),滑动窗口的均值和方差特征更关键。你可以根据预测时长调整特征权重。
好了,这章的内容就到这里。特征工程是个细致活,多试几次,找到适合你数据的那组特征组合,比盲目堆模型强得多。