滞后特征:将时间序列的过去值转化为输入特征
滞后特征:将时间序列的过去值转化为输入特征
在时间序列预测和时序建模中,一个核心思想是:过去的信息蕴含着未来的信号。滞后特征(Lag Features)就是将这种思想工程化的手段——它把时间序列在历史时刻的取值,平移后作为当前时刻的输入特征。本篇教程将带你从原理到实践,系统掌握滞后特征的构建方法与关键考量。
什么是滞后特征?
滞后特征是指将原始时间序列值按照固定时间步长向后平移,形成新的列。如果原始序列为 $y_t$,那么滞后阶数为 $k$ 的滞后特征就是 $y_{t-k}$。例如,对于月度销售额,lag_1 表示上个月的销售额,lag_3 表示三个月前的销售额。
这一转换将时间序列问题转化为标准的监督学习问题:用过去的 $y$ 来预测未来的 $y$。你不再需要显式地处理时间索引,而是让模型从表格化的特征-标签对中学习时序依赖。
为什么需要滞后特征?——从自相关说起
滞后特征的理论基础是自相关(Autocorrelation),即序列与其自身滞后版本之间的相关性。如果自相关图显示滞后 $k$ 处的相关系数显著非零,说明 $y_{t-k}$ 包含预测 $y_t$ 的有效信息。
实践中,滞后特征的价值体现在:
- 直接将时序记忆注入模型:让不具备内在状态记忆的模型(如线性回归、随机森林)也能捕获时间依赖。
- 可解释性强:系数或重要性分数能清晰反映“多少个月前的值对当前影响最大”。
- 配合工程特征使用:滞后特征可作为基础层,与滑动统计、日期特征等组合,构建强大的预测变量集。
手动构建滞后特征
构建滞后特征最直接的方法是使用 pandas 的 shift() 函数。假设你有一个包含日期索引和数值列的数据框:
import pandas as pd
# 示例数据:每日销量
df = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=10, freq='D'),
'sales': [10, 12, 13, 11, 14, 16, 15, 18, 20, 19]
})
df = df.set_index('date')
创建滞后1步和滞后3步的特征:
df['lag_1'] = df['sales'].shift(1)
df['lag_3'] = df['sales'].shift(3)
此时,第一行 lag_1 为 NaN,因为之前没有值;lag_3 前三行均为 NaN。这些缺失值需要在建模前处理。
滞后阶数的选择需结合业务周期:日数据常选1、2、7;周数据选1、4;月数据选1、3、6、12。也可以借助偏自相关函数(PACF)图,选择 PACF 显著的滞后阶数。
使用自动化工具创建多阶滞后
手动编写 shift() 对大量滞后阶数来说繁琐且容易出错。推荐使用 scikit-learn 风格的转换器或专门的时间序列库:
方法一:sktime 的 Lag 转换器
from sktime.transformations.series.lag import Lag
import numpy as np
# 创建1到4阶的所有滞后
lagger = Lag(lags=np.arange(1, 5), index_out='original')
df_lagged = lagger.fit_transform(df[['sales']])
方法二:利用 pandas 的 shift 循环
lags = [1, 2, 3, 6, 12]
for lag in lags:
df[f'lag_{lag}'] = df['sales'].shift(lag)
方法三:feature-engine 的 LagFeatures
from feature_engine.timeseries.forecasting import LagFeatures
lag_f = LagFeatures(variables=['sales'], periods=[1,2,3])
df_lagged = lag_f.fit_transform(df)
这些工具都能自动处理变量名称、保留原始索引,并与预测流程无缝集成。
关键注意事项与陷阱
1. 数据泄漏
构建滞后特征必须在时间上严格切割。如果你对整个历史数据创建滞后,然后随机划分训练/测试集,未来信息会泄露到训练集中。正确做法:先按时间点切割数据集,再对训练集和测试集分别构建滞后特征,或使用带内置保护的时序交叉验证。
2. 缺失值处理
阶数越高,序列开头的 NaN 越多。常见的处理方法:
- 删除开头的空值行(当数据充足时)。
- 使用向前填充(如
fillna(method='ffill'))需谨慎,避免引入未来信息。 - 对于树模型,可以设定一个特殊值(如 -999),但更推荐直接丢弃或使用
pd.NA。
3. 多重共线性
相邻滞后阶数之间往往高度相关(例如 lag_1 和 lag_2 可能相关系数 >0.9)。这对线性模型不利,可考虑:
- 使用正则化(Lasso/Ridge)。
- 选取 PACF 显著的稀疏滞后集。
- 应用滑动窗口统计量(如7日移动平均)替代连续多阶滞后。
4. 时间索引的对齐
确保滞后操作后索引仍然连续且无偏移。使用 dropna() 后可能打破时间连续性,在需要完整时间跨度的场景下要特别注意。
滞后特征的高级变体
除了原始值的滞后,还可以构造:
- 差分滞后:先差分再滞后,同时捕获变化量与滞后依赖,适合非平稳序列。
- 滞后窗口聚合:取过去
p个周期的均值、最大值、标准差等作为特征,减少维度并抑制噪声。 - 周期性滞后:对于强季节性的序列,直接使用季节阶数的滞后(如
shift(7)代表上周同日),效果往往优于短程滞后。
示例:创建7天滚动均值作为滞后特征
df['rolling_mean_7'] = df['sales'].shift(1).rolling(7).mean()
先用 shift(1) 避免使用当天信息,再计算过去7天的均值,形成既滞后又平滑的特征。
从滞后特征到监督学习矩阵
滞后特征构建的最终目标是将序列转换为监督学习的 (X, y) 格式。一个通用的自定义函数如下:
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
data: 单列序列(DataFrame)
n_in: 用作输入特征的滞后阶数
n_out: 预测步数(可生成多个未来目标)
"""
cols = list()
# 输入序列 (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(data.shift(i))
# 预测序列 (t, t+1, ... t+n_out-1)
for i in range(0, n_out):
cols.append(data.shift(-i))
agg = pd.concat(cols, axis=1)
if dropnan:
agg.dropna(inplace=True)
# 重命名列
agg.columns = ['lag_{}'.format(i) for i in range(n_in, 0, -1)] + \
['y_t+{}'.format(i) for i in range(n_out)]
return agg
这个函数可灵活控制使用多少个过去点,以及预测未来多少步,是循环神经网络与Transformer建模前常用的预处理步骤。
完整示例:用滞后特征预测零售销量
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# 生成模拟数据
np.random.seed(42)
date_rng = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D')
sales = 50 + 5 * np.sin(2 * np.pi * date_rng.dayofyear / 365) + np.random.normal(0, 3, len(date_rng))
df = pd.DataFrame({'sales': sales}, index=date_rng)
# 创建滞后特征
for lag in [1, 2, 3, 7, 14]:
df[f'lag_{lag}'] = df['sales'].shift(lag)
df_model = df.dropna().copy() # 移除NaN行
# 划分训练/测试(按时间)
split_date = '2023-10-01'
train = df_model.loc[:split_date]
test = df_model.loc[split_date:]
# 特征与目标
feature_cols = [col for col in df_model.columns if col.startswith('lag_')]
X_train, y_train = train[feature_cols], train['sales']
X_test, y_test = test[feature_cols], test['sales']
# 训练线性模型
lr = LinearRegression()
lr.fit(X_train, y_train)
preds = lr.predict(X_test)
print(f'测试集 RMSE: {mean_squared_error(y_test, preds, squared=False):.4f}')
输出显示模型利用历史滞后值成功捕捉了序列的模式。我们还可以查看系数:
coef_series = pd.Series(lr.coef_, index=feature_cols)
print(coef_series.sort_values(ascending=False))
往往 lag_1 和 lag_7 的权重较高,印证了短期和周期依赖。
适用与不适用场景
✅ 适用情境:
- 平稳或趋势性较弱的数据。
- 规律性明显的业务指标(库存、销售、网站流量)。
- 作为树模型、线性模型的时序特征基线。
- 与深度学习模型(LSTM)中的序列输入形成互补的显式特征。
❌ 需谨慎使用:
- 极高频非平稳序列(如秒级金融数据),直接滞后可能捕捉大量噪声。
- 存在外部冲击且无协变量的情况,滞后特征无法捕捉突变。
- 序列过长且希望压缩历史信息时,单纯的滞后特征可能维度爆炸,宜采用滑动统计量或序列编码。
总结与最佳实践
- 从业务周期出发选择初始滞后集(如7天、30天、4个季度),再用统计检验(ADF、PACF)精炼。
- 严防数据泄漏:始终在时间分割后构建滞后,或使用交叉验证时采用滚动/扩展窗口。
- 处理缺失值:优先删除初始的
NaN行,保证数据清洁。 - 组合使用:滞后特征 + 移动平均 + 时间哑变量,通常能取得稳健的基线效果。
- 监控模型反馈:当预测性能下降时,重新分析自相关性,调整滞后窗口。
滞后特征是时间序列机器学习大厦的基石。掌握它,你就拥有了将单一序列转化为丰富监督学习问题的钥匙。