80·实战项目进阶

实战:时间序列预测

projecttime-series

第80课 - 实战:时间序列预测

1. 学习目标

完成本课学习后,你将能够:

  1. 理解时间序列数据的独特性,识别其趋势、季节性和噪声等核心组成部分。
  2. 掌握时间序列预测的标准工作流,包括数据平稳性检验、处理和特征工程。
  3. 运用ARIMA模型族和Facebook Prophet模型进行实际预测任务。
  4. 建立一套完整的评估体系,通过可视化与量化指标客观评价预测模型的效果。
  5. 将所学应用于实战,独立完成一个从数据探索到模型部署的完整预测项目。

2. 核心概念

什么是时间序列预测?

时间序列是一组按照固定时间间隔顺序排列的数据点序列(例如:每日股价、每月销售额、每年降水量)。时间序列预测的目标,就是基于历史观测值,推断未来某个时间点或时间段的数值

它的特殊性在于:数据点之间存在着强烈的依赖关系(即“自相关性”)。昨天的销售额会影响今天的销售额,今天的天气也会受昨天天气的影响。这与大多数假设样本独立的机器学习问题不同,因此处理方法也截然不同。

时间序列的三大组成部分

  • 趋势 (Trend):数据在长期内呈现的持续上升或下降的模式。
  • 季节性 (Seasonality):数据中固定周期内的规律性波动(如夏季冰淇淋销量高,冬季羽绒服销量高)。
  • 噪声 (Noise):无法用趋势和季节性解释的随机波动。

为什么需要“平稳性”?

平稳时间序列的统计特性(如均值、方差)不随时间变化。大多数经典时间序列模型(如ARIMA)都要求数据是平稳的,因为非平稳序列的统计规律会随时间变化,导致模型不稳定。

  • 检验方法:通常使用 ADF检验。如果p值小于0.05,可以拒绝“存在单位根”的原假设,认为序列是平稳的。
  • 处理方法:如果序列不平稳,常用差分(计算当前值与前一值的差)来使其平稳。

模型选择速览

  • ARIMA (自回归积分移动平均模型):处理单变量非季节性时间序列的经典模型。ARIMA(p,d,q),其中p是自回归阶数,d是差分次数,q是移动平均阶数。
  • SARIMA (季节性ARIMA):在ARIMA基础上加入季节性成分,能处理具有明显季节性的数据。
  • Prophet (先知):Facebook开源的工具,对含有强烈季节性和节假日效应的数据特别友好,且对异常值和缺失值不敏感,易于上手。

3. 代码示例

下面我们使用经典的 AirPassengers(1949-1960年每月国际航空公司乘客数)数据集,完成一个完整的预测流程。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.arima.model import ARIMA
from prophet import Prophet
from sklearn.metrics import mean_absolute_error, mean_squared_error
import warnings
warnings.filterwarnings('ignore')

# 设置绘图风格
plt.style.use('seaborn-v0_8-darkgrid')
plt.rcParams['figure.figsize'] = (12, 6)

# 1. 加载并可视化数据
# 使用内置数据集
data = pd.read_csv('https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv', parse_dates=['Month'], index_col='Month')
data.columns = ['Passengers']
print(data.head())

data.plot(title='International Airline Passengers (1949-1960)')
plt.ylabel('Number of Passengers (Thousands)')
plt.show()

# 2. 数据分解:观察趋势、季节性
decomposition = seasonal_decompose(data['Passengers'], model='multiplicative')
fig = decomposition.plot()
fig.set_size_inches(12, 8)
plt.tight_layout()
plt.show()

# 3. 平稳性检验 (ADF检验)
def check_stationarity(series):
    result = adfuller(series.dropna())
    print(f'ADF Statistic: {result[0]:.4f}')
    print(f'p-value: {result[1]:.4f}')
    if result[1] > 0.05:
        print("序列非平稳,需要进行差分处理")
    else:
        print("序列平稳")

print("原始序列检验:")
check_stationarity(data['Passengers'])

# 4. 差分处理,使其平稳
data['Diff_1'] = data['Passengers'].diff()  # 一阶差分
data['Seasonal_Diff'] = data['Passengers'].diff(12)  # 季节性差分(12个月)

print("\n一阶差分序列检验:")
check_stationarity(data['Seasonal_Diff'].dropna())

# 5. 划分训练集与测试集 (严格按时间顺序)
train_size = len(data) - 12
train, test = data.iloc[:train_size], data.iloc[train_size:]

print(f"\n训练集: {train.index[0]} to {train.index[-1]}, 共 {len(train)} 个月")
print(f"测试集: {test.index[0]} to {test.index[-1]}, 共 {len(test)} 个月")

# 6. 模型一:ARIMA (此处使用季节性差分后的数据简化流程)
# 注意:对于明显季节性的数据,更推荐使用SARIMA,但为演示原理,这里先用ARIMA。
# 实际中应通过ACF/PACF图确定p, q参数,此处我们使用一个示例参数。
model_arima = ARIMA(train['Passengers'], order=(1, 1, 1))
model_arima_fit = model_arima.fit()
print(model_arima_fit.summary())

# 7. 模型二:Prophet
# Prophet需要特定的列名:'ds' (日期), 'y' (目标值)
train_prophet = train.reset_index().rename(columns={'Month': 'ds', 'Passengers': 'y'})
test_prophet = test.reset_index().rename(columns={'Month': 'ds', 'Passengers': 'y'})

model_prophet = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False)
model_prophet.fit(train_prophet)

# 创建未来日期框
future_dates = model_prophet.make_future_dataframe(periods=12, freq='MS') # 预测未来12个月

# 8. 进行预测
# ARIMA预测
forecast_arima = model_arima_fit.forecast(steps=12)

# Prophet预测
forecast_prophet = model_prophet.predict(future_dates)
prophet_forecast = forecast_prophet[forecast_prophet['ds'] >= test.index[0]][['ds', 'yhat', 'yhat_lower', 'yhat_upper']]

# 9. 评估与可视化
fig, ax = plt.subplots(figsize=(14, 7))

# 绘制历史数据
ax.plot(train.index, train['Passengers'], label='Training Data', color='blue')
ax.plot(test.index, test['Passengers'], label='Actual (Test)', color='green', linewidth=2)

# 绘制预测
ax.plot(test.index, forecast_arima, label='ARIMA Forecast', color='red', linestyle='--')
ax.plot(test.index, prophet_forecast['yhat'], label='Prophet Forecast', color='orange', linestyle='--')

# Prophet置信区间
ax.fill_between(test.index, prophet_forecast['yhat_lower'], prophet_forecast['yhat_upper'], alpha=0.2, color='orange')

ax.axvline(x=test.index[0], color='black', linestyle=':', label='Train/Test Split')
ax.set_title('Air Passengers Forecast: ARIMA vs Prophet')
ax.set_xlabel('Date')
ax.set_ylabel('Passengers (Thousands)')
ax.legend()
plt.show()

# 10. 计算评估指标
def evaluate(actual, predicted, model_name):
    mae = mean_absolute_error(actual, predicted)
    rmse = np.sqrt(mean_squared_error(actual, predicted))
    mape = np.mean(np.abs((actual - predicted) / actual)) * 100
    print(f"\n{model_name} 评估结果:")
    print(f"  MAE (平均绝对误差): {mae:.2f}")
    print(f"  RMSE (均方根误差): {rmse:.2f}")
    print(f"  MAPE (平均绝对百分比误差): {mape:.2f}%")

evaluate(test['Passengers'], forecast_arima, 'ARIMA')
evaluate(test['Passengers'], prophet_forecast['yhat'], 'Prophet')

4. 实践练习

练习1 (基础):指数平滑法实战

使用上面的 AirPassengers 数据集,利用 statsmodels 库中的 ExponentialSmoothing 函数(方法参数为 ‘holt-winters’),建立一个三重指数平滑模型(Holt-Winters)进行预测。

  • 要求:选择合适的季节性周期(12),并预测未来12个月的数据。
  • 预期输出:绘制包含实际值、训练集拟合值和预测值的图表。

练习2 (进阶):SARIMA模型调参

基于同一个数据集,使用 statsmodels.tsa.statespace.sarimax.SARIMAX 函数构建SARIMA模型。

  • 要求
    1. 通过绘制ACF和PACF图,辅助确定参数(p, d, q)(P, D, Q, s)。提示:对季节性差分后的序列作图。
    2. 尝试至少两组不同的参数组合(例如(1,1,1)x(1,1,1,12)(0,1,1)x(1,1,1,12)),分别进行预测。
  • 预期输出:使用AIC(Akaike Information Criterion)准则比较不同模型的优劣,并选择AIC值最低的模型进行最终预测和评估。

练习3 (综合):多变量与滚动预测

这是一个更贴近真实业务的练习。

  1. 数据:搜索并加载一个包含外部变量的时间序列数据集(例如,预测电力需求,变量包括日期、温度、假日标志等)。
  2. 任务: a. 对目标变量进行单变量预测(如使用Prophet)。 b. 尝试将外部变量纳入模型(如使用Prophet添加 add_regressor,或使用 SARIMAXexog 参数)。 c. 实现一个简单的滚动预测:每次只预测未来一步,然后将真实值纳入训练集重新训练(或微调)模型,再预测下一步,以此滚动推进。
  3. 预期输出:对比单变量模型与多变量模型在测试集上的表现,并展示滚动预测的过程与结果。

5. 常见错误

  1. 忽略时间顺序,随机划分数据集

    • 错误:像对待普通数据一样,使用 train_test_split 随机划分。
    • 后果:未来数据泄露到训练集,导致模型评估过于乐观,在真实场景中失效。
    • 正确:必须按时间点划分,测试集的时间范围必须严格晚于训练集。
  2. 盲目使用ARIMA,不检查平稳性

    • 错误:直接将原始非平稳数据喂给ARIMA模型。
    • 后果:模型拟合差,预测结果不可靠,ACF/PACF图模式混乱。
    • 正确:先进行ADF检验,根据结果决定差分阶数 d,直至序列平稳。
  3. 过度依赖单一模型

    • 错误:只尝试一种模型(如ARIMA)就下结论。
    • 后果:可能错过更优的模型(如Prophet、LSTM等)。
    • 正确:进行模型比较,使用相同的测试集和评估指标(如MAE, RMSE, MAPE)来选择最佳模型。
  4. 忽略业务背景和可解释性

    • 错误:只追求最低的误差指标。
    • 后果:可能得到一个数学上优秀但业务上荒谬的预测(例如,预测销售额为负)。
    • 正确:结合业务逻辑验证预测结果的合理性。Prophet的优势之一是其结果易于解释(分解为趋势、季节性等)。
  5. 处理缺失值不当

    • 错误:简单删除或用均值填充时间序列中的缺失值。
    • 后果:破坏了时间的连续性和自相关性结构。
    • 正确:使用时间序列特定的方法,如前向填充 (ffill)、后向填充 (bfill) 或插值法 (interpolate)。

6. 小结

本节课我们深入探讨了时间序列预测的完整实战流程:

  • 核心认知:时间序列数据的核心是自相关性,其分析必须尊重时间顺序
  • 关键步骤平稳性检验与处理是大多数经典模型的前置条件。数据分解有助于直观理解序列结构。
  • 模型选择:根据数据特点选择模型。ARIMA/SARIMA 适合有明确理论指导的场景,Prophet 则在处理复杂季节性、节假日和异常值时表现出色且易于使用。
  • 评估与迭代:必须使用时间序列专用的划分和评估方法(MAE, RMSE, MAPE)。模型调参(如SARIMA的(p,d,q))和模型对比是提升效果的关键。

记住,最好的模型不仅在历史数据上表现优异,更能提供稳定、可解释且符合业务逻辑的未来预测。接下来的实战项目,请大胆尝试,将理论与代码相结合,构建你自己的预测模型。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「实战:欺诈检测(不平衡数据处理)」 以巩固所学知识。