• 引言
  • 数据
  • 探索性分析
    • 平稳性
    • 自相关分析
    • 时间序列分解
    • 结论
  • ARIMA-SARIMAX 模型
    • Statsmodels
    • Skforecast
  • pmdarima
  • ForecasterSarimax
    • 训练 - 预测
    • 回测(Backtesting)
    • 模型调优(超参数 p、d、q)
  • 外生变量
  • 复用已训练的 ARIMA 模型
  • 会话信息
  • 参考文献
  • 引用


更多关于时间序列预测的内容请访问 cienciadedatos.net


引言

ARIMA(自回归积分滑动平均,AutoRegressive Integrated Moving Average)和 SARIMAX(带季节项与外生变量的自回归积分滑动平均,Seasonal AutoRegressive Integrated Moving Average with eXogenous regressors)是应用广泛的统计预测模型。该类模型由三部分构成:自回归(AR)将当前值与其过去(滞后)值关联;滑动平均(MA)假设回归误差是过去预测误差的线性组合;积分(I)表示通过差分将数据值替换为与前一时刻的差(该差分过程可能需要执行多次)。

尽管 ARIMA 模型广为人知,SARIMAX 在其基础上进一步扩展,便捷地纳入季节性结构与外生变量。

在 ARIMA-SARIMAX 记号中,参数 pdq 分别表示非季节部分的自回归阶数、差分阶数与滑动平均阶数;PDQ 则对应季节部分的同类参数,m 表示每个季节的周期长度。

  • p:自回归项的阶数(滞后阶数)。
  • d:差分阶数(对数据做差分的次数)。
  • q:滑动平均项的阶数。
  • P:季节部分自回归项的阶数。
  • D:季节部分的差分阶数。
  • Q:季节部分滑动平均项的阶数。
  • m:每个季节包含的周期数。

PDQm 均为 0,且模型不包含外生变量时,SARIMAX 退化为 ARIMA。

多种 Python 库提供了 ARIMA-SARIMAX 的实现,常见的有:

  • statsmodels:功能完备的统计建模库。其 API 对有 R 语言背景的用户更直观,相比之下与 scikit-learn 的面向对象风格略有不同。

  • pmdarimastatsmodels SARIMAX 的封装。其优势在于与 scikit-learn API 的无缝对接,便于熟悉 scikit-learn 规范的用户开展时间序列建模。

  • skforecast:在众多预测功能中,提供了新的 statsmodels SARIMAX 封装,亦遵循 scikit-learn API。实现与 pmdarima 类似,但仅保留 skforecast 所需的核心元素,从而显著加速。

  • statsForecast:提供常用的一元时间序列模型集合,包括自动 ARIMA、ETS、CES 与 Theta,并基于 numba 优化以获得高性能。

本指南聚焦于其中三者 —— statsmodelspmdarimaskforecast —— 并说明如何使用它们构建 ARIMA-SARIMAX 模型。同时引入 ForecasterSarimax 类,以便在 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)来评估平稳性。如果检验表明非平稳,应进行差分直至达到平稳。该分析有助于确定参数 d 的取值。

  • 自相关分析:绘制自相关函数(ACF)与偏自相关函数(PACF),识别数据点之间潜在的滞后关系。该可视化分析有助于确定 ARIMA 模型中的自回归(AR)与滑动平均(MA)项(pq)。

  • 季节分解:若怀疑存在季节性,可使用移动平均或 STL 等方法将序列分解为趋势、季节与残差成分,从而揭示潜在模式并识别季节性。这有助于确定 PDQm 的取值。

上述探索性分析为构建能够捕捉数据基本模式与联系的 ARIMA 模型奠定基础。

平稳性

判断时间序列是否平稳可采用多种方法:

  • 可视化检视:通过观察时序图,若出现明显的趋势或季节性,序列大概率为非平稳。

  • 分段统计量:在不同时间片上计算均值、方差等统计量,若差异显著,则序列非平稳。

  • 统计检验:如增强型 Dickey-Fuller(ADF)检验、Kwiatkowski-Phillips-Schmidt-Shin(KPSS)检验。

前面的图展示了序列存在明显的上升趋势,即均值随时间增加,说明该序列非平稳。

差分是去除趋势的常用方法。通过将当前值与前一时刻的差作为新值来构造新序列,即相邻观测之差。数学形式为:

ΔXt=XtXt1

其中,Xt 为时刻 t 的值,Xt1 为时刻 t1 的值,这称为一阶差分。如有需要可继续差分,直到达到所需的平稳性。

下面将对原始序列进行一阶与二阶差分,并应用统计检验来判断是否达到平稳。

增强型 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 参数 d 的合适取值为 1。

自相关分析

绘制时间序列的自相关函数(ACF)与偏自相关函数(PACF)有助于选择 pq。ACF 主要用于识别 q(MA 项的滞后阶数),PACF 则用于识别 p(AR 项的滞后阶数)。

警告

若平稳性分析表明需要差分,则后续分析应基于差分后的序列,以与 ARIMA 的建模方式保持一致。

自相关函数(ACF)

ACF 计算时间序列与其滞后值之间的相关性。在 ARIMA 建模中,若 ACF 在少数滞后后急剧衰减,表明数据具有有限的 MA 阶数,衰减的滞后位置可用于估计 q。若 ACF 呈现(阻尼)正弦振荡,则提示存在季节性,需要同时考虑季节项。

偏自相关函数(PACF)

PACF 衡量在控制中间滞后影响后,某个滞后与当前值之间的相关性。若 PACF 在某一滞后后迅速截尾,且其余落在置信区间内,提示相应阶数的 AR 模型。截尾的滞后可用于估计 p

✎ 提示

一些经验法则:
  • 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();

根据偏自相关函数,参数 p 的最佳值为 0。但为赋予模型一定的自回归成分,这里取 p=1。另一方面,基于自相关函数,q 建议取 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

警告

探索性数据分析是一个不断迭代的过程,结论可能随着推进而变化。请记住,上述图形仅提供初步指引,pqd 的最佳取值应综合这些图形、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()
SARIMAX Results
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 Sarimaxstatsmodels 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()
SARIMAX Results
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();

警告

尽管 pmdarimastatsmodels 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 的阶数上做选择。

✎ 提示

关于 AIC(赤池信息准则)的详细解读,推荐阅读 Rob J Hyndman 的博客Anderson 与 Burnham 的 AIC 误区与误解

进行超参数优化时,务必基于验证集而非测试集,以确保对模型性能的评估客观准确。

# 训练-验证-测试 数据
# =====================================================================================
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(滑动平均)成分:它将过去的预测误差作为预测因子。因此,要在时刻 t 做预测,就需要已知 t1 时刻的预测误差;若没有该时刻的预测,误差也无法获得。出于这个原因,许多场景下需要在每次生成新预测时对 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} }


您喜欢这篇文章吗?您的支持很重要

您的贡献将帮助我继续创建免费的教育内容。非常感谢!😊

成为GitHub赞助者 成为GitHub赞助者

知识共享许可协议

Joaquín Amat Rodrigo和Javier Escobar Ortiz的这项作品在署名-非商业性使用-相同方式共享 4.0 国际许可协议下提供。

允许:

  • 分享:以任何媒介或格式复制、发行本作品。

  • 演绎:修改、转换或以本作品为基础进行创作。

惟须遵守下列条件:

  • 署名:您必须给出适当的署名,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。

  • 非商业性使用:您不得将本作品用于商业目的

  • 相同方式共享:如果您修改、转换或以本作品为基础进行创作,您必须基于与原先许可协议相同的许可协议分发您贡献的作品。