Seasonal ARIMA with eXogenous regressors, estimated by Kalman-filter MLE.
Classes
Seasonal AutoRegressive Integrated Moving Average model with eXogenous regressors, estimated by exact Gaussian maximum likelihood through a Kalman filter.
__init__( self, order: Tuple[int, int, int] = (), seasonal_order: Tuple[int, int, int, int] = (), trend: Optional[str] = None, maxiter: int = 50, tol: float = 1e-06, enforce_stationarity: bool = True, enforce_invertibility: bool = True, )
Overview
The estimation procedure runs as follows:
- Difference the series d times at lag 1 and D times at
- Expand the multiplicative seasonal polynomials
- Place the resulting ARMA process in Harvey's companion state-space
- Initialise the state covariance from the stationary solution of the
- Run the Kalman filter, concentrating \sigma^2 out of the
- Forecast by iterating the state transition forward, then invert the
Theory
The model is
Writing the differenced, regression-adjusted series as w_t, the state-space representation is
with T the companion matrix of the expanded AR polynomial, R = (1, \theta_1, \dots, \theta_{r-1})^{\top} and Z = (1, 0, \dots, 0). The Kalman recursions give the one-step-ahead innovations v_t and their variances \sigma^2 F_t, and the exact log-likelihood is
Concentrating out \sigma^2 yields \hat{\sigma}^2 = n^{-1}\sum_t v_t^2 / F_t, so only the ARMA and regression parameters remain to be optimised numerically.
Stationarity is enforced structurally. Each AR block is parameterised by partial autocorrelations r_k = \tanh(u_k) \in (-1, 1) which the Levinson-Durbin recursion maps to coefficients whose polynomial has all roots outside the unit circle, so the optimiser physically cannot reach an explosive region.
Parameters
order
seasonal_order
m of 0 or 1 disables the seasonal component.
trend
"c" a constant, "t" a linear time index, "ct" both.
maxiter
tol
enforce_stationarity
enforce_invertibility
Attributes
ar_params_
ma_params_
seasonal_ar_params_
seasonal_ma_params_
exog_params_
trend_params_
trend.
sigma2_
loglik_
aic_
bic_
resid_
state_
state_cov_
state_, in units of :math:`\sigma^2`.
n_obs_
fit.
Notes
Complexity:
- Training: O(\text{maxiter} \cdot k \cdot n r^3) where
- Prediction: O(h r^2) for h steps.
- You have exogenous regressors (price, temperature, a promotion
- You need a genuine seasonal (P, D, Q, m) component rather
- You want exact maximum likelihood and calibrated forecast
Relationship to ARIMA: the two are deliberately not interchangeable. ARIMA is the light, fast option: it estimates a non-seasonal (p, d, q) model by Yule-Walker plus conditional-sum-of-squares refinement, it ignores exogenous input (its fit signature spells the argument _X), and its seasonal_order argument is accepted but not acted upon. Reach for SARIMAX whenever you actually need exogenous regressors, a working seasonal specification, exact-likelihood estimates, or forecast intervals; reach for ARIMA when you want a cheap non-seasonal point forecast and nothing more.
Interval caveat: predict_interval builds the forecast variance from the MA(\infty) weights of the expanded model, which includes the differencing operators, so intervals widen correctly with the horizon under differencing. Parameter-estimation uncertainty is not included -- the intervals condition on the fitted parameters.
References
See Also
>>> import numpy as np
>>> from tuiml.algorithms.timeseries.sarimax import SARIMAX
>>> rng = np.random.default_rng(0)
>>> eps = rng.normal(scale=0.5, size=400)
>>> y = np.zeros(400)
>>> for t in range(1, 400):
... y[t] = 0.7 * y[t - 1] + eps[t]
>>> model = SARIMAX(order=(1, 0, 0)).fit(y)
>>> bool(abs(model.ar_params_[0] - 0.7) < 0.05)
True
>>> model.predict(steps=3).shape
(3,)
An exogenous regression recovers the true coefficient:
>>> x = rng.normal(size=(300, 1))
>>> y2 = 3.0 * x[:, 0] + 0.01 * rng.normal(size=300)
>>> m2 = SARIMAX(order=(0, 0, 0)).fit(y2, x)
>>> bool(abs(m2.exog_params_[0] - 3.0) < 0.01)
True
>>> bool(np.all(np.abs(m2.predict(steps=2, X=np.zeros((2, 1)))) < 0.01))
True
Calling predict without the future exogenous values is an error:
>>> m2.predict(steps=2)
Traceback (most recent call last):
...
ValueError: This SARIMAX was fitted with 1 exogenous regressor(s); predict() requires X with the future values for those regressors.
Methods
fit
(self, y: np.ndarray, X: Optional[np.ndarray]=None) -> 'SARIMAX'
fit
(self, y: np.ndarray, X: Optional[np.ndarray]=None) -> 'SARIMAX'
Fit the model by exact maximum likelihood.
Parameters
y
X
y.
Returns
self
predict
(self, steps: int=1, X: Optional[np.ndarray]=None) -> np.ndarray
predict
(self, steps: int=1, X: Optional[np.ndarray]=None) -> np.ndarray
Forecast future values.
Parameters
steps
X
Returns
forecast
predict_interval
(self, steps: int=1, alpha: float=0.05, X: Optional[np.ndarray]=None)
predict_interval
(self, steps: int=1, alpha: float=0.05, X: Optional[np.ndarray]=None)
Forecast with a Gaussian prediction interval.
Parameters
steps
alpha
0.05 gives a 95% interval.
X
Returns
forecast
lower
upper