"""
SARIMAX parameters class.
Author: Chad Fulton
License: BSD-3
"""
import numpy as np
import pandas as pd
from numpy.polynomial import Polynomial
from statsmodels.tsa.statespace.tools import is_invertible
from statsmodels.tsa.arima.tools import validate_basic
class SARIMAXParams(object):
"""
SARIMAX parameters.
Parameters
----------
spec : SARIMAXSpecification
Specification of the SARIMAX model.
Attributes
----------
spec : SARIMAXSpecification
Specification of the SARIMAX model.
exog_names : list of str
Names associated with exogenous parameters.
ar_names : list of str
Names associated with (non-seasonal) autoregressive parameters.
ma_names : list of str
Names associated with (non-seasonal) moving average parameters.
seasonal_ar_names : list of str
Names associated with seasonal autoregressive parameters.
seasonal_ma_names : list of str
Names associated with seasonal moving average parameters.
param_names :list of str
Names of all model parameters.
k_exog_params : int
Number of parameters associated with exogenous variables.
k_ar_params : int
Number of parameters associated with (non-seasonal) autoregressive
lags.
k_ma_params : int
Number of parameters associated with (non-seasonal) moving average
lags.
k_seasonal_ar_params : int
Number of parameters associated with seasonal autoregressive lags.
k_seasonal_ma_params : int
Number of parameters associated with seasonal moving average lags.
k_params : int
Total number of model parameters.
"""
def __init__(self, spec):
self.spec = spec
# Local copies of relevant attributes
self.exog_names = spec.exog_names
self.ar_names = spec.ar_names
self.ma_names = spec.ma_names
self.seasonal_ar_names = spec.seasonal_ar_names
self.seasonal_ma_names = spec.seasonal_ma_names
self.param_names = spec.param_names
self.k_exog_params = spec.k_exog_params
self.k_ar_params = spec.k_ar_params
self.k_ma_params = spec.k_ma_params
self.k_seasonal_ar_params = spec.k_seasonal_ar_params
self.k_seasonal_ma_params = spec.k_seasonal_ma_params
self.k_params = spec.k_params
# Cache for holding parameter values
self._params_split = spec.split_params(
np.zeros(self.k_params) * np.nan, allow_infnan=True)
self._params = None
@property
def exog_params(self):
"""(array) Parameters associated with exogenous variables."""
return self._params_split['exog_params']
@exog_params.setter
def exog_params(self, value):
if np.isscalar(value):
value = [value] * self.k_exog_params
self._params_split['exog_params'] = validate_basic(
value, self.k_exog_params, title='exogenous coefficients')
self._params = None
@property
def ar_params(self):
"""(array) Autoregressive (non-seasonal) parameters."""
return self._params_split['ar_params']
@ar_params.setter
def ar_params(self, value):
if np.isscalar(value):
value = [value] * self.k_ar_params
self._params_split['ar_params'] = validate_basic(
value, self.k_ar_params, title='AR coefficients')
self._params = None
@property
def ar_poly(self):
"""(Polynomial) Autoregressive (non-seasonal) lag polynomial."""
coef = np.zeros(self.spec.max_ar_order + 1)
coef[0] = 1
ix = self.spec.ar_lags
coef[ix] = -self._params_split['ar_params']
return Polynomial(coef)
@ar_poly.setter
def ar_poly(self, value):
# Convert from the polynomial to the parameters, and set that way
if isinstance(value, Polynomial):
value = value.coef
value = validate_basic(value, self.spec.max_ar_order + 1,
title='AR polynomial')
if value[0] != 1:
raise ValueError('AR polynomial constant must be equal to 1.')
ar_params = []
for i in range(1, self.spec.max_ar_order + 1):
if i in self.spec.ar_lags:
ar_params.append(-value[i])
elif value[i] != 0:
raise ValueError('AR polynomial includes non-zero values'
' for lags that are excluded in the'
' specification.')
self.ar_params = ar_params
@property
def ma_params(self):
"""(array) Moving average (non-seasonal) parameters."""
return self._params_split['ma_params']
@ma_params.setter
def ma_params(self, value):
if np.isscalar(value):
value = [value] * self.k_ma_params
self._params_split['ma_params'] = validate_basic(
value, self.k_ma_params, title='MA coefficients')
self._params = None
@property
def ma_poly(self):
"""(Polynomial) Moving average (non-seasonal) lag polynomial."""
coef = np.zeros(self.spec.max_ma_order + 1)
coef[0] = 1
ix = self.spec.ma_lags
coef[ix] = self._params_split['ma_params']
return Polynomial(coef)
@ma_poly.setter
def ma_poly(self, value):
# Convert from the polynomial to the parameters, and set that way
if isinstance(value, Polynomial):
value = value.coef
value = validate_basic(value, self.spec.max_ma_order + 1,
title='MA polynomial')
if value[0] != 1:
raise ValueError('MA polynomial constant must be equal to 1.')
ma_params = []
for i in range(1, self.spec.max_ma_order + 1):
if i in self.spec.ma_lags:
ma_params.append(value[i])
elif value[i] != 0:
raise ValueError('MA polynomial includes non-zero values'
' for lags that are excluded in the'
' specification.')
self.ma_params = ma_params
@property
def seasonal_ar_params(self):
"""(array) Seasonal autoregressive parameters."""
return self._params_split['seasonal_ar_params']
@seasonal_ar_params.setter
def seasonal_ar_params(self, value):
if np.isscalar(value):
value = [value] * self.k_seasonal_ar_params
self._params_split['seasonal_ar_params'] = validate_basic(
value, self.k_seasonal_ar_params, title='seasonal AR coefficients')
self._params = None
@property
def seasonal_ar_poly(self):
"""(Polynomial) Seasonal autoregressive lag polynomial."""
# Need to expand the polynomial according to the season
s = self.spec.seasonal_periods
coef = [1]
if s > 0:
expanded = np.zeros(self.spec.max_seasonal_ar_order)
ix = np.array(self.spec.seasonal_ar_lags, dtype=int) - 1
expanded[ix] = -self._params_split['seasonal_ar_params']
coef = np.r_[1, np.pad(np.reshape(expanded, (-1, 1)),
[(0, 0), (s - 1, 0)], 'constant').flatten()]
return Polynomial(coef)
@seasonal_ar_poly.setter
def seasonal_ar_poly(self, value):
s = self.spec.seasonal_periods
# Note: assume that we are given coefficients from the full polynomial
# Convert from the polynomial to the parameters, and set that way
if isinstance(value, Polynomial):
value = value.coef
value = validate_basic(value, 1 + s * self.spec.max_seasonal_ar_order,
title='seasonal AR polynomial')
if value[0] != 1:
raise ValueError('Polynomial constant must be equal to 1.')
seasonal_ar_params = []
for i in range(1, self.spec.max_seasonal_ar_order + 1):
if i in self.spec.seasonal_ar_lags:
seasonal_ar_params.append(-value[s * i])
elif value[s * i] != 0:
raise ValueError('AR polynomial includes non-zero values'
' for lags that are excluded in the'
' specification.')
self.seasonal_ar_params = seasonal_ar_params
@property
def seasonal_ma_params(self):
"""(array) Seasonal moving average parameters."""
return self._params_split['seasonal_ma_params']
@seasonal_ma_params.setter
def seasonal_ma_params(self, value):
if np.isscalar(value):
value = [value] * self.k_seasonal_ma_params
self._params_split['seasonal_ma_params'] = validate_basic(
value, self.k_seasonal_ma_params, title='seasonal MA coefficients')
self._params = None
@property
def seasonal_ma_poly(self):
"""(Polynomial) Seasonal moving average lag polynomial."""
# Need to expand the polynomial according to the season
s = self.spec.seasonal_periods
coef = np.array([1])
if s > 0:
expanded = np.zeros(self.spec.max_seasonal_ma_order)
ix = np.array(self.spec.seasonal_ma_lags, dtype=int) - 1
expanded[ix] = self._params_split['seasonal_ma_params']
coef = np.r_[1, np.pad(np.reshape(expanded, (-1, 1)),
[(0, 0), (s - 1, 0)], 'constant').flatten()]
return Polynomial(coef)
@seasonal_ma_poly.setter
def seasonal_ma_poly(self, value):
s = self.spec.seasonal_periods
# Note: assume that we are given coefficients from the full polynomial
# Convert from the polynomial to the parameters, and set that way
if isinstance(value, Polynomial):
value = value.coef
value = validate_basic(value, 1 + s * self.spec.max_seasonal_ma_order,
title='seasonal MA polynomial',)
if value[0] != 1:
raise ValueError('Polynomial constant must be equal to 1.')
seasonal_ma_params = []
for i in range(1, self.spec.max_seasonal_ma_order + 1):
if i in self.spec.seasonal_ma_lags:
seasonal_ma_params.append(value[s * i])
elif value[s * i] != 0:
raise ValueError('MA polynomial includes non-zero values'
' for lags that are excluded in the'
' specification.')
self.seasonal_ma_params = seasonal_ma_params
@property
def sigma2(self):
"""(float) Innovation variance."""
return self._params_split['sigma2']
@sigma2.setter
def sigma2(self, params):
length = int(not self.spec.concentrate_scale)
self._params_split['sigma2'] = validate_basic(
params, length, title='sigma2').item()
self._params = None
@property
def reduced_ar_poly(self):
"""(Polynomial) Reduced form autoregressive lag polynomial."""
return self.ar_poly * self.seasonal_ar_poly
@property
def reduced_ma_poly(self):
"""(Polynomial) Reduced form moving average lag polynomial."""
return self.ma_poly * self.seasonal_ma_poly
@property
def params(self):
"""(array) Complete parameter vector."""
if self._params is None:
self._params = self.spec.join_params(**self._params_split)
return self._params.copy()
@params.setter
def params(self, value):
self._params_split = self.spec.split_params(value)
self._params = None
@property
def is_complete(self):
"""(bool) Are current parameter values all filled in (i.e. not NaN)."""
return not np.any(np.isnan(self.params))
@property
def is_valid(self):
"""(bool) Are current parameter values valid (e.g. variance > 0)."""
valid = True
try:
self.spec.validate_params(self.params)
except ValueError:
valid = False
return valid
@property
def is_stationary(self):
"""(bool) Is the reduced autoregressive lag poylnomial stationary."""
validate_basic(self.ar_params, self.k_ar_params,
title='AR coefficients')
validate_basic(self.seasonal_ar_params, self.k_seasonal_ar_params,
title='seasonal AR coefficients')
ar_stationary = True
seasonal_ar_stationary = True
if self.k_ar_params > 0:
ar_stationary = is_invertible(self.ar_poly.coef)
if self.k_seasonal_ar_params > 0:
seasonal_ar_stationary = is_invertible(self.seasonal_ar_poly.coef)
return ar_stationary and seasonal_ar_stationary
@property
def is_invertible(self):
"""(bool) Is the reduced moving average lag poylnomial invertible."""
# Short-circuit if there is no MA component
validate_basic(self.ma_params, self.k_ma_params,
title='MA coefficients')
validate_basic(self.seasonal_ma_params, self.k_seasonal_ma_params,
title='seasonal MA coefficients')
ma_stationary = True
Loading ...