SARIMAX#

class cultivars.univariate.box_jenkins.SARIMAX(endog, *, order, seasonal_order=(0, 0, 0, 0), trend='c', exog=None)[source]

Bases: _BoxJenkinsModel[ARMAResult]

Seasonal ARIMA with exogenous regressors – the general specification.

The model on the differenced series \(w_t = (1 - L)^d (1 - L^s)^D y_t\) is

\[\phi(L)\,\Phi(L^s)\,(w_t - x_t'\beta) = \theta(L)\,\Theta(L^s)\,\varepsilon_t, \qquad \varepsilon_t \sim N(0, \sigma^2),\]

with \(\phi(L) = 1 - \phi_1 L - \cdots - \phi_p L^p\), \(\theta(L) = 1 + \theta_1 L + \cdots + \theta_q L^q\), their seasonal counterparts in \(L^s\), and \(x_t\) the deterministic terms of trend followed by the columns of exog, differenced alongside the series. The moving-average sign convention is the plus one: a simulated \(e_t - 0.4 e_{t-1}\) is estimated as \(\theta_1 \approx -0.4\). Estimation is exact maximum likelihood through the Harvey state-space form and the Kalman filter, with the autoregressive and moving-average polynomials kept stationary and invertible by the partial-autocorrelation reparameterization; starting values come from a regression of the differenced series on \(x_t\) and a short autoregression on its residuals.

Every other class in this module narrows this one. Here alone exog is optional, because this is the form a user reaches for when they want the full surface rather than a named special case; ARIMAX is the non-seasonal variant that makes regressors mandatory.

Parameters:
  • endog (ArrayLike) – The endogenous series, one-dimensional, of length at least \(p + d + q + s(P + D + Q) + 2\).

  • order (tuple[int, int, int]) – Non-seasonal (p, d, q), non-negative integers.

  • seasonal_order (tuple[int, int, int, int]) – Seasonal (P, D, Q, s); s must be at least 2 whenever any of P, D, Q is positive.

  • trend (str) – Deterministic specification ("n", "c", "ct").

  • exog (ArrayLike | None) – Optional exogenous regressors, (nobs, k) or (nobs,), differenced alongside endog.

Raises:
  • SpecificationError – If an order tuple has the wrong length or a negative entry, seasonal terms are requested with a period below 2, or the trend is unrecognized.

  • DimensionError – If the series is not one-dimensional, is too short for the specification, or exog does not have one row per observation.

Note

Regressors enter as an observation intercept, so this is a regression with SARIMA errors, not a transfer-function model: exog shifts the level of \(w_t\) and the ARMA structure describes what remains. The differencing that d and D apply to endog is applied to exog too, so a regressor in levels against a series in first differences must be supplied in levels, and its coefficient reads on the differenced scale. A result with regressors cannot simulate – their future is not part of the model.

See also

References

Box, G. E. P., Jenkins, G. M., Reinsel, G. C., & Ljung, G. M. (2015). Time Series Analysis: Forecasting and Control, 5th ed. Wiley.

Harvey, A. C. (1989). Forecasting, Structural Time Series Models and the Kalman Filter. Cambridge University Press.

Example

The airline model on a series simulated from it:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> e = rng.standard_normal(400)
>>> y = np.zeros(400)
>>> for t in range(13, 400):
...     y[t] = (
...         y[t - 1] + y[t - 12] - y[t - 13]
...         + e[t] - 0.4 * e[t - 1] - 0.6 * e[t - 12] + 0.24 * e[t - 13]
...     )
>>> res = SARIMAX(y, order=(0, 1, 1), seasonal_order=(0, 1, 1, 12), trend="n").fit()
>>> res.nobs, res.ma_params.round(1).tolist(), res.seasonal_ma_params.round(1).tolist()
(387, [-0.4], [-0.6])
>>> res.is_invertible
True

A regression with AR(1) errors recovers its slopes:

>>> X = rng.standard_normal((400, 2))
>>> noise = rng.standard_normal(400)
>>> reg = SARIMAX(X @ [1.0, -2.0] + noise, order=(1, 0, 0), exog=X).fit()
>>> reg.k_exog, reg.beta[1:].round(1).tolist()
(2, [1.0, -2.0])