4、特征工程:滞后特征、滚动窗口统计量、时间编码

特征工程,说白了就是给模型喂「对的食物」。

我见过太多人,模型选得再好,调参再努力,最后效果不行——回头一看,特征全是原始值,连个滞后项都没加。嗯,这就像让一个厨师只用盐做菜,能好吃才怪。

今天咱们聊三个核心操作:滞后特征滚动窗口统计量时间编码。这三板斧,基本覆盖了90%的时序特征需求。

4.1 滞后特征:让模型「看见」过去

滞后特征,就是把过去某个时刻的值,搬到当前时刻来用。

举个例子:你想预测明天的销量,那今天的销量、昨天的销量、甚至上周同一天的销量,都是极好的特征。

我个人习惯,先做1阶滞后,再看业务周期。如果是日数据,我会做lag-1、lag-7、lag-30。为什么?因为日周期、周周期、月周期,这三个节奏基本覆盖了大部分场景。

核心公式:
lag_k(t) = value(t - k)

代码实现很简单,用pandas的shift方法就行:

import pandas as pd

# 假设df是时序数据,按日期排序
df['lag_1'] = df['value'].shift(1)
df['lag_7'] = df['value'].shift(7)
df['lag_30'] = df['value'].shift(30)
⚠️ 注意:滞后特征会产生NaN值。比如lag_7,前7行都是空的。记得用fillna或者直接丢弃这些行,别让NaN污染模型。

我曾经踩过一个坑:做滞后特征时忘了排序。数据是乱序的,shift出来的值根本不是「过去」,而是「随便某个时间点」。结果模型训练时看起来不错,上线后直接崩了。所以记住:先排序,再shift

4.2 滚动窗口统计量:捕捉「趋势」和「波动」

滞后特征只能看到「某一个点」,但很多时候,我们需要看「一段区间」的统计量。

比如:过去7天的平均销量、过去30天的销量标准差、过去14天的最大值……这些就是滚动窗口统计量。

为什么需要它?因为模型需要理解「趋势」和「波动」。你想想看,如果今天销量突然暴涨,模型光看昨天的值可能反应不过来,但如果它知道「过去7天平均销量很低」,就能判断出「今天是个异常点」。

常用的滚动统计量包括:

  • 均值:反映趋势
  • 标准差:反映波动
  • 最大值/最小值:反映极值
  • 中位数:抗噪性强
  • 偏度/峰度:反映分布形态(高阶用法)
# 滚动窗口统计量示例
df['rolling_mean_7'] = df['value'].rolling(window=7).mean()
df['rolling_std_7'] = df['value'].rolling(window=7).std()
df['rolling_max_14'] = df['value'].rolling(window=14).max()
💡 我的经验:窗口大小怎么选?我一般先看业务周期。如果是日数据,7天(周)、30天(月)、90天(季度)是三个常用窗口。如果数据有季节性,窗口最好覆盖一个完整周期。

这里有个细节:滚动窗口默认是「左闭右开」的,也就是说,当前时刻的值也会被算进去。如果你想要「纯历史」的统计量,记得用shift再偏移一下:

# 纯历史均值(不含当前时刻)
df['rolling_mean_7_pure'] = df['value'].shift(1).rolling(window=7).mean()

嗯,这个坑我踩过不止一次。有一次做实时预测,模型总是「提前知道」当前值,导致线上效果远差于离线。后来才发现,滚动窗口默认包含了当前时刻。

4.3 时间编码:让模型理解「时间」本身

时间本身也是一种特征。模型不懂「2024年3月15日」是什么意思,但你可以把它拆解成:年、月、日、星期几、第几周、是否节假日……

我建议至少做以下几类时间编码:

编码类型 示例 说明
2024 捕捉年度趋势
1-12 捕捉季节性
1-31 捕捉月度内模式
星期几 0-6 捕捉周周期
是否周末 0/1 二值化,简单有效
第几周 1-52 捕捉周级别周期
一年中的第几天 1-365 连续型,适合线性模型
# 时间编码示例
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['weekday'] = df['date'].dt.weekday  # 0=周一, 6=周日
df['is_weekend'] = (df['weekday'] >= 5).astype(int)
df['week_of_year'] = df['date'].dt.isocalendar().week
df['day_of_year'] = df['date'].dt.dayofyear
⚠️ 注意:对于树模型(如XGBoost、LightGBM),月份、星期几可以直接用整数编码。但对于线性模型或神经网络,建议做周期性编码(sin/cos变换),因为「12月」和「1月」在数值上虽然差1,但实际距离很近。

周期性编码怎么做?举个例子:

# 周期性编码示例(以月份为例)
import numpy as np

df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)

这样,12月和1月在sin/cos空间里就是相邻的点了。我一般在做深度学习模型时必加这个,树模型倒不一定需要。

4.4 本章知识体系

下面这张图,帮你理清三个特征工程操作的关系:

时序特征工程三大核心操作 原始时间序列 滞后特征 lag-1, lag-7, lag-30 捕捉「过去某个点」 滚动窗口统计量 均值、标准差、最大/最小值 捕捉「一段区间」的趋势与波动 时间编码 年、月、日、星期几 让模型理解「时间」本身 三者组合使用,效果远好于单一特征 滞后特征 + 滚动统计量 + 时间编码 = 完整时序特征集

4.5 实战建议与避坑指南

最后,分享几个我自己的经验:

  • 不要一次性生成太多滞后特征。我见过有人生成lag-1到lag-100,结果模型过拟合得一塌糊涂。先做业务分析,再选关键滞后阶数。
  • 滚动窗口的边界处理要小心。尤其是预测时,你只能用到「历史数据」,不能「偷看」未来。我建议在训练时就用shift模拟预测场景。
  • 时间编码不是越多越好。年、月、日、星期几这四个基本够用。加太多细粒度特征(比如小时、分钟),反而容易引入噪声。
  • 特征工程完成后,记得做相关性分析。如果两个特征高度相关(比如lag-1和lag-2),考虑只保留一个,或者做PCA降维。
💡 我的习惯:每次做完特征工程,我都会画一张特征重要性图。如果某个滞后特征重要性为0,说明这个滞后阶数没用,果断删掉。别舍不得。

嗯,特征工程这部分就聊到这儿。记住一句话:好的特征,比好的模型更重要。你花80%的时间做特征,20%的时间调模型,这个比例是合理的。


公众号:蓝海资料掘金营,微信deep3321