Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2SARIMAX parameters class. 

3 

4Author: Chad Fulton 

5License: BSD-3 

6""" 

7import numpy as np 

8import pandas as pd 

9from numpy.polynomial import Polynomial 

10 

11from statsmodels.tsa.statespace.tools import is_invertible 

12from statsmodels.tsa.arima.tools import validate_basic 

13 

14 

15class SARIMAXParams(object): 

16 """ 

17 SARIMAX parameters. 

18 

19 Parameters 

20 ---------- 

21 spec : SARIMAXSpecification 

22 Specification of the SARIMAX model. 

23 

24 Attributes 

25 ---------- 

26 spec : SARIMAXSpecification 

27 Specification of the SARIMAX model. 

28 exog_names : list of str 

29 Names associated with exogenous parameters. 

30 ar_names : list of str 

31 Names associated with (non-seasonal) autoregressive parameters. 

32 ma_names : list of str 

33 Names associated with (non-seasonal) moving average parameters. 

34 seasonal_ar_names : list of str 

35 Names associated with seasonal autoregressive parameters. 

36 seasonal_ma_names : list of str 

37 Names associated with seasonal moving average parameters. 

38 param_names :list of str 

39 Names of all model parameters. 

40 k_exog_params : int 

41 Number of parameters associated with exogenous variables. 

42 k_ar_params : int 

43 Number of parameters associated with (non-seasonal) autoregressive 

44 lags. 

45 k_ma_params : int 

46 Number of parameters associated with (non-seasonal) moving average 

47 lags. 

48 k_seasonal_ar_params : int 

49 Number of parameters associated with seasonal autoregressive lags. 

50 k_seasonal_ma_params : int 

51 Number of parameters associated with seasonal moving average lags. 

52 k_params : int 

53 Total number of model parameters. 

54 """ 

55 

56 def __init__(self, spec): 

57 self.spec = spec 

58 

59 # Local copies of relevant attributes 

60 self.exog_names = spec.exog_names 

61 self.ar_names = spec.ar_names 

62 self.ma_names = spec.ma_names 

63 self.seasonal_ar_names = spec.seasonal_ar_names 

64 self.seasonal_ma_names = spec.seasonal_ma_names 

65 self.param_names = spec.param_names 

66 

67 self.k_exog_params = spec.k_exog_params 

68 self.k_ar_params = spec.k_ar_params 

69 self.k_ma_params = spec.k_ma_params 

70 self.k_seasonal_ar_params = spec.k_seasonal_ar_params 

71 self.k_seasonal_ma_params = spec.k_seasonal_ma_params 

72 self.k_params = spec.k_params 

73 

74 # Cache for holding parameter values 

75 self._params_split = spec.split_params( 

76 np.zeros(self.k_params) * np.nan, allow_infnan=True) 

77 self._params = None 

78 

79 @property 

80 def exog_params(self): 

81 """(array) Parameters associated with exogenous variables.""" 

82 return self._params_split['exog_params'] 

83 

84 @exog_params.setter 

85 def exog_params(self, value): 

86 if np.isscalar(value): 

87 value = [value] * self.k_exog_params 

88 self._params_split['exog_params'] = validate_basic( 

89 value, self.k_exog_params, title='exogenous coefficients') 

90 self._params = None 

91 

92 @property 

93 def ar_params(self): 

94 """(array) Autoregressive (non-seasonal) parameters.""" 

95 return self._params_split['ar_params'] 

96 

97 @ar_params.setter 

98 def ar_params(self, value): 

99 if np.isscalar(value): 

100 value = [value] * self.k_ar_params 

101 self._params_split['ar_params'] = validate_basic( 

102 value, self.k_ar_params, title='AR coefficients') 

103 self._params = None 

104 

105 @property 

106 def ar_poly(self): 

107 """(Polynomial) Autoregressive (non-seasonal) lag polynomial.""" 

108 coef = np.zeros(self.spec.max_ar_order + 1) 

109 coef[0] = 1 

110 ix = self.spec.ar_lags 

111 coef[ix] = -self._params_split['ar_params'] 

112 return Polynomial(coef) 

113 

114 @ar_poly.setter 

115 def ar_poly(self, value): 

116 # Convert from the polynomial to the parameters, and set that way 

117 if isinstance(value, Polynomial): 

118 value = value.coef 

119 value = validate_basic(value, self.spec.max_ar_order + 1, 

120 title='AR polynomial') 

121 if value[0] != 1: 

122 raise ValueError('AR polynomial constant must be equal to 1.') 

123 ar_params = [] 

124 for i in range(1, self.spec.max_ar_order + 1): 

125 if i in self.spec.ar_lags: 

126 ar_params.append(-value[i]) 

127 elif value[i] != 0: 

128 raise ValueError('AR polynomial includes non-zero values' 

129 ' for lags that are excluded in the' 

130 ' specification.') 

131 self.ar_params = ar_params 

132 

133 @property 

134 def ma_params(self): 

135 """(array) Moving average (non-seasonal) parameters.""" 

136 return self._params_split['ma_params'] 

137 

138 @ma_params.setter 

139 def ma_params(self, value): 

140 if np.isscalar(value): 

141 value = [value] * self.k_ma_params 

142 self._params_split['ma_params'] = validate_basic( 

143 value, self.k_ma_params, title='MA coefficients') 

144 self._params = None 

145 

146 @property 

147 def ma_poly(self): 

148 """(Polynomial) Moving average (non-seasonal) lag polynomial.""" 

149 coef = np.zeros(self.spec.max_ma_order + 1) 

150 coef[0] = 1 

151 ix = self.spec.ma_lags 

152 coef[ix] = self._params_split['ma_params'] 

153 return Polynomial(coef) 

154 

155 @ma_poly.setter 

156 def ma_poly(self, value): 

157 # Convert from the polynomial to the parameters, and set that way 

158 if isinstance(value, Polynomial): 

159 value = value.coef 

160 value = validate_basic(value, self.spec.max_ma_order + 1, 

161 title='MA polynomial') 

162 if value[0] != 1: 

163 raise ValueError('MA polynomial constant must be equal to 1.') 

164 ma_params = [] 

165 for i in range(1, self.spec.max_ma_order + 1): 

166 if i in self.spec.ma_lags: 

167 ma_params.append(value[i]) 

168 elif value[i] != 0: 

169 raise ValueError('MA polynomial includes non-zero values' 

170 ' for lags that are excluded in the' 

171 ' specification.') 

172 self.ma_params = ma_params 

173 

174 @property 

175 def seasonal_ar_params(self): 

176 """(array) Seasonal autoregressive parameters.""" 

177 return self._params_split['seasonal_ar_params'] 

178 

179 @seasonal_ar_params.setter 

180 def seasonal_ar_params(self, value): 

181 if np.isscalar(value): 

182 value = [value] * self.k_seasonal_ar_params 

183 self._params_split['seasonal_ar_params'] = validate_basic( 

184 value, self.k_seasonal_ar_params, title='seasonal AR coefficients') 

185 self._params = None 

186 

187 @property 

188 def seasonal_ar_poly(self): 

189 """(Polynomial) Seasonal autoregressive lag polynomial.""" 

190 # Need to expand the polynomial according to the season 

191 s = self.spec.seasonal_periods 

192 coef = [1] 

193 if s > 0: 

194 expanded = np.zeros(self.spec.max_seasonal_ar_order) 

195 ix = np.array(self.spec.seasonal_ar_lags, dtype=int) - 1 

196 expanded[ix] = -self._params_split['seasonal_ar_params'] 

197 coef = np.r_[1, np.pad(np.reshape(expanded, (-1, 1)), 

198 [(0, 0), (s - 1, 0)], 'constant').flatten()] 

199 return Polynomial(coef) 

200 

201 @seasonal_ar_poly.setter 

202 def seasonal_ar_poly(self, value): 

203 s = self.spec.seasonal_periods 

204 # Note: assume that we are given coefficients from the full polynomial 

205 # Convert from the polynomial to the parameters, and set that way 

206 if isinstance(value, Polynomial): 

207 value = value.coef 

208 value = validate_basic(value, 1 + s * self.spec.max_seasonal_ar_order, 

209 title='seasonal AR polynomial') 

210 if value[0] != 1: 

211 raise ValueError('Polynomial constant must be equal to 1.') 

212 seasonal_ar_params = [] 

213 for i in range(1, self.spec.max_seasonal_ar_order + 1): 

214 if i in self.spec.seasonal_ar_lags: 

215 seasonal_ar_params.append(-value[s * i]) 

216 elif value[s * i] != 0: 

217 raise ValueError('AR polynomial includes non-zero values' 

218 ' for lags that are excluded in the' 

219 ' specification.') 

220 self.seasonal_ar_params = seasonal_ar_params 

221 

222 @property 

223 def seasonal_ma_params(self): 

224 """(array) Seasonal moving average parameters.""" 

225 return self._params_split['seasonal_ma_params'] 

226 

227 @seasonal_ma_params.setter 

228 def seasonal_ma_params(self, value): 

229 if np.isscalar(value): 

230 value = [value] * self.k_seasonal_ma_params 

231 self._params_split['seasonal_ma_params'] = validate_basic( 

232 value, self.k_seasonal_ma_params, title='seasonal MA coefficients') 

233 self._params = None 

234 

235 @property 

236 def seasonal_ma_poly(self): 

237 """(Polynomial) Seasonal moving average lag polynomial.""" 

238 # Need to expand the polynomial according to the season 

239 s = self.spec.seasonal_periods 

240 coef = np.array([1]) 

241 if s > 0: 

242 expanded = np.zeros(self.spec.max_seasonal_ma_order) 

243 ix = np.array(self.spec.seasonal_ma_lags, dtype=int) - 1 

244 expanded[ix] = self._params_split['seasonal_ma_params'] 

245 coef = np.r_[1, np.pad(np.reshape(expanded, (-1, 1)), 

246 [(0, 0), (s - 1, 0)], 'constant').flatten()] 

247 return Polynomial(coef) 

248 

249 @seasonal_ma_poly.setter 

250 def seasonal_ma_poly(self, value): 

251 s = self.spec.seasonal_periods 

252 # Note: assume that we are given coefficients from the full polynomial 

253 # Convert from the polynomial to the parameters, and set that way 

254 if isinstance(value, Polynomial): 

255 value = value.coef 

256 value = validate_basic(value, 1 + s * self.spec.max_seasonal_ma_order, 

257 title='seasonal MA polynomial',) 

258 if value[0] != 1: 

259 raise ValueError('Polynomial constant must be equal to 1.') 

260 seasonal_ma_params = [] 

261 for i in range(1, self.spec.max_seasonal_ma_order + 1): 

262 if i in self.spec.seasonal_ma_lags: 

263 seasonal_ma_params.append(value[s * i]) 

264 elif value[s * i] != 0: 

265 raise ValueError('MA polynomial includes non-zero values' 

266 ' for lags that are excluded in the' 

267 ' specification.') 

268 self.seasonal_ma_params = seasonal_ma_params 

269 

270 @property 

271 def sigma2(self): 

272 """(float) Innovation variance.""" 

273 return self._params_split['sigma2'] 

274 

275 @sigma2.setter 

276 def sigma2(self, params): 

277 length = int(not self.spec.concentrate_scale) 

278 self._params_split['sigma2'] = validate_basic( 

279 params, length, title='sigma2').item() 

280 self._params = None 

281 

282 @property 

283 def reduced_ar_poly(self): 

284 """(Polynomial) Reduced form autoregressive lag polynomial.""" 

285 return self.ar_poly * self.seasonal_ar_poly 

286 

287 @property 

288 def reduced_ma_poly(self): 

289 """(Polynomial) Reduced form moving average lag polynomial.""" 

290 return self.ma_poly * self.seasonal_ma_poly 

291 

292 @property 

293 def params(self): 

294 """(array) Complete parameter vector.""" 

295 if self._params is None: 

296 self._params = self.spec.join_params(**self._params_split) 

297 return self._params.copy() 

298 

299 @params.setter 

300 def params(self, value): 

301 self._params_split = self.spec.split_params(value) 

302 self._params = None 

303 

304 @property 

305 def is_complete(self): 

306 """(bool) Are current parameter values all filled in (i.e. not NaN).""" 

307 return not np.any(np.isnan(self.params)) 

308 

309 @property 

310 def is_valid(self): 

311 """(bool) Are current parameter values valid (e.g. variance > 0).""" 

312 valid = True 

313 try: 

314 self.spec.validate_params(self.params) 

315 except ValueError: 

316 valid = False 

317 return valid 

318 

319 @property 

320 def is_stationary(self): 

321 """(bool) Is the reduced autoregressive lag poylnomial stationary.""" 

322 validate_basic(self.ar_params, self.k_ar_params, 

323 title='AR coefficients') 

324 validate_basic(self.seasonal_ar_params, self.k_seasonal_ar_params, 

325 title='seasonal AR coefficients') 

326 

327 ar_stationary = True 

328 seasonal_ar_stationary = True 

329 if self.k_ar_params > 0: 

330 ar_stationary = is_invertible(self.ar_poly.coef) 

331 if self.k_seasonal_ar_params > 0: 

332 seasonal_ar_stationary = is_invertible(self.seasonal_ar_poly.coef) 

333 

334 return ar_stationary and seasonal_ar_stationary 

335 

336 @property 

337 def is_invertible(self): 

338 """(bool) Is the reduced moving average lag poylnomial invertible.""" 

339 # Short-circuit if there is no MA component 

340 validate_basic(self.ma_params, self.k_ma_params, 

341 title='MA coefficients') 

342 validate_basic(self.seasonal_ma_params, self.k_seasonal_ma_params, 

343 title='seasonal MA coefficients') 

344 

345 ma_stationary = True 

346 seasonal_ma_stationary = True 

347 if self.k_ma_params > 0: 

348 ma_stationary = is_invertible(self.ma_poly.coef) 

349 if self.k_seasonal_ma_params > 0: 

350 seasonal_ma_stationary = is_invertible(self.seasonal_ma_poly.coef) 

351 

352 return ma_stationary and seasonal_ma_stationary 

353 

354 def to_dict(self): 

355 """ 

356 Return the parameters split by type into a dictionary. 

357 

358 Returns 

359 ------- 

360 split_params : dict 

361 Dictionary with keys 'exog_params', 'ar_params', 'ma_params', 

362 'seasonal_ar_params', 'seasonal_ma_params', and (unless 

363 `concentrate_scale=True`) 'sigma2'. Values are the parameters 

364 associated with the key, based on the `params` argument. 

365 """ 

366 return self._params_split.copy() 

367 

368 def to_pandas(self): 

369 """ 

370 Return the parameters as a Pandas series. 

371 

372 Returns 

373 ------- 

374 series : pd.Series 

375 Pandas series with index set to the parameter names. 

376 """ 

377 return pd.Series(self.params, index=self.param_names) 

378 

379 def __repr__(self): 

380 """Represent SARIMAXParams object as a string.""" 

381 components = [] 

382 if self.k_exog_params: 

383 components.append('exog=%s' % str(self.exog_params)) 

384 if self.k_ar_params: 

385 components.append('ar=%s' % str(self.ar_params)) 

386 if self.k_ma_params: 

387 components.append('ma=%s' % str(self.ma_params)) 

388 if self.k_seasonal_ar_params: 

389 components.append('seasonal_ar=%s' % 

390 str(self.seasonal_ar_params)) 

391 if self.k_seasonal_ma_params: 

392 components.append('seasonal_ma=%s' % 

393 str(self.seasonal_ma_params)) 

394 if not self.spec.concentrate_scale: 

395 components.append('sigma2=%s' % self.sigma2) 

396 return 'SARIMAXParams(%s)' % ', '.join(components)