pymc-extras 0.2.5__py3-none-any.whl → 0.2.7__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.
Files changed (65) hide show
  1. pymc_extras/__init__.py +5 -1
  2. pymc_extras/deserialize.py +224 -0
  3. pymc_extras/distributions/continuous.py +3 -2
  4. pymc_extras/distributions/discrete.py +3 -1
  5. pymc_extras/inference/find_map.py +62 -17
  6. pymc_extras/inference/laplace.py +10 -7
  7. pymc_extras/prior.py +1356 -0
  8. pymc_extras/statespace/core/statespace.py +191 -52
  9. pymc_extras/statespace/filters/distributions.py +15 -16
  10. pymc_extras/statespace/filters/kalman_filter.py +1 -18
  11. pymc_extras/statespace/filters/kalman_smoother.py +2 -6
  12. pymc_extras/statespace/models/ETS.py +10 -0
  13. pymc_extras/statespace/models/SARIMAX.py +26 -5
  14. pymc_extras/statespace/models/VARMAX.py +12 -2
  15. pymc_extras/statespace/models/structural.py +18 -5
  16. pymc_extras-0.2.7.dist-info/METADATA +321 -0
  17. pymc_extras-0.2.7.dist-info/RECORD +66 -0
  18. {pymc_extras-0.2.5.dist-info → pymc_extras-0.2.7.dist-info}/WHEEL +1 -2
  19. pymc_extras/utils/pivoted_cholesky.py +0 -69
  20. pymc_extras/version.py +0 -11
  21. pymc_extras/version.txt +0 -1
  22. pymc_extras-0.2.5.dist-info/METADATA +0 -112
  23. pymc_extras-0.2.5.dist-info/RECORD +0 -108
  24. pymc_extras-0.2.5.dist-info/top_level.txt +0 -2
  25. tests/__init__.py +0 -13
  26. tests/distributions/__init__.py +0 -19
  27. tests/distributions/test_continuous.py +0 -185
  28. tests/distributions/test_discrete.py +0 -210
  29. tests/distributions/test_discrete_markov_chain.py +0 -258
  30. tests/distributions/test_multivariate.py +0 -304
  31. tests/distributions/test_transform.py +0 -77
  32. tests/model/__init__.py +0 -0
  33. tests/model/marginal/__init__.py +0 -0
  34. tests/model/marginal/test_distributions.py +0 -132
  35. tests/model/marginal/test_graph_analysis.py +0 -182
  36. tests/model/marginal/test_marginal_model.py +0 -967
  37. tests/model/test_model_api.py +0 -38
  38. tests/statespace/__init__.py +0 -0
  39. tests/statespace/test_ETS.py +0 -411
  40. tests/statespace/test_SARIMAX.py +0 -405
  41. tests/statespace/test_VARMAX.py +0 -184
  42. tests/statespace/test_coord_assignment.py +0 -181
  43. tests/statespace/test_distributions.py +0 -270
  44. tests/statespace/test_kalman_filter.py +0 -326
  45. tests/statespace/test_representation.py +0 -175
  46. tests/statespace/test_statespace.py +0 -872
  47. tests/statespace/test_statespace_JAX.py +0 -156
  48. tests/statespace/test_structural.py +0 -836
  49. tests/statespace/utilities/__init__.py +0 -0
  50. tests/statespace/utilities/shared_fixtures.py +0 -9
  51. tests/statespace/utilities/statsmodel_local_level.py +0 -42
  52. tests/statespace/utilities/test_helpers.py +0 -310
  53. tests/test_blackjax_smc.py +0 -222
  54. tests/test_find_map.py +0 -103
  55. tests/test_histogram_approximation.py +0 -109
  56. tests/test_laplace.py +0 -281
  57. tests/test_linearmodel.py +0 -208
  58. tests/test_model_builder.py +0 -306
  59. tests/test_pathfinder.py +0 -297
  60. tests/test_pivoted_cholesky.py +0 -24
  61. tests/test_printing.py +0 -98
  62. tests/test_prior_from_trace.py +0 -172
  63. tests/test_splines.py +0 -77
  64. tests/utils.py +0 -0
  65. {pymc_extras-0.2.5.dist-info → pymc_extras-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,181 +0,0 @@
1
- from contextlib import nullcontext as does_not_raise
2
-
3
- import numpy as np
4
- import pandas as pd
5
- import pymc as pm
6
- import pytensor
7
- import pytensor.tensor as pt
8
- import pytest
9
-
10
- from pymc_extras.statespace.models import structural
11
- from pymc_extras.statespace.models.structural import LevelTrendComponent
12
- from pymc_extras.statespace.utils.constants import (
13
- FILTER_OUTPUT_DIMS,
14
- FILTER_OUTPUT_NAMES,
15
- SMOOTHER_OUTPUT_NAMES,
16
- TIME_DIM,
17
- )
18
- from pymc_extras.statespace.utils.data_tools import (
19
- NO_FREQ_INFO_WARNING,
20
- NO_TIME_INDEX_WARNING,
21
- )
22
- from tests.statespace.utilities.test_helpers import load_nile_test_data
23
-
24
- function_names = ["pandas_date_freq", "pandas_date_nofreq", "pandas_nodate", "numpy", "pytensor"]
25
- expected_warning = [
26
- does_not_raise(),
27
- pytest.warns(UserWarning, match=NO_FREQ_INFO_WARNING),
28
- pytest.warns(UserWarning, match=NO_TIME_INDEX_WARNING),
29
- pytest.warns(UserWarning, match=NO_TIME_INDEX_WARNING),
30
- pytest.warns(UserWarning, match=NO_TIME_INDEX_WARNING),
31
- ]
32
- func_inputs = list(zip(function_names, expected_warning))
33
- floatX = pytensor.config.floatX
34
-
35
-
36
- @pytest.fixture
37
- def load_dataset():
38
- data = load_nile_test_data()
39
-
40
- def _load_dataset(f):
41
- if f == "pandas_date_freq":
42
- data.index.freq = data.index.inferred_freq
43
- return data
44
- if f == "pandas_date_nofreq":
45
- data.index.freq = None
46
- return data
47
- elif f == "pandas_nodate":
48
- return data.reset_index(drop=True)
49
- elif f == "numpy":
50
- return data.values
51
- elif f == "pytensor":
52
- return pt.as_tensor_variable(data.values)
53
- else:
54
- raise ValueError
55
-
56
- return _load_dataset
57
-
58
-
59
- @pytest.fixture()
60
- def generate_timeseries():
61
- def _generate_timeseries(freq):
62
- index = pd.date_range(start="2000-01-01", freq=freq, periods=100)
63
- data = np.random.normal(size=100).astype(floatX)
64
- df = pd.DataFrame(data, index=index, columns=["level"])
65
- return df
66
-
67
- return _generate_timeseries
68
-
69
-
70
- @pytest.fixture()
71
- def create_model(load_dataset):
72
- ss_mod = structural.LevelTrendComponent(order=2).build("data", verbose=False)
73
-
74
- def _create_model(f):
75
- data = load_dataset(f)
76
- with pm.Model(coords=ss_mod.coords) as mod:
77
- P0_diag = pm.Exponential(
78
- "P0_diag",
79
- 1,
80
- dims="state",
81
- )
82
- P0 = pm.Deterministic("P0", pt.diag(P0_diag), dims=("state", "state_aux"))
83
- initial_trend = pm.Normal("initial_trend", dims="trend_state")
84
- sigma_trend = pm.Exponential("sigma_trend", 1, dims="trend_shock")
85
- ss_mod.build_statespace_graph(data, save_kalman_filter_outputs_in_idata=True)
86
- return mod
87
-
88
- return _create_model
89
-
90
-
91
- @pytest.mark.parametrize("f, warning", func_inputs, ids=function_names)
92
- def test_filter_output_coord_assignment(f, warning, create_model):
93
- with warning:
94
- pymc_model = create_model(f)
95
-
96
- for output in FILTER_OUTPUT_NAMES + SMOOTHER_OUTPUT_NAMES + ["predicted_observed_state"]:
97
- assert pymc_model.named_vars_to_dims[output] == FILTER_OUTPUT_DIMS[output]
98
-
99
-
100
- def test_model_build_without_coords(load_dataset):
101
- ss_mod = structural.LevelTrendComponent().build(verbose=False)
102
- data = load_dataset("numpy")
103
- with pm.Model() as mod:
104
- P0_diag = pm.Exponential("P0_diag", 1, shape=(2,))
105
- P0 = pm.Deterministic("P0", pt.diag(P0_diag))
106
- initial_trend = pm.Normal("initial_trend", shape=(2,))
107
- sigma_trend = pm.Exponential("sigma_trend", 1, shape=(2,))
108
- ss_mod.build_statespace_graph(data, register_data=False)
109
-
110
- assert mod.coords == {}
111
-
112
-
113
- @pytest.mark.parametrize("f, warning", func_inputs, ids=function_names)
114
- def test_data_index_is_coord(f, warning, create_model):
115
- with warning:
116
- pymc_model = create_model(f)
117
- assert TIME_DIM in pymc_model.coords
118
-
119
-
120
- def make_model(index):
121
- n = len(index)
122
- a = pd.DataFrame(index=index, columns=["A", "B", "C", "D"], data=np.arange(n * 4).reshape(n, 4))
123
-
124
- mod = LevelTrendComponent(order=2, innovations_order=[0, 1])
125
- ss_mod = mod.build(name="a", verbose=False)
126
-
127
- initial_trend_dims, sigma_trend_dims, P0_dims = ss_mod.param_dims.values()
128
- coords = ss_mod.coords
129
-
130
- with pm.Model(coords=coords) as model:
131
- P0_diag = pm.Gamma("P0_diag", alpha=5, beta=5)
132
- P0 = pm.Deterministic("P0", pt.eye(ss_mod.k_states) * P0_diag, dims=P0_dims)
133
-
134
- initial_trend = pm.Normal("initial_trend", dims=initial_trend_dims)
135
- sigma_trend = pm.Gamma("sigma_trend", alpha=2, beta=50, dims=sigma_trend_dims)
136
-
137
- with pytest.warns(UserWarning, match="No time index found on the supplied data"):
138
- ss_mod.build_statespace_graph(
139
- a["A"],
140
- mode="JAX",
141
- )
142
- return model
143
-
144
-
145
- def test_integer_index():
146
- index = np.arange(8).astype(int)
147
- model = make_model(index)
148
- assert TIME_DIM in model.coords
149
- np.testing.assert_allclose(model.coords[TIME_DIM], index)
150
-
151
-
152
- def test_float_index_raises():
153
- index = np.linspace(0, 1, 8)
154
-
155
- with pytest.raises(IndexError, match="Provided index is not an integer index"):
156
- make_model(index)
157
-
158
-
159
- def test_non_strictly_monotone_index_raises():
160
- # Decreases
161
- index = [0, 1, 2, 1, 2, 3]
162
- with pytest.raises(IndexError, match="Provided index is not monotonic increasing"):
163
- make_model(index)
164
-
165
- # Has gaps
166
- index = [0, 1, 2, 3, 5, 6]
167
- with pytest.raises(IndexError, match="Provided index is not monotonic increasing"):
168
- make_model(index)
169
-
170
- # Has duplicates
171
- index = [0, 1, 1, 2, 3, 4]
172
- with pytest.raises(IndexError, match="Provided index is not monotonic increasing"):
173
- make_model(index)
174
-
175
-
176
- def test_multiindex_raises():
177
- index = pd.MultiIndex.from_tuples([(0, 0), (1, 1), (2, 2), (3, 3)])
178
- with pytest.raises(
179
- NotImplementedError, match="MultiIndex panel data is not currently supported"
180
- ):
181
- make_model(index)
@@ -1,270 +0,0 @@
1
- import numpy as np
2
- import pymc as pm
3
- import pytensor
4
- import pytensor.tensor as pt
5
- import pytest
6
-
7
- from numpy.testing import assert_allclose
8
- from scipy.stats import multivariate_normal
9
-
10
- from pymc_extras.statespace import structural
11
- from pymc_extras.statespace.filters.distributions import (
12
- LinearGaussianStateSpace,
13
- SequenceMvNormal,
14
- _LinearGaussianStateSpace,
15
- )
16
- from pymc_extras.statespace.utils.constants import (
17
- ALL_STATE_DIM,
18
- OBS_STATE_DIM,
19
- TIME_DIM,
20
- )
21
- from tests.statespace.utilities.shared_fixtures import ( # pylint: disable=unused-import
22
- rng,
23
- )
24
- from tests.statespace.utilities.test_helpers import (
25
- delete_rvs_from_model,
26
- fast_eval,
27
- load_nile_test_data,
28
- )
29
-
30
- floatX = pytensor.config.floatX
31
-
32
- # TODO: These are pretty loose because of all the stabilizing of covariance matrices that is done inside the kalman
33
- # filters. When that is improved, this should be tightened.
34
- ATOL = 1e-5 if floatX.endswith("64") else 1e-4
35
- RTOL = 1e-5 if floatX.endswith("64") else 1e-4
36
-
37
- filter_names = [
38
- "standard",
39
- "cholesky",
40
- "univariate",
41
- ]
42
-
43
-
44
- @pytest.fixture(scope="session")
45
- def data():
46
- return load_nile_test_data()
47
-
48
-
49
- @pytest.fixture(scope="session")
50
- def pymc_model(data):
51
- with pm.Model() as mod:
52
- data = pm.Data("data", data.values)
53
- P0_diag = pm.Exponential("P0_diag", 1, shape=(2,))
54
- P0 = pm.Deterministic("P0", pt.diag(P0_diag))
55
- initial_trend = pm.Normal("initial_trend", shape=(2,))
56
- sigma_trend = pm.Exponential("sigma_trend", 1, shape=(2,))
57
-
58
- return mod
59
-
60
-
61
- @pytest.fixture(scope="session")
62
- def pymc_model_2(data):
63
- coords = {
64
- ALL_STATE_DIM: ["level", "trend"],
65
- OBS_STATE_DIM: ["level"],
66
- TIME_DIM: np.arange(101, dtype="int"),
67
- }
68
-
69
- with pm.Model(coords=coords) as mod:
70
- P0_diag = pm.Exponential("P0_diag", 1, shape=(2,))
71
- P0 = pm.Deterministic("P0", pt.diag(P0_diag))
72
- initial_trend = pm.Normal("initial_trend", shape=(2,))
73
- sigma_trend = pm.Exponential("sigma_trend", 1, shape=(2,))
74
- sigma_me = pm.Exponential("sigma_error", 1)
75
-
76
- return mod
77
-
78
-
79
- @pytest.fixture(scope="session")
80
- def ss_mod_me():
81
- ss_mod = structural.LevelTrendComponent(order=2)
82
- ss_mod += structural.MeasurementError(name="error")
83
- ss_mod = ss_mod.build("data", verbose=False)
84
-
85
- return ss_mod
86
-
87
-
88
- @pytest.fixture(scope="session")
89
- def ss_mod_no_me():
90
- ss_mod = structural.LevelTrendComponent(order=2)
91
- ss_mod = ss_mod.build("data", verbose=False)
92
-
93
- return ss_mod
94
-
95
-
96
- @pytest.mark.parametrize("kfilter", filter_names, ids=filter_names)
97
- def test_loglike_vectors_agree(kfilter, pymc_model):
98
- # TODO: This test might be flakey, I've gotten random failures
99
- ss_mod = structural.LevelTrendComponent(order=2).build(
100
- "data", verbose=False, filter_type=kfilter
101
- )
102
- with pymc_model:
103
- ss_mod._insert_random_variables()
104
- matrices = ss_mod.unpack_statespace()
105
-
106
- filter_outputs = ss_mod.kalman_filter.build_graph(pymc_model["data"], *matrices)
107
- filter_mus, pred_mus, obs_mu, filter_covs, pred_covs, obs_cov, ll = filter_outputs
108
-
109
- test_ll = fast_eval(ll)
110
-
111
- # TODO: BUG: Why does fast eval end up with a 2d output when filter is "single"?
112
- obs_mu_np = obs_mu.eval()
113
- obs_cov_np = fast_eval(obs_cov)
114
- data_np = fast_eval(pymc_model["data"])
115
-
116
- scipy_lls = []
117
- for y, mu, cov in zip(data_np, obs_mu_np, obs_cov_np):
118
- scipy_lls.append(multivariate_normal.logpdf(y, mean=mu, cov=cov))
119
- assert_allclose(test_ll, np.array(scipy_lls).ravel(), atol=ATOL, rtol=RTOL)
120
-
121
-
122
- def test_sequence_mvn_distribution():
123
- # Base Case
124
- mu_sequence = pt.tensor("mu_sequence", shape=(100, 3))
125
- cov_sequence = pt.tensor("cov_sequence", shape=(100, 3, 3))
126
- logp = pt.tensor("logp", shape=(100,))
127
-
128
- dist = SequenceMvNormal.dist(mu_sequence, cov_sequence, logp)
129
- assert dist.type.shape == (100, 3)
130
-
131
- # With batch dimension
132
- mu_sequence = pt.tensor("mu_sequence", shape=(10, 100, 3))
133
- cov_sequence = pt.tensor("cov_sequence", shape=(10, 100, 3, 3))
134
- logp = pt.tensor(
135
- "logp",
136
- shape=(
137
- 10,
138
- 100,
139
- ),
140
- )
141
-
142
- dist = SequenceMvNormal.dist(mu_sequence, cov_sequence, logp)
143
- assert dist.type.shape == (10, 100, 3)
144
-
145
-
146
- @pytest.mark.parametrize("output_name", ["states_latent", "states_observed"])
147
- def test_lgss_distribution_from_steps(output_name, ss_mod_me, pymc_model_2):
148
- with pymc_model_2:
149
- ss_mod_me._insert_random_variables()
150
- matrices = ss_mod_me.unpack_statespace()
151
-
152
- # pylint: disable=unpacking-non-sequence
153
- latent_states, obs_states = LinearGaussianStateSpace("states", *matrices, steps=100)
154
- # pylint: enable=unpacking-non-sequence
155
-
156
- idata = pm.sample_prior_predictive(draws=10)
157
- delete_rvs_from_model(["states_latent", "states_observed", "states_combined"])
158
-
159
- assert idata.prior.coords["states_latent_dim_0"].shape == (101,)
160
- assert not np.any(np.isnan(idata.prior[output_name].values))
161
-
162
-
163
- @pytest.mark.parametrize("output_name", ["states_latent", "states_observed"])
164
- def test_lgss_distribution_with_dims(output_name, ss_mod_me, pymc_model_2):
165
- with pymc_model_2:
166
- ss_mod_me._insert_random_variables()
167
- matrices = ss_mod_me.unpack_statespace()
168
-
169
- # pylint: disable=unpacking-non-sequence
170
- latent_states, obs_states = LinearGaussianStateSpace(
171
- "states",
172
- *matrices,
173
- steps=100,
174
- dims=[TIME_DIM, ALL_STATE_DIM, OBS_STATE_DIM],
175
- sequence_names=[],
176
- k_endog=ss_mod_me.k_endog,
177
- )
178
- # pylint: enable=unpacking-non-sequence
179
- idata = pm.sample_prior_predictive(draws=10)
180
- delete_rvs_from_model(["states_latent", "states_observed", "states_combined"])
181
-
182
- assert idata.prior.coords["time"].shape == (101,)
183
- assert all(
184
- [dim in idata.prior.states_latent.coords.keys() for dim in [TIME_DIM, ALL_STATE_DIM]]
185
- )
186
- assert all(
187
- [dim in idata.prior.states_observed.coords.keys() for dim in [TIME_DIM, OBS_STATE_DIM]]
188
- )
189
- assert not np.any(np.isnan(idata.prior[output_name].values))
190
-
191
-
192
- @pytest.mark.parametrize("output_name", ["states_latent", "states_observed"])
193
- def test_lgss_with_time_varying_inputs(output_name, rng):
194
- X = rng.random(size=(10, 3), dtype=floatX)
195
- ss_mod = structural.LevelTrendComponent() + structural.RegressionComponent(
196
- name="exog", k_exog=3
197
- )
198
- mod = ss_mod.build("data", verbose=False)
199
-
200
- coords = {
201
- ALL_STATE_DIM: ["level", "trend", "beta_1", "beta_2", "beta_3"],
202
- OBS_STATE_DIM: ["level"],
203
- TIME_DIM: np.arange(10, dtype="int"),
204
- }
205
-
206
- with pm.Model(coords=coords):
207
- exog_data = pm.Data("data_exog", X)
208
- P0_diag = pm.Exponential("P0_diag", 1, shape=(mod.k_states,))
209
- P0 = pm.Deterministic("P0", pt.diag(P0_diag))
210
- initial_trend = pm.Normal("initial_trend", shape=(2,))
211
- sigma_trend = pm.Exponential("sigma_trend", 1, shape=(2,))
212
- beta_exog = pm.Normal("beta_exog", shape=(3,))
213
-
214
- mod._insert_random_variables()
215
- mod._insert_data_variables()
216
- matrices = mod.unpack_statespace()
217
-
218
- # pylint: disable=unpacking-non-sequence
219
- latent_states, obs_states = LinearGaussianStateSpace(
220
- "states",
221
- *matrices,
222
- steps=9,
223
- sequence_names=["d", "Z"],
224
- dims=[TIME_DIM, ALL_STATE_DIM, OBS_STATE_DIM],
225
- )
226
- # pylint: enable=unpacking-non-sequence
227
- idata = pm.sample_prior_predictive(draws=10)
228
-
229
- assert idata.prior.coords["time"].shape == (10,)
230
- assert all(
231
- [dim in idata.prior.states_latent.coords.keys() for dim in [TIME_DIM, ALL_STATE_DIM]]
232
- )
233
- assert all(
234
- [dim in idata.prior.states_observed.coords.keys() for dim in [TIME_DIM, OBS_STATE_DIM]]
235
- )
236
- assert not np.any(np.isnan(idata.prior[output_name].values))
237
-
238
-
239
- def test_lgss_signature():
240
- # Base case
241
- x0 = pt.tensor("x0", shape=(None,))
242
- P0 = pt.tensor("P0", shape=(None, None))
243
- c = pt.tensor("c", shape=(None,))
244
- d = pt.tensor("d", shape=(None,))
245
- T = pt.tensor("T", shape=(None, None))
246
- Z = pt.tensor("Z", shape=(None, None))
247
- R = pt.tensor("R", shape=(None, None))
248
- H = pt.tensor("H", shape=(None, None))
249
- Q = pt.tensor("Q", shape=(None, None))
250
-
251
- lgss = _LinearGaussianStateSpace.dist(x0, P0, c, d, T, Z, R, H, Q, steps=100)
252
- assert (
253
- lgss.owner.op.extended_signature
254
- == "(s),(s,s),(s),(p),(s,s),(p,s),(s,r),(p,p),(r,r),[rng]->[rng],(t,n)"
255
- )
256
- assert lgss.owner.op.ndim_supp == 2
257
- assert lgss.owner.op.ndims_params == [1, 2, 1, 1, 2, 2, 2, 2, 2]
258
-
259
- # Case with time-varying matrices
260
- T = pt.tensor("T", shape=(None, None, None))
261
- lgss = _LinearGaussianStateSpace.dist(
262
- x0, P0, c, d, T, Z, R, H, Q, steps=100, sequence_names=["T"]
263
- )
264
-
265
- assert (
266
- lgss.owner.op.extended_signature
267
- == "(s),(s,s),(s),(p),(t,s,s),(p,s),(s,r),(p,p),(r,r),[rng]->[rng],(t,n)"
268
- )
269
- assert lgss.owner.op.ndim_supp == 2
270
- assert lgss.owner.op.ndims_params == [1, 2, 1, 1, 3, 2, 2, 2, 2]