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), optionalexog- Select by AIC and by error on a held-out final period
- Check residual autocorrelation and use
get_forecastfor intervals
Watch out for
- Evaluating a forecast model on randomly sampled points instead of a final hold-out period.