时间序列预测是数据分析中最常见的任务之一,它广泛应用于零售销售预测、股票价格分析、气象预报等领域。Kaggle作为全球最大的数据科学竞赛平台,提供了大量真实的时间序列数据集供我们练习和比赛。在开始构建复杂模型之前,我们需要先理解几个核心概念。
时间序列数据最大的特点就是它的观测值之间存在时间依赖性。举个例子,今天的销售额往往会受到昨天销售额的影响,这就是所谓的时间依赖性。为了更好地捕捉这种特性,我们需要创建一些特殊的特征。最常见的有三类特征:时间步特征、滞后特征和窗口统计特征。
时间步特征是最简单的,它直接把时间戳转化为数值特征。比如我们可以把日期转化为从开始日期算起的天数。这种特征特别适合捕捉数据的长期趋势。在实际操作中,我们可以用Python的datetime模块轻松实现:
python复制import pandas as pd
# 创建时间序列
dates = pd.date_range(start='2023-01-01', periods=365)
df = pd.DataFrame({'date': dates})
df['time_step'] = (df['date'] - df['date'].min()).dt.days
滞后特征则是将时间序列的值向后移动一定步长。比如创建滞后1天的特征,就是把昨天的值作为今天的特征。这种特征能帮助模型捕捉短期依赖关系。在Pandas中,我们可以用shift()函数轻松创建滞后特征:
python复制df['lag_1'] = df['value'].shift(1)
窗口统计特征计算的是某个时间窗口内的统计量,比如过去7天的平均值。这类特征能平滑噪声,显示出更长期的模式。Pandas提供了rolling()方法来计算这类特征:
python复制df['rolling_7_mean'] = df['value'].rolling(window=7).mean()
趋势代表时间序列长期的变化方向。识别和建模趋势是预测的重要一步。最简单的方法是使用线性回归来拟合时间步特征。但现实中的数据往往表现出更复杂的非线性趋势,这时多项式特征就派上用场了。
在实际项目中,我发现二次或三次多项式通常就能很好地捕捉大多数趋势。使用scikit-learn的PolynomialFeatures可以轻松创建这些特征:
python复制from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 创建多项式特征
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(df[['time_step']].values)
# 拟合趋势模型
trend_model = LinearRegression()
trend_model.fit(X_poly, df['value'])
移动平均是另一种识别趋势的有效方法。它通过计算滑动窗口内的平均值来平滑短期波动,突出长期趋势。选择合适的窗口大小很重要 - 太小无法有效平滑,太大会丢失重要细节。对于以周为季节性的数据,7天或14天的窗口通常效果不错。
季节性指数据中固定周期的重复模式,比如每天的交通高峰或每年的节假日销售高峰。处理季节性有两种主要方法:季节性指示器和傅里叶特征。
季节性指示器适合周期较短的情况,比如以周为周期。我们可以对周期中的每个时间点创建一个二元特征(one-hot编码)。例如,对星期几进行编码:
python复制df['day_of_week'] = df['date'].dt.dayofweek
seasonal_dummies = pd.get_dummies(df['day_of_week'], prefix='day')
df = pd.concat([df, seasonal_dummies], axis=1)
傅里叶特征更适合长周期季节性,比如年度模式。它使用正弦和余弦函数的组合来近似季节性模式,相比季节性指示器更节省特征。statsmodels库提供了方便的CalendarFourier类来生成这些特征:
python复制from statsmodels.tsa.deterministic import CalendarFourier
fourier = CalendarFourier(freq='A', order=4) # 年度季节性,4对正弦余弦
在实际应用中,我通常会先绘制季节性图来直观观察季节性模式,然后根据观察结果选择合适的特征工程方法。对于既有短周期又有长周期的数据,可以同时使用两种方法。
在时间序列预测中,没有一种模型能解决所有问题。线性回归擅长捕捉明确的趋势和季节性,但无法学习复杂的非线性关系。树模型如XGBoost能捕捉复杂模式,但不擅长外推趋势。混合模型结合两者的优势,通常能获得更好的预测性能。
混合模型的核心思想是"分而治之":先用简单模型捕捉明显的模式(如趋势),再用复杂模型学习剩余的模式(残差)。这种方法不仅提高了预测精度,还使模型更易于解释。
让我们通过一个零售销售预测的案例来看看如何构建混合模型。假设我们有一家零售店的历史销售数据,目标是预测未来一个月的销售额。
首先,我们使用线性回归来拟合趋势和季节性:
python复制from statsmodels.tsa.deterministic import DeterministicProcess
# 创建趋势和季节性特征
dp = DeterministicProcess(
index=df.index,
constant=True, # 截距项
order=2, # 二次趋势
seasonal=True, # 周季节性
additional_terms=[fourier], # 傅里叶特征
drop=True # 避免共线性
)
X = dp.in_sample() # 生成特征
# 拟合线性模型
linear_model = LinearRegression(fit_intercept=False)
linear_model.fit(X, df['sales'])
linear_pred = linear_model.predict(X)
接下来,我们计算线性模型的残差,并用XGBoost来学习这些残差中的模式:
python复制# 计算残差
df['residual'] = df['sales'] - linear_pred
# 准备XGBoost特征
# 这里可以添加其他可能有用的特征,如促销信息、天气等
X_features = X.copy()
X_features['lag_7'] = df['sales'].shift(7) # 添加滞后特征
# 训练XGBoost
xgb_model = XGBRegressor(n_estimators=100)
xgb_model.fit(X_features, df['residual'])
xgb_pred = xgb_model.predict(X_features)
最后,将两个模型的预测相加得到最终预测:
python复制df['hybrid_pred'] = linear_pred + xgb_pred
在实际项目中,这种混合方法通常比单独使用任一模型表现更好。特别是在趋势明显但又有复杂非线性模式的数据上,提升尤为显著。
构建好模型后,我们需要评估其性能并选择最重要的特征。对于时间序列,不能简单地使用随机交叉验证,而应采用时间相关的验证方法,如TimeSeriesSplit。
特征重要性分析可以帮助我们理解模型的决策过程。XGBoost提供了内置的特征重要性计算:
python复制from xgboost import plot_importance
import matplotlib.pyplot as plt
plot_importance(xgb_model)
plt.show()
在特征选择方面,我通常会:
现实中的预测任务往往需要预测未来多个时间点(多步预测)。有几种策略可以处理这种情况:
每种方法都有优缺点。直接多输出最简单但可能精度不高;递归策略可能累积误差;直接策略计算成本高但通常效果最好。在实际项目中,我通常会先尝试直接多输出,如果效果不理想再考虑其他方法。
经过多个项目的实践,我总结出一些宝贵经验:
首先,数据质量比模型复杂更重要。确保处理好了缺失值、异常值,并且时间戳对齐正确。我曾遇到一个项目,仅仅因为夏令时转换导致的时间戳错位就让模型性能下降了30%。
其次,特征工程需要结合业务理解。比如在零售预测中,添加节假日、促销活动等信息往往比复杂的模型结构调整更有效。
最后,模型监控和维护同样重要。随着时间的推移,数据分布可能发生变化(概念漂移),需要定期重新训练模型。我建议设置自动化的性能监控,当误差超过阈值时触发重新训练。