第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 知识体系总览

下面这张图总结了本章的核心内容,大家可以对照着梳理思路:

时间序列特征工程知识体系 特征工程 时间特征提取 滑动窗口统计特征 滞后特征构建 小时特征(双编码) 星期特征(含周末) 季节特征(月份编码) 均值(多窗口) 方差(波动程度) 最大/最小值(范围) 短滞后(t-1, t-2) 长滞后(t-6, t-12) PACF辅助选阶 目标:将时间序列转化为结构化特征矩阵

4.5 实战避坑指南

讲几个我踩过的坑,你们以后遇到了能少走弯路:

  • 数据泄露问题:滑动窗口和滞后特征只能用历史数据,不能用未来数据。我见过有人不小心用了未来窗口的均值,结果模型在测试集上表现完美,一上线就崩。
  • 窗口边界处理:前几个时间点没有足够的历史数据,会生成NaN。我习惯用fillna(method='bfill')或者直接丢弃前几个样本。
  • 特征冗余:不要一股脑把所有窗口都加上。窗口太多会导致特征高度相关,模型容易过拟合。我一般先用相关性分析筛选一遍。
  • 滞后阶数选择:不是越多越好。我曾经试过加20个滞后特征,结果模型训练时间翻倍,精度反而下降了。用PACF图辅助选择,一般3-6个就够了。

我的经验:对于超短期功率预测(未来15分钟到1小时),滞后特征比滑动窗口特征更重要。对于中长期预测(未来6小时以上),滑动窗口的均值和方差特征更关键。你可以根据预测时长调整特征权重。

好了,这章的内容就到这里。特征工程是个细致活,多试几次,找到适合你数据的那组特征组合,比盲目堆模型强得多。


专注资料整理