更多关于时间序列预测的内容请访问 cienciadedatos.net
- 使用 Python 的 ARIMA 与 SARIMAX 模型
- 用机器学习进行时间序列预测
- 用梯度提升进行时间序列预测:XGBoost、LightGBM 与 CatBoost
- 使用 XGBoost 进行时间序列预测
- 全局预测模型:多序列预测
- 全局预测模型:单序列与多序列建模的比较分析
- 概率性预测
- 用深度学习进行预测
- 用机器学习预测能源需求
- 用机器学习预测网站流量
- 间歇性需求预测
- 用树模型建模时间序列趋势
- 用 Python 预测比特币价格
- 通过集成堆叠提升预测效果
- 可解释的预测模型
- 减轻新冠疫情对预测模型的影响
- 含缺失值的时间序列预测

引言
ARIMA(自回归积分滑动平均,AutoRegressive Integrated Moving Average)和 SARIMAX(带季节项与外生变量的自回归积分滑动平均,Seasonal AutoRegressive Integrated Moving Average with eXogenous regressors)是应用广泛的统计预测模型。该类模型由三部分构成:自回归(AR)将当前值与其过去(滞后)值关联;滑动平均(MA)假设回归误差是过去预测误差的线性组合;积分(I)表示通过差分将数据值替换为与前一时刻的差(该差分过程可能需要执行多次)。
尽管 ARIMA 模型广为人知,SARIMAX 在其基础上进一步扩展,便捷地纳入季节性结构与外生变量。
在 ARIMA-SARIMAX 记号中,参数 、、 分别表示非季节部分的自回归阶数、差分阶数与滑动平均阶数;、、 则对应季节部分的同类参数, 表示每个季节的周期长度。
- :自回归项的阶数(滞后阶数)。
- :差分阶数(对数据做差分的次数)。
- :滑动平均项的阶数。
- :季节部分自回归项的阶数。
- :季节部分的差分阶数。
- :季节部分滑动平均项的阶数。
- :每个季节包含的周期数。
当 、、 与 均为 0,且模型不包含外生变量时,SARIMAX 退化为 ARIMA。
多种 Python 库提供了 ARIMA-SARIMAX 的实现,常见的有:
statsmodels:功能完备的统计建模库。其 API 对有 R 语言背景的用户更直观,相比之下与 scikit-learn 的面向对象风格略有不同。
pmdarima:
statsmodels SARIMAX
的封装。其优势在于与 scikit-learn API 的无缝对接,便于熟悉 scikit-learn 规范的用户开展时间序列建模。skforecast:在众多预测功能中,提供了新的
statsmodels SARIMAX
封装,亦遵循 scikit-learn API。实现与 pmdarima 类似,但仅保留 skforecast 所需的核心元素,从而显著加速。statsForecast:提供常用的一元时间序列模型集合,包括自动 ARIMA、ETS、CES 与 Theta,并基于 numba 优化以获得高性能。
本指南聚焦于其中三者 —— statsmodels、pmdarima 与 skforecast —— 并说明如何使用它们构建 ARIMA-SARIMAX 模型。同时引入 ForecasterSarimax
类,以便在 ARIMA-SARIMAX 框架上实现回测、超参数寻优、概率预测等能力。
✎ 提示
如需更详细的 ARIMA-SARIMAX 讲解,请参阅:库
本文所用到的库。
# 库
# ======================================================================================
import numpy as np
import pandas as pd
from io import StringIO
import contextlib
import re
import matplotlib.pyplot as plt
plt.style.use('seaborn-v0_8-darkgrid')
# pmdarima
import pmdarima
from pmdarima import ARIMA
from pmdarima import auto_arima
# statsmodels
import statsmodels
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.stattools import kpss
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.seasonal import seasonal_decompose
# skforecast
import skforecast
from skforecast.datasets import fetch_dataset
from skforecast.plot import set_dark_theme
from skforecast.sarimax import Sarimax
from skforecast.recursive import ForecasterSarimax
from skforecast.model_selection import TimeSeriesFold
from skforecast.model_selection import backtesting_sarimax
from skforecast.model_selection import grid_search_sarimax
import warnings
warnings.filterwarnings('once')
color = '\033[1m\033[38;5;208m'
print(f"{color}skforecast 版本: {skforecast.__version__}")
print(f"{color}pmdarima 版本: {pmdarima.__version__}")
print(f"{color}statsmodels 版本: {statsmodels.__version__}")
print(f"{color}pandas 版本: {pd.__version__}")
print(f"{color}numpy 版本: {np.__version__}")
skforecast 版本: 0.17.0 pmdarima 版本: 2.0.4 statsmodels 版本: 0.14.4 pandas 版本: 2.3.1 numpy 版本: 1.26.4
# !pip install numpy==2.2.6
⚠ 警告
撰写本文时,pmdarima
仅与 numpy
2.0 以下版本兼容。如果你的版本更高,可运行以下命令降级:pip install numpy==1.26.4
数据
本文数据集为西班牙月度燃油消耗的汇总。
# 下载数据
# ==============================================================================
data = fetch_dataset(name='fuel_consumption', raw=True)
data = data[['Fecha', 'Gasolinas']]
data = data.rename(columns={'Fecha':'date', 'Gasolinas':'litters'})
data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d')
data = data.set_index('date')
data = data.loc[:'1990-01-01 00:00:00']
data = data.asfreq('MS')
data = data['litters']
display(data.head(4))
fuel_consumption ---------------- Monthly fuel consumption in Spain from 1969-01-01 to 2022-08-01. Obtained from Corporación de Reservas Estratégicas de Productos Petrolíferos and Corporación de Derecho Público tutelada por el Ministerio para la Transición Ecológica y el Reto Demográfico. https://www.cores.es/es/estadisticas Shape of the dataset: (644, 6)
date 1969-01-01 166875.2129 1969-02-01 155466.8105 1969-03-01 184983.6699 1969-04-01 202319.8164 Freq: MS, Name: litters, dtype: float64
# 训练-测试集时间范围
# =====================================================================================
set_dark_theme()
end_train = '1980-01-01 23:59:59'
print(
f"训练集时间段 : {data.index.min()} --- {data.loc[:end_train].index.max()} "
f"(n={len(data.loc[:end_train])})"
)
print(
f"测试集时间段 : {data.loc[end_train:].index.min()} --- {data.loc[:].index.max()} "
f"(n={len(data.loc[end_train:])})"
)
data_train = data.loc[:end_train]
data_test = data.loc[end_train:]
# 绘图
# =====================================================================================
fig, ax=plt.subplots(figsize=(7, 3))
data_train.plot(ax=ax, label='train')
data_test.plot(ax=ax, label='test')
ax.set_title('Monthly fuel consumption in Spain')
ax.legend();
训练集时间段 : 1969-01-01 00:00:00 --- 1980-01-01 00:00:00 (n=133) 测试集时间段 : 1980-02-01 00:00:00 --- 1990-01-01 00:00:00 (n=120)
探索性分析
在构建 ARIMA 模型前,需要进行全面的探索性分析。这一步至关重要,它像罗盘一样帮助分析者理解数据的内在动态。拟合 ARIMA 之前,至少应确认以下几点:
平稳性:平稳意味着统计性质(如均值、方差等)在时间上保持不变,因此带有趋势或季节性的序列通常非平稳。由于 ARIMA 假设数据平稳,必须通过严格的检验(如增强型 Dickey-Fuller,ADF)来评估平稳性。如果检验表明非平稳,应进行差分直至达到平稳。该分析有助于确定参数 的取值。
自相关分析:绘制自相关函数(ACF)与偏自相关函数(PACF),识别数据点之间潜在的滞后关系。该可视化分析有助于确定 ARIMA 模型中的自回归(AR)与滑动平均(MA)项( 与 )。
季节分解:若怀疑存在季节性,可使用移动平均或 STL 等方法将序列分解为趋势、季节与残差成分,从而揭示潜在模式并识别季节性。这有助于确定 、、 与 的取值。
上述探索性分析为构建能够捕捉数据基本模式与联系的 ARIMA 模型奠定基础。
平稳性
判断时间序列是否平稳可采用多种方法:
可视化检视:通过观察时序图,若出现明显的趋势或季节性,序列大概率为非平稳。
分段统计量:在不同时间片上计算均值、方差等统计量,若差异显著,则序列非平稳。
统计检验:如增强型 Dickey-Fuller(ADF)检验、Kwiatkowski-Phillips-Schmidt-Shin(KPSS)检验。
前面的图展示了序列存在明显的上升趋势,即均值随时间增加,说明该序列非平稳。
差分是去除趋势的常用方法。通过将当前值与前一时刻的差作为新值来构造新序列,即相邻观测之差。数学形式为:
其中, 为时刻 的值, 为时刻 的值,这称为一阶差分。如有需要可继续差分,直到达到所需的平稳性。
下面将对原始序列进行一阶与二阶差分,并应用统计检验来判断是否达到平稳。
增强型 Dickey-Fuller 检验(ADF)
ADF 检验的原假设为序列存在单位根(非平稳),备择假设为序列平稳。
- 原假设(H0):序列非平稳/存在单位根。
- 备择假设(H1):序列平稳/不存在单位根。
由于原假设假定存在单位根,因此当 p 值小于显著性水平(通常为 0.05)时,可以拒绝原假设,认为序列平稳。Statsmodels 提供了 adfuller()
函数,其输出包括:检验统计量、p 值、滞后项个数以及不同显著性水平下的临界值。
Kwiatkowski-Phillips-Schmidt-Shin 检验(KPSS)
KPSS 检验用于检验序列是否围绕常数或线性趋势平稳。其原假设为序列平稳,我们希望找到反对该假设的证据。因此,小的 p 值(如 < 0.05)意味着拒绝平稳性原假设,提示需要差分。Statsmodels 中可用 kpss()
实现 KPSS 检验。
✎ 提示
虽然两者都用于检查平稳性:- KPSS 关注趋势的存在,p 值小表示因趋势导致的非平稳。
- ADF 关注单位根的存在,p 值小表示序列无单位根,提示可能平稳。
# 平稳性检验
# ==============================================================================
warnings.filterwarnings("ignore")
data_diff_1 = data_train.diff().dropna()
data_diff_2 = data_diff_1.diff().dropna()
print('Test stationarity for original series')
print('-------------------------------------')
adfuller_result = adfuller(data)
kpss_result = kpss(data)
print(f'ADF Statistic: {adfuller_result[0]}, p-value: {adfuller_result[1]}')
print(f'KPSS Statistic: {kpss_result[0]}, p-value: {kpss_result[1]}')
print('\nTest stationarity for differenced series (order=1)')
print('--------------------------------------------------')
adfuller_result = adfuller(data_diff_1)
kpss_result = kpss(data.diff().dropna())
print(f'ADF Statistic: {adfuller_result[0]}, p-value: {adfuller_result[1]}')
print(f'KPSS Statistic: {kpss_result[0]}, p-value: {kpss_result[1]}')
print('\nTest stationarity for differenced series (order=2)')
print('--------------------------------------------------')
adfuller_result = adfuller(data_diff_2)
kpss_result = kpss(data.diff().diff().dropna())
print(f'ADF Statistic: {adfuller_result[0]}, p-value: {adfuller_result[1]}')
print(f'KPSS Statistic: {kpss_result[0]}, p-value: {kpss_result[1]}')
warnings.filterwarnings("default")
# 绘制序列
# ==============================================================================
fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(7, 5), sharex=True)
data.plot(ax=axs[0], title='Original time series')
data_diff_1.plot(ax=axs[1], title='Differenced order 1')
data_diff_2.plot(ax=axs[2], title='Differenced order 2');
Test stationarity for original series ------------------------------------- ADF Statistic: -0.44612980998227997, p-value: 0.9021071923942665 KPSS Statistic: 2.2096370946978383, p-value: 0.01 Test stationarity for differenced series (order=1) -------------------------------------------------- ADF Statistic: -3.641727690032331, p-value: 0.005011605002137098 KPSS Statistic: 0.313271162357279, p-value: 0.1 Test stationarity for differenced series (order=2) -------------------------------------------------- ADF Statistic: -8.233942641656038, p-value: 5.959599575494846e-13 KPSS Statistic: 0.08065668267482215, p-value: 0.1
在比较一阶与二阶差分后,p 值在一阶差分时显著下降并低于 0.05 的常用阈值。因此,ARIMA 参数 的合适取值为 1。
自相关分析
绘制时间序列的自相关函数(ACF)与偏自相关函数(PACF)有助于选择 与 。ACF 主要用于识别 (MA 项的滞后阶数),PACF 则用于识别 (AR 项的滞后阶数)。
⚠ 警告
若平稳性分析表明需要差分,则后续分析应基于差分后的序列,以与 ARIMA 的建模方式保持一致。自相关函数(ACF)
ACF 计算时间序列与其滞后值之间的相关性。在 ARIMA 建模中,若 ACF 在少数滞后后急剧衰减,表明数据具有有限的 MA 阶数,衰减的滞后位置可用于估计 。若 ACF 呈现(阻尼)正弦振荡,则提示存在季节性,需要同时考虑季节项。
偏自相关函数(PACF)
PACF 衡量在控制中间滞后影响后,某个滞后与当前值之间的相关性。若 PACF 在某一滞后后迅速截尾,且其余落在置信区间内,提示相应阶数的 AR 模型。截尾的滞后可用于估计 。
✎ 提示
一些经验法则:PACF 中超出显著性界限的滞后个数可作为 AR 阶数 p 的初始估计。
ACF 中超出显著性界限的滞后个数可作为 MA 阶数 q 的初始估计。
若 ACF 在滞后 q 处截尾,且 PACF 在滞后 p 处截尾,可尝试 ARIMA(p, d, q)。
若仅 PACF 截尾在 p,可尝试 AR(p)。
若仅 ACF 截尾在 q,可尝试 MA(q)。
# 原始与差分序列的自相关图
# ==============================================================================
fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(6, 4), sharex=True)
plot_acf(data, ax=axs[0], lags=50, alpha=0.05)
axs[0].set_title('Autocorrelation original series')
plot_acf(data_diff_1, ax=axs[1], lags=50, alpha=0.05)
axs[1].set_title('Autocorrelation differentiated series (order=1)');
# 原始与差分序列的偏自相关图
# ==============================================================================
fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(6, 3), sharex=True)
plot_pacf(data, ax=axs[0], lags=50, alpha=0.05)
axs[0].set_title('Partial autocorrelation original series')
plot_pacf(data_diff_1, ax=axs[1], lags=50, alpha=0.05)
axs[1].set_title('Partial autocorrelation differenced series (order=1)');
plt.tight_layout();
根据偏自相关函数,参数 的最佳值为 0。但为赋予模型一定的自回归成分,这里取 。另一方面,基于自相关函数, 建议取 1。
时间序列分解
时间序列分解将原序列拆分为趋势、季节与残差成分,分解形式可为加性或乘性。结合 ACF 与 PACF 的分析,分解有助于全面理解数据结构并选择合适的 ARIMA 参数。
# 原始与差分序列的时间序列分解
# ==============================================================================
res_decompose = seasonal_decompose(data, model='additive', extrapolate_trend='freq')
res_descompose_diff_2 = seasonal_decompose(data_diff_1, model='additive', extrapolate_trend='freq')
fig, axs = plt.subplots(nrows=4, ncols=2, figsize=(9, 6), sharex=True)
res_decompose.observed.plot(ax=axs[0, 0])
axs[0, 0].set_title('Original series', fontsize=12)
res_decompose.trend.plot(ax=axs[1, 0])
axs[1, 0].set_title('Trend', fontsize=12)
res_decompose.seasonal.plot(ax=axs[2, 0])
axs[2, 0].set_title('Seasonal', fontsize=12)
res_decompose.resid.plot(ax=axs[3, 0])
axs[3, 0].set_title('Residuals', fontsize=12)
res_descompose_diff_2.observed.plot(ax=axs[0, 1])
axs[0, 1].set_title('Differenced series (order=1)', fontsize=12)
res_descompose_diff_2.trend.plot(ax=axs[1, 1])
axs[1, 1].set_title('Trend', fontsize=12)
res_descompose_diff_2.seasonal.plot(ax=axs[2, 1])
axs[2, 1].set_title('Seasonal', fontsize=12)
res_descompose_diff_2.resid.plot(ax=axs[3, 1])
axs[3, 1].set_title('Residuals', fontsize=12)
fig.suptitle('Time serie decomposition original series versus differenced series', fontsize=14)
fig.tight_layout();
每 12 个月出现的重复模式暗示年度季节性,可能受节假日等因素影响。ACF 图在 12 期倍数的滞后位置出现显著峰值,也进一步印证了季节性的存在。
结论
基于探索性分析结果,采用一阶差分与季节差分的组合更为合适。一阶差分有助于刻画相邻观测之间的转变与短期波动;同时,以 12 个月为周期的季节差分(跨年差分)可有效捕捉数据中的周期性结构。该组合能够实现后续 ARIMA 建模所需的平稳性。
# 一阶差分 + 季节差分
# ==============================================================================
data_diff_1_12 = data_train.diff().diff(12).dropna()
warnings.filterwarnings("ignore")
adfuller_result = adfuller(data_diff_1_12)
print(f'ADF Statistic: {adfuller_result[0]}, p-value: {adfuller_result[1]}')
kpss_result = kpss(data_diff_1_12)
print(f'KPSS Statistic: {kpss_result[0]}, p-value: {kpss_result[1]}')
warnings.filterwarnings("default")
ADF Statistic: -4.387457230769957, p-value: 0.0003123773271126916 KPSS Statistic: 0.06291573421251052, p-value: 0.1
⚠ 警告
探索性数据分析是一个不断迭代的过程,结论可能随着推进而变化。请记住,上述图形仅提供初步指引,、、 的最佳取值应综合这些图形、AIC/BIC 等统计准则以及如 回测(backtesting) 的时序验证来选择。ARIMA-SARIMAX 模型
本节展示如何使用三种库分别训练 ARIMA 模型并进行预测。
Statsmodels
在 statsmodels 中,定义模型与训练模型是分离的。这种方式对 R 用户较为熟悉,但对习惯 scikit-learn 或 XGBoost 这类 Python 库的用户而言可能稍显不同。
流程从模型定义开始(给定可配置参数与训练数据)。调用 fit
方法时,statsmodels 不会就地修改模型对象,而是返回一个新的 SARIMAXResults 对象。该对象封装了残差、已学习参数等信息,并提供预测所需的接口。
# 使用 statsmodels.SARIMAX 的 ARIMA 模型
# ==============================================================================
warnings.filterwarnings("ignore", category=UserWarning, message='Non-invertible|Non-stationary')
model = SARIMAX(endog = data_train, order = (1, 1, 1), seasonal_order = (1, 1, 1, 12))
model_res = model.fit(disp=0)
warnings.filterwarnings("default")
model_res.summary()
Dep. Variable: | litters | No. Observations: | 133 |
---|---|---|---|
Model: | SARIMAX(1, 1, 1)x(1, 1, 1, 12) | Log Likelihood | -1356.051 |
Date: | Wed, 13 Aug 2025 | AIC | 2722.103 |
Time: | 17:53:37 | BIC | 2736.040 |
Sample: | 01-01-1969 | HQIC | 2727.763 |
- 01-01-1980 | |||
Covariance Type: | opg |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
ar.L1 | -0.4972 | 0.134 | -3.707 | 0.000 | -0.760 | -0.234 |
ma.L1 | -0.0096 | 0.146 | -0.066 | 0.947 | -0.295 | 0.276 |
ar.S.L12 | 0.0465 | 0.162 | 0.288 | 0.774 | -0.270 | 0.364 |
ma.S.L12 | -0.3740 | 0.203 | -1.847 | 0.065 | -0.771 | 0.023 |
sigma2 | 3.291e+08 | 1.06e-09 | 3.1e+17 | 0.000 | 3.29e+08 | 3.29e+08 |
Ljung-Box (L1) (Q): | 5.13 | Jarque-Bera (JB): | 18.12 |
---|---|---|---|
Prob(Q): | 0.02 | Prob(JB): | 0.00 |
Heteroskedasticity (H): | 1.26 | Skew: | -0.42 |
Prob(H) (two-sided): | 0.46 | Kurtosis: | 4.71 |
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
[2] Covariance matrix is singular or near-singular, with condition number 1.04e+34. Standard errors may be unstable.
模型摘要展示了拟合过程的关键信息:
拟合优度统计量:用于评估拟合好坏。
对数似然(Log-Likelihood):衡量模型解释观测数据的能力。ARIMA 拟合时该值通常为负,越接近 0 代表拟合越好(越小/越负代表更差)。
AIC(赤池信息准则):在拟合优度与复杂度之间折中,越小越好。
BIC(贝叶斯信息准则):类似 AIC,但对复杂度惩罚更强,同样越小越好。
HQIC(Hannan-Quinn 信息准则):另一种模型选择准则,含义与 AIC/BIC 类似。
系数:列出模型参数(AR、MA 以及可能的外生变量)的估计值。还包括标准误、p 值与 95% 置信区间,用于评估参数不确定性与显著性。
诊断:关于残差(训练观测与模型拟合值之差)的统计特征。
Ljung-Box:检验残差的自相关性。
Jarque-Bera:检验残差的正态性。
偏度/峰度:描述残差分布形状。
# 预测
# ==============================================================================
predictions_statsmodels = model_res.get_forecast(steps=len(data_test)).predicted_mean
predictions_statsmodels.name = 'predictions_statsmodels'
display(predictions_statsmodels.head(4))
1980-02-01 407504.056939 1980-03-01 473997.245807 1980-04-01 489983.091486 1980-05-01 485517.462863 Freq: MS, Name: predictions_statsmodels, dtype: float64
Skforecast
Skforecast 对 statsmodels.SARIMAX
进行了封装,并适配为 scikit-learn 风格的 API。
# 使用 skforecast.Sarimax 的 ARIMA 模型
# ==============================================================================
warnings.filterwarnings("ignore", category=UserWarning, message='Non-invertible|Non-stationary')
model = Sarimax(order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
model.fit(y=data_train)
model.summary()
warnings.filterwarnings("default")
# 预测
# ==============================================================================
predictions_skforecast = model.predict(steps=len(data_test))
predictions_skforecast.columns = ['skforecast']
display(predictions_skforecast.head(4))
skforecast | |
---|---|
1980-02-01 | 407504.056939 |
1980-03-01 | 473997.245807 |
1980-04-01 | 489983.091486 |
1980-05-01 | 485517.462863 |
✎ 提示
由于 skforecast Sarimax 是 statsmodels SARIMAX 的封装,二者结果应一致。pmdarima
# 使用 pmdarima.Sarimax 的 ARIMA 模型
# ==============================================================================
warnings.filterwarnings("ignore", message=".*force_all_finite.*", category=FutureWarning)
model = ARIMA(order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
model.fit(y=data_train)
model.summary()
Dep. Variable: | y | No. Observations: | 133 |
---|---|---|---|
Model: | SARIMAX(1, 1, 1)x(1, 1, 1, 12) | Log Likelihood | -1355.749 |
Date: | Wed, 13 Aug 2025 | AIC | 2723.498 |
Time: | 17:53:39 | BIC | 2740.223 |
Sample: | 01-01-1969 | HQIC | 2730.290 |
- 01-01-1980 | |||
Covariance Type: | opg |
coef | std err | z | P>|z| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
intercept | -474.5820 | 1101.722 | -0.431 | 0.667 | -2633.918 | 1684.754 |
ar.L1 | -0.4896 | 0.138 | -3.554 | 0.000 | -0.760 | -0.220 |
ma.L1 | -0.0211 | 0.151 | -0.139 | 0.889 | -0.317 | 0.275 |
ar.S.L12 | 0.0545 | 0.164 | 0.331 | 0.740 | -0.268 | 0.377 |
ma.S.L12 | -0.3841 | 0.204 | -1.884 | 0.060 | -0.784 | 0.015 |
sigma2 | 3.289e+08 | 0.002 | 1.84e+11 | 0.000 | 3.29e+08 | 3.29e+08 |
Ljung-Box (L1) (Q): | 4.90 | Jarque-Bera (JB): | 18.55 |
---|---|---|---|
Prob(Q): | 0.03 | Prob(JB): | 0.00 |
Heteroskedasticity (H): | 1.27 | Skew: | -0.43 |
Prob(H) (two-sided): | 0.46 | Kurtosis: | 4.73 |
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
[2] Covariance matrix is singular or near-singular, with condition number 8.24e+27. Standard errors may be unstable.
# 预测
# ==============================================================================
warnings.filterwarnings("ignore", message=".*force_all_finite.*", category=FutureWarning)
predictions_pdmarima = model.predict(len(data_test))
predictions_pdmarima.name = 'predictions_pdmarima'
display(predictions_pdmarima.head(4))
1980-02-01 406998.311394 1980-03-01 472944.444495 1980-04-01 488389.125309 1980-05-01 483432.075708 Freq: MS, Name: predictions_pdmarima, dtype: float64
# 绘制预测结果
# ==============================================================================
fig, ax = plt.subplots(figsize=(7, 3))
data_train.plot(ax=ax, label='train')
data_test.plot(ax=ax, label='test')
predictions_statsmodels.plot(ax=ax, label='statsmodels')
predictions_skforecast.plot(ax=ax, label='skforecast')
predictions_pdmarima.plot(ax=ax, label='pmdarima')
ax.set_title('Predictions with ARIMA models')
ax.legend();
⚠ 警告
尽管pmdarima
是 statsmodels SARIMAX
的封装,结果可能出现差异。撰写本文时,作者仍在调查该不可复现性的原因。
ForecasterSarimax
ForecasterSarimax
类允许用 skforecast API 训练与验证 ARIMA/SARIMAX 模型。由于其与库中其他 Forecaster 拥有相同 API,因此很容易将 ARIMA-SARIMAX 与其他机器学习模型(如 随机森林或梯度提升)进行公平、稳健的对比。
训练 - 预测
训练-预测流程与 scikit-learn 的 API 类似。ForecasterSarimax 用户指南。
# 使用 ForecasterSarimax 与 skforecast Sarimax 的 ARIMA 模型
# ==============================================================================
forecaster = ForecasterSarimax(
regressor=Sarimax(order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
)
forecaster.fit(y=data_train, suppress_warnings=True)
# 预测
predictions = forecaster.predict(steps=len(data_test))
predictions.head(4)
1980-02-01 407504.056939 1980-03-01 473997.245807 1980-04-01 489983.091486 1980-05-01 485517.462863 Freq: MS, Name: pred, dtype: float64
回测(Backtesting)
下面示例展示如何使用 回测 评估 SARIMAX 模型:每年 12 月底生成一次未来 12 个月的预测。
💡 小贴士
若将suppress_warnings_fit=True
,拟合阶段产生的警告将被忽略。
# 回测前向验证
# ==============================================================================
forecaster = ForecasterSarimax(
regressor = Sarimax(
order = (1, 1, 1),
seasonal_order =(1, 1, 1, 12),
maxiter = 200
)
)
cv = TimeSeriesFold(
steps = 12,
initial_train_size = len(data_train),
refit = True,
fixed_train_size = False,
)
metric, predictions = backtesting_sarimax(
forecaster = forecaster,
y = data,
cv = cv,
metric = 'mean_absolute_error',
suppress_warnings_fit = True,
verbose = True,
)
display(metric)
predictions.head(4)
Information of folds -------------------- Number of observations used for initial training: 133 Number of observations used for backtesting: 120 Number of folds: 10 Number skipped folds: 0 Number of steps per fold: 12 Number of steps to exclude between last observed data (last window) and predictions (gap): 0 Fold: 0 Training: 1969-01-01 00:00:00 -- 1980-01-01 00:00:00 (n=133) Validation: 1980-02-01 00:00:00 -- 1981-01-01 00:00:00 (n=12) Fold: 1 Training: 1969-01-01 00:00:00 -- 1981-01-01 00:00:00 (n=145) Validation: 1981-02-01 00:00:00 -- 1982-01-01 00:00:00 (n=12) Fold: 2 Training: 1969-01-01 00:00:00 -- 1982-01-01 00:00:00 (n=157) Validation: 1982-02-01 00:00:00 -- 1983-01-01 00:00:00 (n=12) Fold: 3 Training: 1969-01-01 00:00:00 -- 1983-01-01 00:00:00 (n=169) Validation: 1983-02-01 00:00:00 -- 1984-01-01 00:00:00 (n=12) Fold: 4 Training: 1969-01-01 00:00:00 -- 1984-01-01 00:00:00 (n=181) Validation: 1984-02-01 00:00:00 -- 1985-01-01 00:00:00 (n=12) Fold: 5 Training: 1969-01-01 00:00:00 -- 1985-01-01 00:00:00 (n=193) Validation: 1985-02-01 00:00:00 -- 1986-01-01 00:00:00 (n=12) Fold: 6 Training: 1969-01-01 00:00:00 -- 1986-01-01 00:00:00 (n=205) Validation: 1986-02-01 00:00:00 -- 1987-01-01 00:00:00 (n=12) Fold: 7 Training: 1969-01-01 00:00:00 -- 1987-01-01 00:00:00 (n=217) Validation: 1987-02-01 00:00:00 -- 1988-01-01 00:00:00 (n=12) Fold: 8 Training: 1969-01-01 00:00:00 -- 1988-01-01 00:00:00 (n=229) Validation: 1988-02-01 00:00:00 -- 1989-01-01 00:00:00 (n=12) Fold: 9 Training: 1969-01-01 00:00:00 -- 1989-01-01 00:00:00 (n=241) Validation: 1989-02-01 00:00:00 -- 1990-01-01 00:00:00 (n=12)
0%| | 0/10 [00:00<?, ?it/s]
mean_absolute_error | |
---|---|
0 | 19611.236352 |
pred | |
---|---|
1980-02-01 | 407504.056939 |
1980-03-01 | 473997.245807 |
1980-04-01 | 489983.091486 |
1980-05-01 | 485517.462863 |
# 绘制回测预测
# ==============================================================================
fig, ax = plt.subplots(figsize=(6, 3))
data.loc[end_train:].plot(ax=ax, label='test')
predictions.plot(ax=ax)
ax.set_title('Backtest predictions with SARIMAX model')
ax.legend();
模型调优(超参数 p、d、q)
探索性分析已帮助我们缩小了模型超参数的搜索空间。但要最终确定最优取值,还需借助系统性的搜索策略。常见做法包括:
统计准则:如 AIC(赤池信息准则)或 BIC(贝叶斯信息准则),它们在最大似然(对数似然)基础上施加不同的复杂度惩罚。不需要在新数据上做预测,仅基于训练数据计算,因此优化速度非常快。著名的 Auto ARIMA 就采用此思路。
验证技术:验证技术 尤其是回测(backtesting),通过历史数据模拟真实情境来评估模型表现。该方法能检验超参数在不同场景下的有效性,所得指标也更具业务可解释性。
第一类方法仅依赖训练数据,速度快,但需要注意:信息准则只衡量模型的相对好坏,所有候选模型都可能整体拟合较差。因此,最终选择的模型仍应接受回测,使用 MAE、MSE、MAPE 等指标进行评估。
第二类方法(验证技术)通常更耗时,因为需要先训练再在新数据上评估。但其结果往往更稳健,指标也更具洞见。
💡 小贴士
总而言之,基于统计准则的方法速度快、效率高;而基于验证的数据驱动方法虽更耗时,但评估更全面、更具洞见。对于足够大的数据集,两种策略往往会收敛到相同的模型。⚠ 警告
评估 ARIMA/SARIMAX 时需注意,AIC 假定所有模型在同一数据集上训练。因此,使用 AIC 在不同差分阶数之间做决策在技术上并不严谨(每进行一次差分都会损失一个数据点)。因此,Auto ARIMA 通常用单位根检验选择差分阶数,而仅用 AIC 在 AR 与 MA 的阶数上做选择。进行超参数优化时,务必基于验证集而非测试集,以确保对模型性能的评估客观准确。
# 训练-验证-测试 数据
# =====================================================================================
end_train = '1976-01-01 23:59:59'
end_val = '1984-01-01 23:59:59'
print(
f"训练集时间段 : {data.index.min()} --- {data.loc[:end_train].index.max()} "
f"(n={len(data.loc[:end_train])})"
)
print(
f"验证集时间段 : {data.loc[end_train:].index.min()} --- {data.loc[:end_val].index.max()} "
f"(n={len(data.loc[end_train:end_val])})"
)
print(
f"测试集时间段 : {data.loc[end_val:].index.min()} --- {data.index.max()} "
f"(n={len(data.loc[end_val:])})"
)
# 绘图
# =====================================================================================
fig, ax = plt.subplots(figsize=(7, 3))
data.loc[:end_train].plot(ax=ax, label='train')
data.loc[end_train:end_val].plot(ax=ax, label='validation')
data.loc[end_val:].plot(ax=ax, label='test')
ax.set_title('Monthly fuel consumption in Spain')
ax.legend();
训练集时间段 : 1969-01-01 00:00:00 --- 1976-01-01 00:00:00 (n=85) 验证集时间段 : 1976-02-01 00:00:00 --- 1984-01-01 00:00:00 (n=96) 测试集时间段 : 1984-02-01 00:00:00 --- 1990-01-01 00:00:00 (n=72)
# 基于回测的网格搜索
# ==============================================================================
forecaster = ForecasterSarimax(
regressor = Sarimax(
order = (1, 1, 1), # 网格搜索中将替换该占位符
maxiter = 500
)
)
param_grid = {
'order': [(0, 1, 0), (0, 1, 1), (1, 1, 0), (1, 1, 1), (2, 1, 1)],
'seasonal_order': [(0, 0, 0, 0), (0, 1, 0, 12), (1, 1, 1, 12)],
'trend': [None, 'n', 'c']
}
cv = TimeSeriesFold(
steps = 12,
initial_train_size = len(data_train),
refit = True,
fixed_train_size = False,
)
results_grid = grid_search_sarimax(
forecaster = forecaster,
y = data.loc[:end_val],
cv = cv,
param_grid = param_grid,
metric = 'mean_absolute_error',
return_best = False,
suppress_warnings_fit = True,
)
results_grid.head(5)
Number of models compared: 45.
params grid: 0%| | 0/45 [00:00<?, ?it/s]
params | mean_absolute_error | order | seasonal_order | trend | |
---|---|---|---|---|---|
0 | {'order': (0, 1, 1), 'seasonal_order': (1, 1, ... | 18789.897512 | (0, 1, 1) | (1, 1, 1, 12) | n |
1 | {'order': (0, 1, 1), 'seasonal_order': (1, 1, ... | 18789.897512 | (0, 1, 1) | (1, 1, 1, 12) | None |
2 | {'order': (1, 1, 1), 'seasonal_order': (1, 1, ... | 19897.376856 | (1, 1, 1) | (1, 1, 1, 12) | n |
3 | {'order': (1, 1, 1), 'seasonal_order': (1, 1, ... | 19897.376856 | (1, 1, 1) | (1, 1, 1, 12) | None |
4 | {'order': (2, 1, 1), 'seasonal_order': (1, 1, ... | 20176.731313 | (2, 1, 1) | (1, 1, 1, 12) | n |
# Auto arima:基于 AIC 的选择
# ==============================================================================
warnings.filterwarnings("ignore", message=".*force_all_finite.*", category=FutureWarning)
model = auto_arima(
y = data.loc[:end_val],
start_p = 0,
start_q = 0,
max_p = 3,
max_q = 3,
seasonal = True,
test = 'adf',
m = 12, # 季节周期长度
d = None, # 算法将自动确定 'd'
D = None, # 算法将自动确定 'D'
trace = True,
error_action = 'ignore',
suppress_warnings = True,
stepwise = True
)
Performing stepwise search to minimize aic ARIMA(0,1,0)(1,1,1)[12] : AIC=3903.204, Time=0.62 sec ARIMA(0,1,0)(0,1,0)[12] : AIC=3942.897, Time=0.05 sec ARIMA(1,1,0)(1,1,0)[12] : AIC=3846.786, Time=0.43 sec ARIMA(0,1,1)(0,1,1)[12] : AIC=3840.318, Time=0.48 sec ARIMA(0,1,1)(0,1,0)[12] : AIC=3873.797, Time=0.13 sec ARIMA(0,1,1)(1,1,1)[12] : AIC=3841.882, Time=0.69 sec ARIMA(0,1,1)(0,1,2)[12] : AIC=3841.572, Time=2.61 sec ARIMA(0,1,1)(1,1,0)[12] : AIC=3852.231, Time=0.41 sec ARIMA(0,1,1)(1,1,2)[12] : AIC=3842.593, Time=3.87 sec ARIMA(0,1,0)(0,1,1)[12] : AIC=3904.615, Time=0.39 sec ARIMA(1,1,1)(0,1,1)[12] : AIC=3834.135, Time=0.63 sec ARIMA(1,1,1)(0,1,0)[12] : AIC=3866.187, Time=0.14 sec ARIMA(1,1,1)(1,1,1)[12] : AIC=3835.564, Time=0.55 sec ARIMA(1,1,1)(0,1,2)[12] : AIC=3835.160, Time=2.69 sec ARIMA(1,1,1)(1,1,0)[12] : AIC=3844.410, Time=0.42 sec ARIMA(1,1,1)(1,1,2)[12] : AIC=3836.443, Time=3.81 sec ARIMA(1,1,0)(0,1,1)[12] : AIC=3836.696, Time=0.31 sec ARIMA(2,1,1)(0,1,1)[12] : AIC=3836.104, Time=1.49 sec ARIMA(1,1,2)(0,1,1)[12] : AIC=3836.107, Time=1.05 sec ARIMA(0,1,2)(0,1,1)[12] : AIC=3834.320, Time=0.88 sec ARIMA(2,1,0)(0,1,1)[12] : AIC=3834.277, Time=0.47 sec ARIMA(2,1,2)(0,1,1)[12] : AIC=3837.435, Time=1.83 sec ARIMA(1,1,1)(0,1,1)[12] intercept : AIC=3835.455, Time=1.09 sec Best model: ARIMA(1,1,1)(0,1,1)[12] Total fit time: 25.123 seconds
有时希望捕获 auto_arima
的 trace 输出,便于更系统地分析结果。尽管其默认直接打印,我们仍可将输出捕获并整理为结构化的 Pandas DataFrame。
# 捕获 auto_arima 的 trace 并整理为 Pandas DataFrame
# ==============================================================================
warnings.filterwarnings("ignore", message=".*force_all_finite.*", category=FutureWarning)
buffer = StringIO()
with contextlib.redirect_stdout(buffer):
auto_arima(
y = data.loc[:end_val],
start_p = 0,
start_q = 0,
max_p = 3,
max_q = 3,
seasonal = True,
test = 'adf',
m = 12, # 季节周期长度
d = None, # 算法将自动确定 'd'
D = None, # 算法将自动确定 'D'
trace = True,
error_action = 'ignore',
suppress_warnings = True,
stepwise = True
)
trace_autoarima = buffer.getvalue()
pattern = r"ARIMA\((\d+),(\d+),(\d+)\)\((\d+),(\d+),(\d+)\)\[(\d+)\]\s+(intercept)?\s+:\s+AIC=([\d\.]+), Time=([\d\.]+) sec"
matches = re.findall(pattern, trace_autoarima)
results = pd.DataFrame(
matches, columns=["p", "d", "q", "P", "D", "Q", "m", "intercept", "AIC", "Time"]
)
results["order"] = results[["p", "d", "q"]].apply(
lambda x: f"({x.iloc[0]},{x.iloc[1]},{x.iloc[2]})", axis=1
)
results["seasonal_order"] = results[["P", "D", "Q", "m"]].apply(
lambda x: f"({x.iloc[0]},{x.iloc[1]},{x.iloc[2]},{x.iloc[3]})", axis=1
)
results = results[["order", "seasonal_order", "intercept", "AIC", "Time"]]
results.sort_values(by="AIC").reset_index(drop=True)
order | seasonal_order | intercept | AIC | Time | |
---|---|---|---|---|---|
0 | (1,1,1) | (0,1,1,12) | 3834.135 | 0.55 | |
1 | (2,1,0) | (0,1,1,12) | 3834.277 | 0.65 | |
2 | (0,1,2) | (0,1,1,12) | 3834.320 | 0.60 | |
3 | (1,1,1) | (0,1,2,12) | 3835.160 | 2.22 | |
4 | (1,1,1) | (0,1,1,12) | intercept | 3835.455 | 0.68 |
5 | (1,1,1) | (1,1,1,12) | 3835.564 | 0.58 | |
6 | (2,1,1) | (0,1,1,12) | 3836.104 | 1.95 | |
7 | (1,1,2) | (0,1,1,12) | 3836.107 | 1.07 | |
8 | (1,1,1) | (1,1,2,12) | 3836.443 | 3.71 | |
9 | (1,1,0) | (0,1,1,12) | 3836.696 | 0.50 | |
10 | (2,1,2) | (0,1,1,12) | 3837.435 | 1.86 | |
11 | (0,1,1) | (0,1,1,12) | 3840.318 | 0.56 | |
12 | (0,1,1) | (0,1,2,12) | 3841.572 | 2.48 | |
13 | (0,1,1) | (1,1,1,12) | 3841.882 | 0.66 | |
14 | (0,1,1) | (1,1,2,12) | 3842.593 | 3.46 | |
15 | (1,1,1) | (1,1,0,12) | 3844.410 | 0.35 | |
16 | (1,1,0) | (1,1,0,12) | 3846.786 | 0.28 | |
17 | (0,1,1) | (1,1,0,12) | 3852.231 | 0.35 | |
18 | (1,1,1) | (0,1,0,12) | 3866.187 | 0.13 | |
19 | (0,1,1) | (0,1,0,12) | 3873.797 | 0.15 | |
20 | (0,1,0) | (1,1,1,12) | 3903.204 | 0.53 | |
21 | (0,1,0) | (0,1,1,12) | 3904.615 | 0.23 | |
22 | (0,1,0) | (0,1,0,12) | 3942.897 | 0.03 |
将两个候选模型进行比较:一个是基于回测与 MAE 指标、由 grid_search_sarimax
选出的模型;另一个是基于 AIC、由 auto_arima
选出的模型。我们以每次 12 个月的批次方式,比较其对未来三年的预测表现。
# 基于网格搜索(回测+MAE)选出的最佳模型的回测预测
# ==============================================================================
forecaster = ForecasterSarimax(
regressor=Sarimax(order=(0, 1, 1), seasonal_order=(1, 1, 1, 12), maxiter=500),
)
cv = TimeSeriesFold(
steps = 12,
initial_train_size = len(data.loc[:end_val]),
refit = True,
)
metric_m1, predictions_m1 = backtesting_sarimax(
forecaster = forecaster,
y = data,
cv = cv,
metric = 'mean_absolute_error',
suppress_warnings_fit = True,
)
# 基于 auto_arima(AIC)选出的最佳模型的回测预测
# ==============================================================================
forecaster = ForecasterSarimax(
regressor=Sarimax(order=(1, 1, 1), seasonal_order=(0, 1, 1, 12), maxiter=500),
)
metric_m2, predictions_m2 = backtesting_sarimax(
forecaster = forecaster,
y = data,
cv = cv,
metric = 'mean_absolute_error',
suppress_warnings_fit = True,
)
0%| | 0/6 [00:00<?, ?it/s]
0%| | 0/6 [00:00<?, ?it/s]
# 对比预测结果
# ==============================================================================
print("评价指标 (mean_absolute_error) - 网格搜索模型:")
display(metric_m1)
print("评价指标 (mean_absolute_error) - auto arima 模型:")
display(metric_m2)
fig, ax = plt.subplots(figsize=(6, 3))
data.loc[end_val:].plot(ax=ax, label='test')
predictions_m1 = predictions_m1.rename(columns={'pred': 'grid search'})
predictions_m2 = predictions_m2.rename(columns={'pred': 'autoarima'})
predictions_m1.plot(ax=ax)
predictions_m2.plot(ax=ax)
ax.set_title('Backtest predictions with ARIMA model')
ax.legend();
评价指标 (mean_absolute_error) - 网格搜索模型:
mean_absolute_error | |
---|---|
0 | 19803.080683 |
评价指标 (mean_absolute_error) - auto arima 模型:
mean_absolute_error | |
---|---|
0 | 20149.352206 |
基于回测与 MAE 的网格搜索所选的 SARIMAX 配置,表现略优。
✎ 提示
由于 Auto ARIMA 的搜索速度很快,可先用它筛选一组初始候选模型。但鉴于其缺少业务可解释的评估指标,建议再通过回测对最终候选进行比较。外生变量
在 statsmodels 的 ARIMA-SARIMAX 实现中,可将外生变量一同纳入预测。唯一要求是:在预测区间内也必须已知这些外生变量的取值。通过 exog
参数即可为模型添加外生变量。
💡 小贴士
想了解外生变量及其在 skforecast 中的正确用法,请参阅:外生变量(特征)使用指南。复用已训练的 ARIMA 模型
当预测区间并非紧接训练期末端时,使用 ARIMA 进行预测会变得棘手。这主要源于 MA(滑动平均)成分:它将过去的预测误差作为预测因子。因此,要在时刻 做预测,就需要已知 时刻的预测误差;若没有该时刻的预测,误差也无法获得。出于这个原因,许多场景下需要在每次生成新预测时对 ARIMA 重新训练。
虽然加速训练的工作持续推进,但并非总能在两次预测之间重训模型:可能受时间或计算资源限制,无法频繁访问完整历史数据。折中做法是:在预测前,将“训练期末到预测起点之间”的观测补喂给模型,使其能够依次生成这些中间点的预测,从而得到相应的误差。举例来说,若模型是在 20 天前用近三年的日频数据训练的,那么生成新预测时,只需提供最近 20 天的观测,而不必再次加载全部历史(365 × 3 + 20)。
将新数据增量地馈入模型的过程较为繁琐,但 ForecasterSarimax
通过其 predict
方法中的 last_window
参数对这一流程做了自动化封装。
# 划分数据:训练集 - 最后一段窗口
# ==============================================================================
end_train = '1980-01-01 23:59:59'
print(
f"Train dates : {data.index.min()} --- {data.loc[:end_train].index.max()} "
f"(n={len(data.loc[:end_train])})"
)
print(
f"Last window dates : {data.loc[end_train:].index.min()} --- {data.index.max()} "
f"(n={len(data.loc[end_train:])})"
)
# 绘图
# =====================================================================================
fig, ax = plt.subplots(figsize=(7, 3))
data.loc[:end_train].plot(ax=ax, label='train')
data.loc[end_train:].plot(ax=ax, label='last window')
ax.set_title('Monthly fuel consumption in Spain')
ax.legend();
Train dates : 1969-01-01 00:00:00 --- 1980-01-01 00:00:00 (n=133) Last window dates : 1980-02-01 00:00:00 --- 1990-01-01 00:00:00 (n=120)
Forecaster 将使用截至 '1980-01-01' 的数据进行训练,随后用剩余部分作为“最后窗口”来生成新的预测。
# 训练 ARIMA(训练期:1969-01-01 至 1980-01-01)
# ==============================================================================
forecaster = ForecasterSarimax(
regressor = Sarimax(
order = (0, 1, 1),
seasonal_order = (1, 1, 1, 12),
maxiter = 500
)
)
forecaster.fit(y=data.loc[:end_train])
接下来预测未来 12 个时间步。
# 使用最后窗口进行预测
# ==============================================================================
predictions = forecaster.predict(
steps = 12,
last_window = data.loc[end_train:]
)
predictions.head(3)
1990-02-01 580893.320818 1990-03-01 693624.449225 1990-04-01 654315.472043 Freq: MS, Name: pred, dtype: float64
# 绘制预测
# =====================================================================================
fig, ax = plt.subplots(figsize=(7, 3))
data.loc[:end_train].plot(ax=ax, label='train')
data.loc[end_train:].plot(ax=ax, label='last window')
predictions.plot(ax=ax, label='predictions')
ax.set_title('Monthly fuel consumption in Spain')
ax.legend();
会话信息
import session_info
session_info.show(html=False)
----- matplotlib 3.10.1 numpy 1.26.4 pandas 2.3.1 pmdarima 2.0.4 session_info v1.0.1 skforecast 0.17.0 statsmodels 0.14.4 ----- IPython 9.1.0 jupyter_client 8.6.3 jupyter_core 5.7.2 jupyterlab 4.4.5 notebook 7.4.5 ----- Python 3.12.9 | packaged by Anaconda, Inc. | (main, Feb 6 2025, 18:56:27) [GCC 11.2.0] Linux-6.14.0-27-generic-x86_64-with-glibc2.39 ----- Session information updated at 2025-08-13 17:55
参考文献
Hyndman, R.J., & Athanasopoulos, G. (2021) 《Forecasting: principles and practice》(第 3 版),OTexts:墨尔本,澳大利亚。
Ivan Svetunkov,《Time Series Analysis and Forecasting with ADAM》。
《Python for Finance: Mastering Data-Driven Finance》。
《Forecasting: theory and practice》。
引用
如何引用本文档
如果您使用本文档或其中的任何部分,请注明出处,谢谢!
《使用Python进行ARIMA和SARIMAX模型》由Joaquín Amat Rodrigo和Javier Escobar Ortiz撰写,在Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 DEED)许可下提供,网址:https://www.cienciadedatos.net/documentos/py51-arima-sarimax-models-python.html
如何引用skforecast
如果您在出版物中使用skforecast,我们将很高兴您引用已发布的软件。
Zenodo:
Amat Rodrigo, Joaquin, & Escobar Ortiz, Javier. (2024). skforecast (v0.16.0). Zenodo. https://doi.org/10.5281/zenodo.8382788
APA:
Amat Rodrigo, J., & Escobar Ortiz, J. (2024). skforecast (Version 0.16.0) [Computer software]. https://doi.org/10.5281/zenodo.8382788
BibTeX:
@software{skforecast, author = {Amat Rodrigo, Joaquin and Escobar Ortiz, Javier}, title = {skforecast}, version = {0.16.0}, month = {05}, year = {2025}, license = {BSD-3-Clause}, url = {https://skforecast.org/}, doi = {10.5281/zenodo.8382788} }
您喜欢这篇文章吗?您的支持很重要
您的贡献将帮助我继续创建免费的教育内容。非常感谢!😊
Joaquín Amat Rodrigo和Javier Escobar Ortiz的这项作品在署名-非商业性使用-相同方式共享 4.0 国际许可协议下提供。
允许:
-
分享:以任何媒介或格式复制、发行本作品。
-
演绎:修改、转换或以本作品为基础进行创作。
惟须遵守下列条件:
-
署名:您必须给出适当的署名,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。
-
非商业性使用:您不得将本作品用于商业目的。
-
相同方式共享:如果您修改、转换或以本作品为基础进行创作,您必须基于与原先许可协议相同的许可协议分发您贡献的作品。