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,11 +1,17 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
|
-
from typing import Any
|
|
3
2
|
|
|
4
3
|
import pytensor
|
|
5
4
|
import pytensor.tensor as pt
|
|
6
5
|
|
|
6
|
+
from pymc_extras.statespace.core.properties import (
|
|
7
|
+
Coord,
|
|
8
|
+
Data,
|
|
9
|
+
Parameter,
|
|
10
|
+
Shock,
|
|
11
|
+
State,
|
|
12
|
+
)
|
|
7
13
|
from pymc_extras.statespace.core.statespace import PyMCStateSpace
|
|
8
|
-
from pymc_extras.statespace.models.utilities import
|
|
14
|
+
from pymc_extras.statespace.models.utilities import validate_names
|
|
9
15
|
from pymc_extras.statespace.utils.constants import (
|
|
10
16
|
ALL_STATE_AUX_DIM,
|
|
11
17
|
ALL_STATE_DIM,
|
|
@@ -247,10 +253,10 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
247
253
|
and are modeled as a white noise process, i.e., :math:`f_t = \varepsilon_{f,t}`.
|
|
248
254
|
Therefore, the state vector will include one state per factor and "factor_ar" will not exist.
|
|
249
255
|
|
|
250
|
-
endog_names :
|
|
256
|
+
endog_names : Sequence of str, optional
|
|
251
257
|
Names of the observed time series.
|
|
252
258
|
|
|
253
|
-
exog_names : Sequence
|
|
259
|
+
exog_names : Sequence of str, optional
|
|
254
260
|
Names of the exogenous variables.
|
|
255
261
|
|
|
256
262
|
shared_exog_states: bool, optional
|
|
@@ -281,7 +287,7 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
281
287
|
|
|
282
288
|
validate_names(endog_names, var_name="endog_names", optional=False)
|
|
283
289
|
k_endog = len(endog_names)
|
|
284
|
-
self.endog_names = endog_names
|
|
290
|
+
self.endog_names = tuple(endog_names)
|
|
285
291
|
self.k_endog = k_endog
|
|
286
292
|
self.k_factors = k_factors
|
|
287
293
|
self.factor_order = factor_order
|
|
@@ -322,9 +328,7 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
322
328
|
# Typically, the latent factors introduce k_factors shocks.
|
|
323
329
|
# If error_order > 0 and errors are modeled jointly or separately, add appropriate count.
|
|
324
330
|
k_posdef = k_factors + (k_endog if error_order > 0 else 0) + self.k_exog_states
|
|
325
|
-
# k_posdef = (k_factors + (k_endog if error_order > 0 else 0) + self.k_exog_states if self.exog_innovations else 0)
|
|
326
331
|
|
|
327
|
-
# Initialize the PyMCStateSpace base class.
|
|
328
332
|
super().__init__(
|
|
329
333
|
k_endog=k_endog,
|
|
330
334
|
k_states=k_states,
|
|
@@ -333,99 +337,134 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
333
337
|
measurement_error=measurement_error,
|
|
334
338
|
)
|
|
335
339
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
names = [
|
|
339
|
-
"x0",
|
|
340
|
-
"P0",
|
|
341
|
-
"factor_loadings",
|
|
342
|
-
"factor_ar",
|
|
343
|
-
"error_ar",
|
|
344
|
-
"error_sigma",
|
|
345
|
-
"error_cov",
|
|
346
|
-
"sigma_obs",
|
|
347
|
-
"beta",
|
|
348
|
-
"beta_sigma",
|
|
349
|
-
]
|
|
340
|
+
def set_parameters(self) -> Parameter | tuple[Parameter, ...] | None:
|
|
341
|
+
parameters = []
|
|
350
342
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
343
|
+
k_endog = self.k_endog
|
|
344
|
+
k_states = self.k_states
|
|
345
|
+
|
|
346
|
+
# x0 - initial state
|
|
347
|
+
parameters.append(
|
|
348
|
+
Parameter(
|
|
349
|
+
name="x0",
|
|
350
|
+
shape=(k_states,),
|
|
351
|
+
dims=(ALL_STATE_DIM,),
|
|
352
|
+
constraints=None,
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# P0 - initial covariance
|
|
357
|
+
parameters.append(
|
|
358
|
+
Parameter(
|
|
359
|
+
name="P0",
|
|
360
|
+
shape=(k_states, k_states),
|
|
361
|
+
dims=(ALL_STATE_DIM, ALL_STATE_AUX_DIM),
|
|
362
|
+
constraints="Positive Semi-definite",
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# factor_loadings
|
|
367
|
+
parameters.append(
|
|
368
|
+
Parameter(
|
|
369
|
+
name="factor_loadings",
|
|
370
|
+
shape=(k_endog, self.k_factors),
|
|
371
|
+
dims=(OBS_STATE_DIM, FACTOR_DIM),
|
|
372
|
+
constraints=None,
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# factor_ar - only if factor_order > 0
|
|
377
|
+
if self.factor_order > 0:
|
|
378
|
+
parameters.append(
|
|
379
|
+
Parameter(
|
|
380
|
+
name="factor_ar",
|
|
381
|
+
shape=(self.k_factors, self.factor_order * self.k_factors),
|
|
382
|
+
dims=(FACTOR_DIM, AR_PARAM_DIM),
|
|
383
|
+
constraints=None,
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# error_ar - only if error_order > 0
|
|
388
|
+
if self.error_order > 0:
|
|
389
|
+
error_ar_shape = (
|
|
390
|
+
(k_endog, self.error_order * k_endog)
|
|
391
|
+
if self.error_var
|
|
392
|
+
else (k_endog, self.error_order)
|
|
393
|
+
)
|
|
394
|
+
parameters.append(
|
|
395
|
+
Parameter(
|
|
396
|
+
name="error_ar",
|
|
397
|
+
shape=error_ar_shape,
|
|
398
|
+
dims=(OBS_STATE_DIM, ERROR_AR_PARAM_DIM),
|
|
399
|
+
constraints=None,
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# error_sigma or error_cov depending on error_cov_type
|
|
404
|
+
if self.error_cov_type == "scalar":
|
|
405
|
+
parameters.append(
|
|
406
|
+
Parameter(
|
|
407
|
+
name="error_sigma",
|
|
408
|
+
shape=(),
|
|
409
|
+
dims=(),
|
|
410
|
+
constraints="Positive",
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
elif self.error_cov_type == "diagonal":
|
|
414
|
+
parameters.append(
|
|
415
|
+
Parameter(
|
|
416
|
+
name="error_sigma",
|
|
417
|
+
shape=(k_endog,),
|
|
418
|
+
dims=(OBS_STATE_DIM,),
|
|
419
|
+
constraints="Positive",
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
elif self.error_cov_type == "unstructured":
|
|
423
|
+
parameters.append(
|
|
424
|
+
Parameter(
|
|
425
|
+
name="error_cov",
|
|
426
|
+
shape=(k_endog, k_endog),
|
|
427
|
+
dims=(OBS_STATE_DIM, OBS_STATE_AUX_DIM),
|
|
428
|
+
constraints="Positive Semi-definite",
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# sigma_obs - only if measurement_error
|
|
433
|
+
if self.measurement_error:
|
|
434
|
+
parameters.append(
|
|
435
|
+
Parameter(
|
|
436
|
+
name="sigma_obs",
|
|
437
|
+
shape=(k_endog,),
|
|
438
|
+
dims=(OBS_STATE_DIM,),
|
|
439
|
+
constraints="Positive",
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# beta - only if exog_flag
|
|
444
|
+
if self.exog_flag:
|
|
445
|
+
parameters.append(
|
|
446
|
+
Parameter(
|
|
447
|
+
name="beta",
|
|
448
|
+
shape=(self.k_exog_states,),
|
|
449
|
+
dims=(EXOG_STATE_DIM,),
|
|
450
|
+
constraints=None,
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# beta_sigma - only if exog_innovations
|
|
455
|
+
if self.exog_innovations:
|
|
456
|
+
parameters.append(
|
|
457
|
+
Parameter(
|
|
458
|
+
name="beta_sigma",
|
|
459
|
+
shape=(self.k_exog_states,),
|
|
460
|
+
dims=(EXOG_STATE_DIM,),
|
|
461
|
+
constraints="Positive",
|
|
462
|
+
)
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return tuple(parameters)
|
|
466
|
+
|
|
467
|
+
def set_states(self) -> State | tuple[State, ...] | None:
|
|
429
468
|
names = [
|
|
430
469
|
f"L{lag}.factor_{i}"
|
|
431
470
|
for i in range(self.k_factors)
|
|
@@ -446,37 +485,15 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
446
485
|
for exog_name in self.exog_names
|
|
447
486
|
for endog_name in self.endog_names
|
|
448
487
|
)
|
|
449
|
-
return names
|
|
450
488
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
"""
|
|
456
|
-
return self.endog_names
|
|
457
|
-
|
|
458
|
-
@property
|
|
459
|
-
def coords(self) -> dict[str, Sequence]:
|
|
460
|
-
coords = make_default_coords(self)
|
|
461
|
-
|
|
462
|
-
coords[FACTOR_DIM] = [f"factor_{i+1}" for i in range(self.k_factors)]
|
|
463
|
-
|
|
464
|
-
if self.factor_order > 0:
|
|
465
|
-
coords[AR_PARAM_DIM] = list(range(1, (self.factor_order * self.k_factors) + 1))
|
|
466
|
-
|
|
467
|
-
if self.error_order > 0:
|
|
468
|
-
if self.error_var:
|
|
469
|
-
coords[ERROR_AR_PARAM_DIM] = list(range(1, (self.error_order * self.k_endog) + 1))
|
|
470
|
-
else:
|
|
471
|
-
coords[ERROR_AR_PARAM_DIM] = list(range(1, self.error_order + 1))
|
|
472
|
-
|
|
473
|
-
if self.exog_flag:
|
|
474
|
-
coords[EXOG_STATE_DIM] = list(range(1, self.k_exog_states + 1))
|
|
489
|
+
hidden_states = [State(name=name, observed=False, shared=False) for name in names]
|
|
490
|
+
observed_states = [
|
|
491
|
+
State(name=name, observed=True, shared=False) for name in self.endog_names
|
|
492
|
+
]
|
|
475
493
|
|
|
476
|
-
return
|
|
494
|
+
return *hidden_states, *observed_states
|
|
477
495
|
|
|
478
|
-
|
|
479
|
-
def shock_names(self) -> list[str]:
|
|
496
|
+
def set_shocks(self) -> Shock | tuple[Shock, ...] | None:
|
|
480
497
|
shock_names = [f"factor_shock_{i}" for i in range(self.k_factors)]
|
|
481
498
|
|
|
482
499
|
if self.error_order > 0:
|
|
@@ -492,56 +509,50 @@ class BayesianDynamicFactor(PyMCStateSpace):
|
|
|
492
509
|
for j in range(self.k_endog)
|
|
493
510
|
)
|
|
494
511
|
|
|
495
|
-
return shock_names
|
|
512
|
+
return tuple(Shock(name=name) for name in shock_names)
|
|
496
513
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
coord_map = {
|
|
500
|
-
"x0": (ALL_STATE_DIM,),
|
|
501
|
-
"P0": (ALL_STATE_DIM, ALL_STATE_AUX_DIM),
|
|
502
|
-
"factor_loadings": (OBS_STATE_DIM, FACTOR_DIM),
|
|
503
|
-
}
|
|
504
|
-
if self.factor_order > 0:
|
|
505
|
-
coord_map["factor_ar"] = (FACTOR_DIM, AR_PARAM_DIM)
|
|
514
|
+
def set_data_info(self) -> Data | tuple[Data, ...] | None:
|
|
515
|
+
data = []
|
|
506
516
|
|
|
507
|
-
if self.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
517
|
+
if self.exog_flag:
|
|
518
|
+
data.append(
|
|
519
|
+
Data(
|
|
520
|
+
name="exog_data",
|
|
521
|
+
shape=(None, self.k_exog),
|
|
522
|
+
dims=(TIME_DIM, EXOG_STATE_DIM),
|
|
523
|
+
is_exogenous=True,
|
|
524
|
+
)
|
|
525
|
+
)
|
|
512
526
|
|
|
513
|
-
|
|
514
|
-
coord_map["error_sigma"] = (OBS_STATE_DIM,)
|
|
527
|
+
return tuple(data)
|
|
515
528
|
|
|
516
|
-
|
|
517
|
-
|
|
529
|
+
def set_coords(self) -> Coord | tuple[Coord, ...] | None:
|
|
530
|
+
k_endog = self.k_endog
|
|
531
|
+
coords = list(self.default_coords())
|
|
518
532
|
|
|
519
|
-
|
|
520
|
-
|
|
533
|
+
# Factor coords
|
|
534
|
+
factor_labels = tuple(f"factor_{i+1}" for i in range(self.k_factors))
|
|
535
|
+
coords.append(Coord(dimension=FACTOR_DIM, labels=factor_labels))
|
|
521
536
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
537
|
+
# AR param coords for factors
|
|
538
|
+
if self.factor_order > 0:
|
|
539
|
+
ar_labels = tuple(range(1, (self.factor_order * self.k_factors) + 1))
|
|
540
|
+
coords.append(Coord(dimension=AR_PARAM_DIM, labels=ar_labels))
|
|
526
541
|
|
|
527
|
-
|
|
542
|
+
# AR param coords for errors
|
|
543
|
+
if self.error_order > 0:
|
|
544
|
+
if self.error_var:
|
|
545
|
+
error_ar_labels = tuple(range(1, (self.error_order * k_endog) + 1))
|
|
546
|
+
else:
|
|
547
|
+
error_ar_labels = tuple(range(1, self.error_order + 1))
|
|
548
|
+
coords.append(Coord(dimension=ERROR_AR_PARAM_DIM, labels=error_ar_labels))
|
|
528
549
|
|
|
529
|
-
|
|
530
|
-
def data_info(self):
|
|
550
|
+
# Exogenous coords
|
|
531
551
|
if self.exog_flag:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
},
|
|
537
|
-
}
|
|
538
|
-
return {}
|
|
539
|
-
|
|
540
|
-
@property
|
|
541
|
-
def data_names(self):
|
|
542
|
-
if self.exog_flag:
|
|
543
|
-
return ["exog_data"]
|
|
544
|
-
return []
|
|
552
|
+
exog_labels = tuple(range(1, self.k_exog_states + 1))
|
|
553
|
+
coords.append(Coord(dimension=EXOG_STATE_DIM, labels=exog_labels))
|
|
554
|
+
|
|
555
|
+
return tuple(coords)
|
|
545
556
|
|
|
546
557
|
def make_symbolic_graph(self):
|
|
547
558
|
if not self.exog_flag:
|