ARIMA and SARIMAX forecasting

statsmodels · Advanced · 14 min

ARIMA-family models are the standard statistical baseline for forecasting with honest uncertainty.

An ARIMA(p, d, q) model differences the series d times, then models it with p autoregressive lags and q moving-average error terms. Seasonal ARIMA adds (P, D, Q, s) terms at the seasonal lag s (12 for monthly data). SARIMAX further adds exogenous regressors such as price or holidays.

In statsmodels:

from statsmodels.tsa.statespace.sarimax import SARIMAX
res = SARIMAX(y, exog=X, order=(1, 1, 1), seasonal_order=(0, 1, 1, 12)).fit(disp=False)

statsmodels.tsa.arima.model.ARIMA offers the same model class with a simpler interface and more estimation methods.

Choosing orders: use ACF/PACF of the differenced series as a starting point, then compare candidates by AIC on the same data and by out-of-sample error on a held-out final period.

After fitting, check the residuals: res.plot_diagnostics() and res.test_serial_correlation("ljungbox") should show no remaining autocorrelation.

Forecasting: fc = res.get_forecast(steps=12, exog=future_X), then fc.predicted_mean and fc.conf_int(). Exogenous regressors must be known (or forecast) for the future period. Intervals widen with the horizon, as they should.

For a new observation, res.append(new_y) or res.extend(new_y) updates the model without refitting from scratch.

Example

import numpy as np
import pandas as pd
from statsmodels.tsa.statespace.sarimax import SARIMAX

rng = np.random.default_rng(10)
idx = pd.date_range("2016-01-01", periods=96, freq="MS")
t = np.arange(96)
y = pd.Series(200 + 1.5 * t + 15 * np.sin(2 * np.pi * t / 12) + rng.normal(0, 3, 96), index=idx)
train, test = y[:-12], y[-12:]

res = SARIMAX(train, order=(1, 1, 1), seasonal_order=(0, 1, 1, 12)).fit(disp=False)
print(round(res.aic, 1))                              # 362.7

fc = res.get_forecast(steps=12)
mae = (fc.predicted_mean - test).abs().mean()
print(round(mae, 2))                                  # 2.75
print(fc.conf_int().iloc[[0, -1]].round(1).values.tolist())
# [[319.5, 329.8], [328.6, 339.2]]  (interval widens with horizon)
print(round(res.test_serial_correlation("ljungbox")[0, 1, -1], 2))  # 0.56 (no leftover autocorrelation)

Remember

  • order=(p, d, q), seasonal_order=(P, D, Q, s), optional exog
  • Select by AIC and by error on a held-out final period
  • Check residual autocorrelation and use get_forecast for intervals

Watch out for

  • Evaluating a forecast model on randomly sampled points instead of a final hold-out period.

Check yourself

  1. What must you provide to forecast a SARIMAX model that was fitted with exogenous regressors?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.statsmodels.org/stable/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.html