@@ -67,52 +67,65 @@ class BayesianETS(PyMCStateSpace):
67
67
68
68
\begin{align}
69
69
y_t &= l_{t-1} + b_{t-1} + \epsilon_t \\
70
- l_t &= l_{t-1} + \alpha \epsilon_t \\
71
- b_t &= b_{t-1} + \beta \epsilon_t
70
+ l_t &= l_{t-1} + b_{t-1} + \alpha \epsilon_t \\
71
+ b_t &= b_{t-1} + \alpha \ beta^\star \epsilon_t
72
72
\end{align}
73
73
74
+ [1]_ also consider an alternative parameterization with :math:`\beta = \alpha \beta^\star`.
75
+
74
76
* `ETS(A,N,A)`: Additive seasonal method
75
77
76
78
.. math::
77
79
78
80
\begin{align}
79
81
y_t &= l_{t-1} + s_{t-m} + \epsilon_t \\
80
82
l_t &= l_{t-1} + \alpha \epsilon_t \\
81
- s_t &= s_{t-m} + \ gamma \epsilon_t
83
+ s_t &= s_{t-m} + (1 - \alpha)\ gamma^\star \epsilon_t
82
84
\end{align}
83
85
86
+ [1]_ also consider an alternative parameterization with :math:`\gamma = (1 - \alpha) \gamma^\star`.
87
+
84
88
* `ETS(A,A,A)`: Additive Holt-Winters method
85
89
86
90
.. math::
87
91
88
92
\begin{align}
89
93
y_t &= l_{t-1} + b_{t-1} + s_{t-m} + \epsilon_t \\
90
94
l_t &= l_{t-1} + \alpha \epsilon_t \\
91
- b_t &= b_{t-1} + \beta \epsilon_t \\
92
- s_t &= s_{t-m} + \ gamma \epsilon_t
95
+ b_t &= b_{t-1} + \alpha \ beta^\star \epsilon_t \\
96
+ s_t &= s_{t-m} + (1 - \alpha) \ gamma^\star \epsilon_t
93
97
\end{align}
94
98
99
+ [1]_ also consider an alternative parameterization with :math:`\beta = \alpha \beta^star` and
100
+ :math:`\gamma = (1 - \alpha) \gamma^\star`.
101
+
95
102
* `ETS(A, Ad, N)`: Dampened trend method
96
103
97
104
.. math::
98
105
99
106
\begin{align}
100
107
y_t &= l_{t-1} + b_{t-1} + \epsilon_t \\
101
108
l_t &= l_{t-1} + \alpha \epsilon_t \\
102
- b_t &= \phi b_{t-1} + \beta \epsilon_t
109
+ b_t &= \phi b_{t-1} + \alpha \ beta^\star \epsilon_t
103
110
\end{align}
104
111
112
+ [1]_ also consider an alternative parameterization with :math:`\beta = \alpha \beta^\star`.
113
+
105
114
* `ETS(A, Ad, A)`: Dampened trend with seasonal method
106
115
107
116
.. math::
108
117
109
118
\begin{align}
110
119
y_t &= l_{t-1} + b_{t-1} + s_{t-m} + \epsilon_t \\
111
120
l_t &= l_{t-1} + \alpha \epsilon_t \\
112
- b_t &= \phi b_{t-1} + \beta \epsilon_t \\
113
- s_t &= s_{t-m} + \ gamma \epsilon_t
121
+ b_t &= \phi b_{t-1} + \alpha \ beta^\star \epsilon_t \\
122
+ s_t &= s_{t-m} + (1 - \alpha) \ gamma^\star \epsilon_t
114
123
\end{align}
115
124
125
+ [1]_ also consider an alternative parameterization with :math:`\beta = \alpha \beta^star` and
126
+ :math:`\gamma = (1 - \alpha) \gamma^\star`.
127
+
128
+
116
129
Parameters
117
130
----------
118
131
endog: pd.DataFrame
@@ -138,6 +151,17 @@ class BayesianETS(PyMCStateSpace):
138
151
The number of periods in a complete seasonal cycle. Ignored if `seasonal` is `False`.
139
152
measurement_error: bool
140
153
Whether to include a measurement error term in the model. Default is `False`.
154
+ use_transformed_parameterization: bool, default False
155
+ If true, use the :math:`\alpha, \beta, \gamma` parameterization, otherwise use the :math:`\alpha, \beta^\star,
156
+ \gamma^\star` parameterization. This will change the admissible region for the priors.
157
+
158
+ - Under the **non-transformed** parameterization, all of :math:`\alpha, \beta^\star, \gamma^\star` should be
159
+ between 0 and 1.
160
+ - Under the **transformed** parameterization, :math:`\alpha \in (0, 1)`, :math:`\beta \in (0, \alpha)`, and
161
+ :math:`\gamma \in (0, 1 - \alpha)`
162
+
163
+ The :meth:`param_info` method will change to reflect the suggested intervals based on the value of this
164
+ argument.
141
165
filter_type: str, default "standard"
142
166
The type of Kalman Filter to use. Options are "standard", "single", "univariate", "steady_state",
143
167
and "cholesky". See the docs for kalman filters for more details.
@@ -157,6 +181,7 @@ def __init__(
157
181
seasonal : bool = False ,
158
182
seasonal_periods : int | None = None ,
159
183
measurement_error : bool = False ,
184
+ use_transformed_parameterization : bool = False ,
160
185
filter_type : str = "standard" ,
161
186
verbose : bool = True ,
162
187
):
@@ -184,6 +209,7 @@ def __init__(
184
209
self .damped_trend = damped_trend
185
210
self .seasonal = seasonal
186
211
self .seasonal_periods = seasonal_periods
212
+ self .use_transformed_parameterization = use_transformed_parameterization
187
213
188
214
if self .seasonal and self .seasonal_periods is None :
189
215
raise ValueError ("If seasonal is True, seasonal_periods must be provided." )
@@ -258,15 +284,19 @@ def param_info(self) -> dict[str, dict[str, Any]]:
258
284
},
259
285
"alpha" : {
260
286
"shape" : None ,
261
- "constraints" : "0 < Sum( alpha, beta, gamma) < 1" ,
287
+ "constraints" : "0 < alpha < 1" ,
262
288
},
263
289
"beta" : {
264
290
"shape" : None ,
265
- "constraints" : "0 < Sum(alpha, beta, gamma) < 1" ,
291
+ "constraints" : "0 < beta < 1"
292
+ if not self .use_transformed_parameterization
293
+ else "0 < beta < alpha" ,
266
294
},
267
295
"gamma" : {
268
296
"shape" : None ,
269
- "constraints" : "0 < Sum(alpha, beta, gamma) < 1" ,
297
+ "constraints" : "0 < gamma< 1"
298
+ if not self .use_transformed_parameterization
299
+ else "0 < gamma < (1 - alpha)" ,
270
300
},
271
301
"phi" : {
272
302
"shape" : None ,
@@ -342,11 +372,18 @@ def make_symbolic_graph(self) -> None:
342
372
343
373
# The shape of R can be pre-allocated, then filled with the required parameters
344
374
R = pt .zeros ((self .k_states , self .k_posdef ))
345
- R = pt .set_subtensor (R [0 , :], 1.0 ) # We will always have y_t = ... + e_t
346
375
347
376
alpha = self .make_and_register_variable ("alpha" , shape = (), dtype = floatX )
348
377
R = pt .set_subtensor (R [1 , 0 ], alpha ) # and l_t = ... + alpha * e_t
349
378
379
+ # The R[0, 0] entry needs to be adjusted for a shift in the time indices. Consider the (A, N, N) model:
380
+ # y_t = l_{t-1} + e_t
381
+ # l_t = l_{t-1} + alpha * e_t
382
+ # We want the first equation to be in terms of time t on the RHS, because our observation equation is always
383
+ # y_t = Z @ x_t. Re-arranging equation 2, we get l_{t-1} = l_t - alpha * e_t --> y_t = l_t + e_t - alpha * e_t
384
+ # --> y_t = l_t + (1 - alpha) * e_t
385
+ R = pt .set_subtensor (R [0 , :], (1 - alpha ))
386
+
350
387
# Shock and level component always exists, the base case is e_t = e_t and l_t = l_{t-1}
351
388
T_base = pt .as_tensor_variable (np .array ([[0.0 , 0.0 ], [0.0 , 1.0 ]]))
352
389
@@ -357,10 +394,12 @@ def make_symbolic_graph(self) -> None:
357
394
self .ssm ["initial_state" , 2 ] = initial_trend
358
395
359
396
beta = self .make_and_register_variable ("beta" , shape = (), dtype = floatX )
360
- R = pt .set_subtensor (R [2 , 0 ], beta )
397
+ if self .use_transformed_parameterization :
398
+ R = pt .set_subtensor (R [2 , 0 ], beta )
399
+ else :
400
+ R = pt .set_subtensor (R [2 , 0 ], alpha * beta )
361
401
362
402
# If a trend is requested, we have the following transition equations (omitting the shocks):
363
- # y_t = l_{t-1} + b_{t-1}
364
403
# l_t = l_{t-1} + b_{t-1}
365
404
# b_t = b_{t-1}
366
405
T_base = pt .as_tensor_variable (([0.0 , 0.0 , 0.0 ], [0.0 , 1.0 , 1.0 ], [0.0 , 0.0 , 1.0 ]))
@@ -369,7 +408,6 @@ def make_symbolic_graph(self) -> None:
369
408
phi = self .make_and_register_variable ("phi" , shape = (), dtype = floatX )
370
409
# We are always in the case where we have a trend, so we can add the dampening parameter to T_base defined
371
410
# in that branch. Transition equations become:
372
- # y_t = l_{t-1} + phi * b_{t-1}
373
411
# l_t = l_{t-1} + phi * b_{t-1}
374
412
# b_t = phi * b_{t-1}
375
413
T_base = pt .set_subtensor (T_base [1 :, 2 ], phi )
@@ -384,7 +422,21 @@ def make_symbolic_graph(self) -> None:
384
422
self .ssm ["initial_state" , 2 + int (self .trend ) :] = initial_seasonal
385
423
386
424
gamma = self .make_and_register_variable ("gamma" , shape = (), dtype = floatX )
387
- R = pt .set_subtensor (R [2 + int (self .trend ), 0 ], gamma )
425
+
426
+ if self .use_transformed_parameterization :
427
+ param = gamma
428
+ else :
429
+ param = (1 - alpha ) * gamma
430
+
431
+ R = pt .set_subtensor (R [2 + int (self .trend ), 0 ], param )
432
+
433
+ # Additional adjustment to the R[0, 0] position is required. Start from:
434
+ # y_t = l_{t-1} + s_{t-m} + e_t
435
+ # l_t = l_{t-1} + alpha * e_t
436
+ # s_t = s_{t-m} + gamma * e_t
437
+ # Solve for l_{t-1} and s_{t-m} in terms of l_t and s_t, then substitute into the observation equation:
438
+ # y_t = l_t + s_t - alpha * e_t - gamma * e_t + e_t --> y_t = l_t + s_t + (1 - alpha - gamma) * e_t
439
+ R = pt .set_subtensor (R [0 , 0 ], R [0 , 0 ] - param )
388
440
389
441
# The seasonal component is always going to look like a TimeFrequency structural component, see that
390
442
# docstring for more details
0 commit comments