滞后特征:将时间序列的过去值转化为输入特征

FreeGuideOnline 最新 2026-06-27

滞后特征:将时间序列的过去值转化为输入特征

在时间序列预测和时序建模中,一个核心思想是:过去的信息蕴含着未来的信号。滞后特征(Lag Features)就是将这种思想工程化的手段——它把时间序列在历史时刻的取值,平移后作为当前时刻的输入特征。本篇教程将带你从原理到实践,系统掌握滞后特征的构建方法与关键考量。


什么是滞后特征?

滞后特征是指将原始时间序列值按照固定时间步长向后平移,形成新的列。如果原始序列为 $y_t$,那么滞后阶数为 $k$ 的滞后特征就是 $y_{t-k}$。例如,对于月度销售额,lag_1 表示上个月的销售额,lag_3 表示三个月前的销售额。

这一转换将时间序列问题转化为标准的监督学习问题:用过去的 $y$ 来预测未来的 $y$。你不再需要显式地处理时间索引,而是让模型从表格化的特征-标签对中学习时序依赖。


为什么需要滞后特征?——从自相关说起

滞后特征的理论基础是自相关(Autocorrelation),即序列与其自身滞后版本之间的相关性。如果自相关图显示滞后 $k$ 处的相关系数显著非零,说明 $y_{t-k}$ 包含预测 $y_t$ 的有效信息。

实践中,滞后特征的价值体现在:

  • 直接将时序记忆注入模型:让不具备内在状态记忆的模型(如线性回归、随机森林)也能捕获时间依赖。
  • 可解释性强:系数或重要性分数能清晰反映“多少个月前的值对当前影响最大”。
  • 配合工程特征使用:滞后特征可作为基础层,与滑动统计、日期特征等组合,构建强大的预测变量集。

手动构建滞后特征

构建滞后特征最直接的方法是使用 pandasshift() 函数。假设你有一个包含日期索引和数值列的数据框:

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_1NaN,因为之前没有值;lag_3 前三行均为 NaN。这些缺失值需要在建模前处理。

滞后阶数的选择需结合业务周期:日数据常选1、2、7;周数据选1、4;月数据选1、3、6、12。也可以借助偏自相关函数(PACF)图,选择 PACF 显著的滞后阶数。


使用自动化工具创建多阶滞后

手动编写 shift() 对大量滞后阶数来说繁琐且容易出错。推荐使用 scikit-learn 风格的转换器或专门的时间序列库:

方法一:sktimeLag 转换器

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']])

方法二:利用 pandasshift 循环

lags = [1, 2, 3, 6, 12]
for lag in lags:
    df[f'lag_{lag}'] = df['sales'].shift(lag)

方法三:feature-engineLagFeatures

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_1lag_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_1lag_7 的权重较高,印证了短期和周期依赖。


适用与不适用场景

适用情境

  • 平稳或趋势性较弱的数据。
  • 规律性明显的业务指标(库存、销售、网站流量)。
  • 作为树模型、线性模型的时序特征基线。
  • 与深度学习模型(LSTM)中的序列输入形成互补的显式特征。

需谨慎使用

  • 极高频非平稳序列(如秒级金融数据),直接滞后可能捕捉大量噪声。
  • 存在外部冲击且无协变量的情况,滞后特征无法捕捉突变。
  • 序列过长且希望压缩历史信息时,单纯的滞后特征可能维度爆炸,宜采用滑动统计量或序列编码。

总结与最佳实践

  1. 从业务周期出发选择初始滞后集(如7天、30天、4个季度),再用统计检验(ADF、PACF)精炼。
  2. 严防数据泄漏:始终在时间分割后构建滞后,或使用交叉验证时采用滚动/扩展窗口。
  3. 处理缺失值:优先删除初始的 NaN 行,保证数据清洁。
  4. 组合使用:滞后特征 + 移动平均 + 时间哑变量,通常能取得稳健的基线效果。
  5. 监控模型反馈:当预测性能下降时,重新分析自相关性,调整滞后窗口。

滞后特征是时间序列机器学习大厦的基石。掌握它,你就拥有了将单一序列转化为丰富监督学习问题的钥匙。