pymc-extras 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymc_extras/inference/laplace_approx/laplace.py +2 -2
- pymc_extras/inference/pathfinder/pathfinder.py +1 -1
- pymc_extras/prior.py +3 -3
- pymc_extras/statespace/core/properties.py +276 -0
- pymc_extras/statespace/core/statespace.py +180 -44
- pymc_extras/statespace/filters/distributions.py +12 -29
- pymc_extras/statespace/filters/kalman_filter.py +1 -1
- pymc_extras/statespace/models/DFM.py +179 -168
- pymc_extras/statespace/models/ETS.py +177 -151
- pymc_extras/statespace/models/SARIMAX.py +149 -152
- pymc_extras/statespace/models/VARMAX.py +134 -145
- pymc_extras/statespace/models/__init__.py +8 -1
- pymc_extras/statespace/models/structural/__init__.py +30 -8
- pymc_extras/statespace/models/structural/components/autoregressive.py +87 -45
- pymc_extras/statespace/models/structural/components/cycle.py +119 -80
- pymc_extras/statespace/models/structural/components/level_trend.py +95 -42
- pymc_extras/statespace/models/structural/components/measurement_error.py +27 -17
- pymc_extras/statespace/models/structural/components/regression.py +105 -68
- pymc_extras/statespace/models/structural/components/seasonality.py +138 -100
- pymc_extras/statespace/models/structural/core.py +397 -286
- pymc_extras/statespace/models/utilities.py +5 -20
- {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/METADATA +3 -3
- {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/RECORD +25 -24
- {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/WHEEL +0 -0
- {pymc_extras-0.7.0.dist-info → pymc_extras-0.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
|
|
3
5
|
from pytensor import tensor as pt
|
|
4
|
-
from pytensor.tensor.
|
|
5
|
-
|
|
6
|
+
from pytensor.tensor.linalg import block_diag
|
|
7
|
+
|
|
8
|
+
from pymc_extras.statespace.core.properties import (
|
|
9
|
+
Coord,
|
|
10
|
+
Parameter,
|
|
11
|
+
Shock,
|
|
12
|
+
State,
|
|
13
|
+
)
|
|
6
14
|
from pymc_extras.statespace.models.structural.core import Component
|
|
7
15
|
from pymc_extras.statespace.models.structural.utils import _frequency_transition_block
|
|
8
16
|
|
|
9
17
|
|
|
10
|
-
class
|
|
18
|
+
class Cycle(Component):
|
|
11
19
|
r"""
|
|
12
20
|
A component for modeling longer-term cyclical effects
|
|
13
21
|
|
|
@@ -64,7 +72,7 @@ class CycleComponent(Component):
|
|
|
64
72
|
effects, such as business cycles, and that the seasonal component be used for shorter term effects, such as
|
|
65
73
|
weekly or monthly seasonality.
|
|
66
74
|
|
|
67
|
-
Unlike a FrequencySeasonality component, the length of a
|
|
75
|
+
Unlike a FrequencySeasonality component, the length of a Cycle can be estimated.
|
|
68
76
|
|
|
69
77
|
**Multivariate Support:**
|
|
70
78
|
For multivariate time series with k endogenous variables, the component creates:
|
|
@@ -89,8 +97,8 @@ class CycleComponent(Component):
|
|
|
89
97
|
data = np.random.normal(size=(100, 1))
|
|
90
98
|
|
|
91
99
|
# Build the structural model
|
|
92
|
-
grw = st.
|
|
93
|
-
cycle = st.
|
|
100
|
+
grw = st.LevelTrend(order=1, innovations_order=1)
|
|
101
|
+
cycle = st.Cycle(
|
|
94
102
|
"business_cycle", cycle_length=12, estimate_cycle_length=False, innovations=True, dampen=True
|
|
95
103
|
)
|
|
96
104
|
ss_mod = (grw + cycle).build()
|
|
@@ -117,7 +125,7 @@ class CycleComponent(Component):
|
|
|
117
125
|
.. code:: python
|
|
118
126
|
|
|
119
127
|
# Multivariate cycle component
|
|
120
|
-
cycle = st.
|
|
128
|
+
cycle = st.Cycle(
|
|
121
129
|
name='business_cycle',
|
|
122
130
|
cycle_length=12,
|
|
123
131
|
estimate_cycle_length=False,
|
|
@@ -188,18 +196,111 @@ class CycleComponent(Component):
|
|
|
188
196
|
obs_state_idx = np.zeros(k_states)
|
|
189
197
|
obs_state_idx[slice(0, k_states, 2)] = 1
|
|
190
198
|
|
|
199
|
+
state_names = [f"{f}_{name}" for f in ["Cos", "Sin"]]
|
|
200
|
+
|
|
191
201
|
super().__init__(
|
|
192
202
|
name=name,
|
|
193
203
|
k_endog=k_endog,
|
|
194
204
|
k_states=k_states,
|
|
195
205
|
k_posdef=k_posdef,
|
|
206
|
+
base_state_names=state_names,
|
|
196
207
|
measurement_error=False,
|
|
197
208
|
combine_hidden_states=True,
|
|
198
209
|
obs_state_idxs=obs_state_idx,
|
|
199
|
-
|
|
210
|
+
base_observed_state_names=observed_state_names,
|
|
200
211
|
share_states=share_states,
|
|
201
212
|
)
|
|
202
213
|
|
|
214
|
+
def set_states(self) -> State | tuple[State, ...] | None:
|
|
215
|
+
k_endog_effective = 1 if self.share_states else self.k_endog
|
|
216
|
+
|
|
217
|
+
base_names = self.base_state_names
|
|
218
|
+
observed_state_names = self.base_observed_state_names
|
|
219
|
+
|
|
220
|
+
if self.share_states:
|
|
221
|
+
state_names = [f"{name}[shared]" for name in base_names]
|
|
222
|
+
else:
|
|
223
|
+
state_names = [
|
|
224
|
+
f"{name}[{var_name}]" if k_endog_effective > 1 else name
|
|
225
|
+
for var_name in observed_state_names
|
|
226
|
+
for name in base_names
|
|
227
|
+
]
|
|
228
|
+
hidden_states = [State(name=name, observed=False, shared=True) for name in state_names]
|
|
229
|
+
observed_states = [
|
|
230
|
+
State(name=name, observed=True, shared=False) for name in observed_state_names
|
|
231
|
+
]
|
|
232
|
+
return *hidden_states, *observed_states
|
|
233
|
+
|
|
234
|
+
def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
|
|
235
|
+
k_endog = self.k_endog
|
|
236
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
237
|
+
|
|
238
|
+
cycle_param = Parameter(
|
|
239
|
+
name=f"params_{self.name}",
|
|
240
|
+
shape=(2,) if k_endog_effective == 1 else (k_endog_effective, 2),
|
|
241
|
+
dims=(f"state_{self.name}",)
|
|
242
|
+
if k_endog_effective == 1
|
|
243
|
+
else (f"endog_{self.name}", f"state_{self.name}"),
|
|
244
|
+
constraints=None,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
params_container = [cycle_param]
|
|
248
|
+
|
|
249
|
+
if self.estimate_cycle_length:
|
|
250
|
+
length_param = Parameter(
|
|
251
|
+
name=f"length_{self.name}",
|
|
252
|
+
shape=() if k_endog_effective == 1 else (k_endog_effective,),
|
|
253
|
+
dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
|
|
254
|
+
constraints="Positive, non-zero",
|
|
255
|
+
)
|
|
256
|
+
params_container.append(length_param)
|
|
257
|
+
|
|
258
|
+
if self.dampen:
|
|
259
|
+
dampen_param = Parameter(
|
|
260
|
+
name=f"dampening_factor_{self.name}",
|
|
261
|
+
shape=() if k_endog_effective == 1 else (k_endog_effective,),
|
|
262
|
+
dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
|
|
263
|
+
constraints="0 < x ≤ 1",
|
|
264
|
+
)
|
|
265
|
+
params_container.append(dampen_param)
|
|
266
|
+
|
|
267
|
+
if self.innovations:
|
|
268
|
+
sigma_param = Parameter(
|
|
269
|
+
name=f"sigma_{self.name}",
|
|
270
|
+
shape=() if k_endog_effective == 1 else (k_endog_effective,),
|
|
271
|
+
dims=None if k_endog_effective == 1 else (f"endog_{self.name}",),
|
|
272
|
+
constraints="Positive",
|
|
273
|
+
)
|
|
274
|
+
params_container.append(sigma_param)
|
|
275
|
+
|
|
276
|
+
return tuple(params_container)
|
|
277
|
+
|
|
278
|
+
def set_shocks(self) -> Shock | tuple[Shock, ...] | None:
|
|
279
|
+
if self.innovations:
|
|
280
|
+
return tuple(Shock(name=name) for name in self.state_names)
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
def set_coords(self) -> Coord | tuple[Coord, ...] | None:
|
|
284
|
+
k_endog = self.k_endog
|
|
285
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
286
|
+
base_names = tuple(f"{f}_{self.name}" for f in ["Cos", "Sin"])
|
|
287
|
+
observed_state_names = self.observed_state_names
|
|
288
|
+
|
|
289
|
+
state_coords = Coord(
|
|
290
|
+
dimension=f"state_{self.name}",
|
|
291
|
+
labels=base_names
|
|
292
|
+
if k_endog_effective == 1
|
|
293
|
+
else (f"Cos_{self.name}", f"Sin_{self.name}"),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
coord_container = [state_coords]
|
|
297
|
+
|
|
298
|
+
if k_endog_effective != 1:
|
|
299
|
+
endog_coords = Coord(dimension=f"endog_{self.name}", labels=observed_state_names)
|
|
300
|
+
coord_container.append(endog_coords)
|
|
301
|
+
|
|
302
|
+
return tuple(coord_container)
|
|
303
|
+
|
|
203
304
|
def make_symbolic_graph(self) -> None:
|
|
204
305
|
k_endog = self.k_endog
|
|
205
306
|
k_endog_effective = 1 if self.share_states else k_endog
|
|
@@ -250,76 +351,14 @@ class CycleComponent(Component):
|
|
|
250
351
|
# explicitly set state cov to 0 when no innovations
|
|
251
352
|
self.ssm["state_cov", :, :] = pt.zeros((self.k_posdef, self.k_posdef))
|
|
252
353
|
|
|
253
|
-
def populate_component_properties(self):
|
|
254
|
-
k_endog = self.k_endog
|
|
255
|
-
k_endog_effective = 1 if self.share_states else k_endog
|
|
256
|
-
|
|
257
|
-
base_names = [f"{f}_{self.name}" for f in ["Cos", "Sin"]]
|
|
258
354
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if k_endog_effective == 1:
|
|
271
|
-
self.param_dims = {f"params_{self.name}": (f"state_{self.name}",)}
|
|
272
|
-
self.coords = {f"state_{self.name}": base_names}
|
|
273
|
-
self.param_info = {
|
|
274
|
-
f"params_{self.name}": {
|
|
275
|
-
"shape": (2,),
|
|
276
|
-
"constraints": None,
|
|
277
|
-
"dims": (f"state_{self.name}",),
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else:
|
|
281
|
-
self.param_dims = {f"params_{self.name}": (f"endog_{self.name}", f"state_{self.name}")}
|
|
282
|
-
self.coords = {
|
|
283
|
-
f"state_{self.name}": [f"Cos_{self.name}", f"Sin_{self.name}"],
|
|
284
|
-
f"endog_{self.name}": self.observed_state_names,
|
|
285
|
-
}
|
|
286
|
-
self.param_info = {
|
|
287
|
-
f"params_{self.name}": {
|
|
288
|
-
"shape": (k_endog_effective, 2),
|
|
289
|
-
"constraints": None,
|
|
290
|
-
"dims": (f"endog_{self.name}", f"state_{self.name}"),
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if self.estimate_cycle_length:
|
|
295
|
-
self.param_names += [f"length_{self.name}"]
|
|
296
|
-
self.param_info[f"length_{self.name}"] = {
|
|
297
|
-
"shape": () if k_endog_effective == 1 else (k_endog_effective,),
|
|
298
|
-
"constraints": "Positive, non-zero",
|
|
299
|
-
"dims": None if k_endog_effective == 1 else (f"endog_{self.name}",),
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if self.dampen:
|
|
303
|
-
self.param_names += [f"dampening_factor_{self.name}"]
|
|
304
|
-
self.param_info[f"dampening_factor_{self.name}"] = {
|
|
305
|
-
"shape": () if k_endog_effective == 1 else (k_endog_effective,),
|
|
306
|
-
"constraints": "0 < x ≤ 1",
|
|
307
|
-
"dims": None if k_endog_effective == 1 else (f"endog_{self.name}",),
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if self.innovations:
|
|
311
|
-
self.param_names += [f"sigma_{self.name}"]
|
|
312
|
-
if k_endog_effective == 1:
|
|
313
|
-
self.param_info[f"sigma_{self.name}"] = {
|
|
314
|
-
"shape": (),
|
|
315
|
-
"constraints": "Positive",
|
|
316
|
-
"dims": None,
|
|
317
|
-
}
|
|
318
|
-
else:
|
|
319
|
-
self.param_dims[f"sigma_{self.name}"] = (f"endog_{self.name}",)
|
|
320
|
-
self.param_info[f"sigma_{self.name}"] = {
|
|
321
|
-
"shape": (k_endog_effective,),
|
|
322
|
-
"constraints": "Positive",
|
|
323
|
-
"dims": (f"endog_{self.name}",),
|
|
324
|
-
}
|
|
325
|
-
self.shock_names = self.state_names.copy()
|
|
355
|
+
def __getattr__(name: str):
|
|
356
|
+
if name == "CycleComponent":
|
|
357
|
+
warnings.warn(
|
|
358
|
+
"CycleComponent is deprecated and will be removed in a future release. "
|
|
359
|
+
"Use Cycle instead.",
|
|
360
|
+
FutureWarning,
|
|
361
|
+
stacklevel=2,
|
|
362
|
+
)
|
|
363
|
+
return Cycle
|
|
364
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pytensor.tensor as pt
|
|
3
5
|
|
|
6
|
+
from pymc_extras.statespace.core.properties import (
|
|
7
|
+
Coord,
|
|
8
|
+
Parameter,
|
|
9
|
+
Shock,
|
|
10
|
+
State,
|
|
11
|
+
)
|
|
4
12
|
from pymc_extras.statespace.models.structural.core import Component
|
|
5
13
|
from pymc_extras.statespace.models.structural.utils import order_to_mask
|
|
6
14
|
from pymc_extras.statespace.utils.constants import POSITION_DERIVATIVE_NAMES
|
|
7
15
|
|
|
8
16
|
|
|
9
|
-
class
|
|
17
|
+
class LevelTrend(Component):
|
|
10
18
|
r"""
|
|
11
19
|
Level and trend component of a structural time series model
|
|
12
20
|
|
|
@@ -145,6 +153,7 @@ class LevelTrendComponent(Component):
|
|
|
145
153
|
f"lower order model, explicitly omit the zeros."
|
|
146
154
|
)
|
|
147
155
|
k_states = max_state
|
|
156
|
+
state_names = POSITION_DERIVATIVE_NAMES[:max_state]
|
|
148
157
|
|
|
149
158
|
if isinstance(innovations_order, int):
|
|
150
159
|
n = innovations_order
|
|
@@ -164,7 +173,8 @@ class LevelTrendComponent(Component):
|
|
|
164
173
|
k_endog=k_endog,
|
|
165
174
|
k_states=k_states * k_endog if not share_states else k_states,
|
|
166
175
|
k_posdef=k_posdef * k_endog if not share_states else k_posdef,
|
|
167
|
-
|
|
176
|
+
base_state_names=state_names,
|
|
177
|
+
base_observed_state_names=observed_state_names,
|
|
168
178
|
measurement_error=False,
|
|
169
179
|
combine_hidden_states=False,
|
|
170
180
|
obs_state_idxs=np.tile(
|
|
@@ -173,71 +183,102 @@ class LevelTrendComponent(Component):
|
|
|
173
183
|
share_states=share_states,
|
|
174
184
|
)
|
|
175
185
|
|
|
176
|
-
def
|
|
177
|
-
|
|
178
|
-
k_endog_effective = 1 if self.share_states else k_endog
|
|
179
|
-
|
|
180
|
-
k_states = self.k_states // k_endog_effective
|
|
181
|
-
k_posdef = self.k_posdef // k_endog_effective
|
|
182
|
-
|
|
183
|
-
name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
|
|
184
|
-
self.param_names = [f"initial_{self.name}"]
|
|
185
|
-
base_names = [name for name, mask in zip(name_slice, self._order_mask) if mask]
|
|
186
|
+
def set_states(self) -> State | tuple[State, ...] | None:
|
|
187
|
+
observed_state_names = self.base_observed_state_names
|
|
186
188
|
|
|
187
189
|
if self.share_states:
|
|
188
|
-
|
|
190
|
+
state_names = [f"{name}[{self.name}_shared]" for name in self.base_state_names]
|
|
189
191
|
else:
|
|
190
|
-
|
|
192
|
+
state_names = [
|
|
191
193
|
f"{name}[{obs_name}]"
|
|
192
|
-
for obs_name in
|
|
193
|
-
for name in
|
|
194
|
+
for obs_name in observed_state_names
|
|
195
|
+
for name in self.base_state_names
|
|
194
196
|
]
|
|
195
197
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
hidden_states = [State(name=name, observed=False, shared=True) for name in state_names]
|
|
199
|
+
observed_states = [
|
|
200
|
+
State(name=name, observed=True, shared=False) for name in observed_state_names
|
|
201
|
+
]
|
|
202
|
+
return *hidden_states, *observed_states
|
|
198
203
|
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
|
|
205
|
+
k_endog = self.k_endog
|
|
206
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
201
207
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
f"endog_{self.name}",
|
|
205
|
-
f"state_{self.name}",
|
|
206
|
-
)
|
|
207
|
-
self.param_dims = {f"initial_{self.name}": (f"endog_{self.name}", f"state_{self.name}")}
|
|
208
|
+
k_states = self.k_states // k_endog_effective
|
|
209
|
+
k_posdef = self.k_posdef // k_endog_effective
|
|
208
210
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
initial_param = Parameter(
|
|
212
|
+
name=f"initial_{self.name}",
|
|
213
|
+
shape=(k_endog_effective, k_states) if k_endog_effective > 1 else (k_states,),
|
|
214
|
+
dims=(f"endog_{self.name}", f"state_{self.name}")
|
|
215
|
+
if k_endog_effective > 1
|
|
216
|
+
else (f"state_{self.name}",),
|
|
217
|
+
constraints=None,
|
|
218
|
+
)
|
|
211
219
|
|
|
212
220
|
if self.k_posdef > 0:
|
|
213
|
-
|
|
221
|
+
sigma_param = Parameter(
|
|
222
|
+
name=f"sigma_{self.name}",
|
|
223
|
+
shape=(k_posdef,) if k_endog_effective == 1 else (k_endog_effective, k_posdef),
|
|
224
|
+
dims=(f"shock_{self.name}",)
|
|
225
|
+
if k_endog_effective == 1
|
|
226
|
+
else (f"endog_{self.name}", f"shock_{self.name}"),
|
|
227
|
+
constraints="Positive",
|
|
228
|
+
)
|
|
229
|
+
return initial_param, sigma_param
|
|
230
|
+
else:
|
|
231
|
+
return (initial_param,)
|
|
214
232
|
|
|
233
|
+
def set_shocks(self) -> Shock | tuple[Shock, ...] | None:
|
|
234
|
+
k_endog = self.k_endog
|
|
235
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
236
|
+
k_states = self.k_states // k_endog_effective
|
|
237
|
+
name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
|
|
238
|
+
|
|
239
|
+
if self.k_posdef > 0:
|
|
215
240
|
base_shock_names = [
|
|
216
241
|
name for name, mask in zip(name_slice, self.innovations_order) if mask
|
|
217
242
|
]
|
|
218
243
|
|
|
219
244
|
if self.share_states:
|
|
220
|
-
|
|
245
|
+
shock_names = [f"{name}[{self.name}_shared]" for name in base_shock_names]
|
|
221
246
|
else:
|
|
222
|
-
|
|
247
|
+
shock_names = [
|
|
223
248
|
f"{name}[{obs_name}]"
|
|
224
249
|
for obs_name in self.observed_state_names
|
|
225
250
|
for name in base_shock_names
|
|
226
251
|
]
|
|
227
252
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
253
|
+
return tuple(Shock(name=name) for name in shock_names)
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def set_coords(self) -> Coord | tuple[Coord, ...] | None:
|
|
257
|
+
k_endog = self.k_endog
|
|
258
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
259
|
+
k_states = self.k_states // k_endog_effective
|
|
260
|
+
name_slice = POSITION_DERIVATIVE_NAMES[:k_states]
|
|
261
|
+
|
|
262
|
+
base_names = [name for name, mask in zip(name_slice, self._order_mask) if mask]
|
|
263
|
+
|
|
264
|
+
base_shock_names = [name for name, mask in zip(name_slice, self.innovations_order) if mask]
|
|
265
|
+
|
|
266
|
+
state_coord = Coord(dimension=f"state_{self.name}", labels=tuple(base_names))
|
|
267
|
+
|
|
268
|
+
coords_container = [state_coord]
|
|
269
|
+
|
|
270
|
+
if k_endog > 1:
|
|
271
|
+
endog_coord = Coord(
|
|
272
|
+
dimension=f"endog_{self.name}",
|
|
273
|
+
labels=self.observed_state_names,
|
|
232
274
|
)
|
|
233
|
-
|
|
234
|
-
self.param_info[f"sigma_{self.name}"] = {
|
|
235
|
-
"shape": (k_posdef,) if k_endog_effective == 1 else (k_endog_effective, k_posdef),
|
|
236
|
-
"constraints": "Positive",
|
|
237
|
-
}
|
|
275
|
+
coords_container.append(endog_coord)
|
|
238
276
|
|
|
239
|
-
|
|
240
|
-
self.
|
|
277
|
+
if self.k_posdef > 0:
|
|
278
|
+
shock_coord = Coord(dimension=f"shock_{self.name}", labels=tuple(base_shock_names))
|
|
279
|
+
coords_container.append(shock_coord)
|
|
280
|
+
|
|
281
|
+
return tuple(coords_container)
|
|
241
282
|
|
|
242
283
|
def make_symbolic_graph(self) -> None:
|
|
243
284
|
k_endog = self.k_endog
|
|
@@ -287,3 +328,15 @@ class LevelTrendComponent(Component):
|
|
|
287
328
|
diag_idx = np.diag_indices(k_posdef * k_endog_effective)
|
|
288
329
|
idx = np.s_["state_cov", diag_idx[0], diag_idx[1]]
|
|
289
330
|
self.ssm[idx] = (sigma_trend**2).ravel()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def __getattr__(name: str):
|
|
334
|
+
if name == "LevelTrendComponent":
|
|
335
|
+
warnings.warn(
|
|
336
|
+
"LevelTrendComponent is deprecated and will be removed in a future release. "
|
|
337
|
+
"Use LevelTrend instead.",
|
|
338
|
+
FutureWarning,
|
|
339
|
+
stacklevel=2,
|
|
340
|
+
)
|
|
341
|
+
return LevelTrend
|
|
342
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
|
+
from pymc_extras.statespace.core.properties import (
|
|
4
|
+
Coord,
|
|
5
|
+
Parameter,
|
|
6
|
+
)
|
|
3
7
|
from pymc_extras.statespace.models.structural.core import Component
|
|
4
8
|
|
|
5
9
|
|
|
@@ -49,7 +53,7 @@ class MeasurementError(Component):
|
|
|
49
53
|
import pymc as pm
|
|
50
54
|
import pytensor.tensor as pt
|
|
51
55
|
|
|
52
|
-
trend = st.
|
|
56
|
+
trend = st.LevelTrend(order=2, innovations_order=1)
|
|
53
57
|
error = st.MeasurementError()
|
|
54
58
|
|
|
55
59
|
ss_mod = (trend + error).build()
|
|
@@ -79,7 +83,7 @@ class MeasurementError(Component):
|
|
|
79
83
|
|
|
80
84
|
.. code:: python
|
|
81
85
|
|
|
82
|
-
trend = st.
|
|
86
|
+
trend = st.LevelTrend(order=2, innovations_order=1)
|
|
83
87
|
seasonal = st.TimeSeasonality(season_length=12, innovations=True)
|
|
84
88
|
error = st.MeasurementError()
|
|
85
89
|
|
|
@@ -118,29 +122,35 @@ class MeasurementError(Component):
|
|
|
118
122
|
k_posdef,
|
|
119
123
|
measurement_error=True,
|
|
120
124
|
combine_hidden_states=False,
|
|
121
|
-
|
|
125
|
+
base_observed_state_names=observed_state_names,
|
|
122
126
|
share_states=share_states,
|
|
123
127
|
)
|
|
124
128
|
|
|
125
|
-
def
|
|
129
|
+
def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
|
|
126
130
|
k_endog = self.k_endog
|
|
127
131
|
k_endog_effective = 1 if self.share_states else k_endog
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
sigma_param = Parameter(
|
|
134
|
+
name=f"sigma_{self.name}",
|
|
135
|
+
shape=(k_endog_effective,) if k_endog_effective > 1 else (),
|
|
136
|
+
dims=(f"endog_{self.name}",) if k_endog_effective > 1 else None,
|
|
137
|
+
constraints="Positive",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return (sigma_param,)
|
|
141
|
+
|
|
142
|
+
def set_coords(self) -> Coord | tuple[Coord, ...] | None:
|
|
143
|
+
k_endog = self.k_endog
|
|
144
|
+
k_endog_effective = 1 if self.share_states else k_endog
|
|
145
|
+
observed_state_names = self.base_observed_state_names
|
|
146
|
+
|
|
147
|
+
coords_container = []
|
|
132
148
|
|
|
133
149
|
if k_endog_effective > 1:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
f"sigma_{self.name}": {
|
|
139
|
-
"shape": (k_endog_effective,) if k_endog_effective > 1 else (),
|
|
140
|
-
"constraints": "Positive",
|
|
141
|
-
"dims": (f"endog_{self.name}",) if k_endog_effective > 1 else None,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
150
|
+
endog_coord = Coord(dimension=f"endog_{self.name}", labels=observed_state_names)
|
|
151
|
+
coords_container.append(endog_coord)
|
|
152
|
+
|
|
153
|
+
return tuple(coords_container) if coords_container else None
|
|
144
154
|
|
|
145
155
|
def make_symbolic_graph(self) -> None:
|
|
146
156
|
k_endog = self.k_endog
|