4
4
5
5
from pymc_extras .statespace .core .statespace import PyMCStateSpace
6
6
from pymc_extras .statespace .utils .constants import (
7
+ ALL_STATE_AUX_DIM ,
7
8
ALL_STATE_DIM ,
8
9
AR_PARAM_DIM ,
9
10
MA_PARAM_DIM ,
11
+ OBS_STATE_DIM ,
10
12
SHOCK_DIM ,
11
13
)
12
14
@@ -28,6 +30,8 @@ class BayesianDynamicFactor(PyMCStateSpace):
28
30
29
31
exog : array_like, optional
30
32
Array of exogenous regressors for the observation equation (nobs x k_exog).
33
+ Default is None, meaning no exogenous regressors.
34
+ Not implemented yet.
31
35
32
36
error_order : int, optional
33
37
Order of the AR process for the observation error component.
@@ -58,6 +62,9 @@ class BayesianDynamicFactor(PyMCStateSpace):
58
62
the observed time series are driven by a set of latent factors that evolve
59
63
according to a VAR process, possibly along with an autoregressive error term.
60
64
65
+ Up to now just a draft implementation to test the working of the class and comparing
66
+ with the Custom model done in the Notebook (notebook/Making a Custom DFM.ipynb).
67
+ The model work just with two observations and one factor (k_endog=2, k_factors=1).
61
68
62
69
63
70
"""
@@ -113,9 +120,15 @@ def __init__(
113
120
114
121
@property
115
122
def param_names (self ):
116
- names = ["factor_loadings" , "factor_ar" , "error_ar" , "error_sigma" ]
117
-
118
- # factor_sigma is fixed and equal to the identity matrix
123
+ names = [
124
+ "x0" ,
125
+ "P0" ,
126
+ "factor_loadings" ,
127
+ "factor_ar" ,
128
+ "factor_sigma" ,
129
+ "error_ar" ,
130
+ "error_sigma" ,
131
+ ]
119
132
120
133
# Handle cases where parameters should be excluded based on model settings
121
134
if self .factor_order == 0 :
@@ -130,6 +143,14 @@ def param_names(self):
130
143
@property
131
144
def param_info (self ) -> dict [str , dict [str , Any ]]:
132
145
info = {
146
+ "x0" : {
147
+ "shape" : (self .k_factors ,),
148
+ "constraints" : None ,
149
+ },
150
+ "P0" : {
151
+ "shape" : (self .k_factors , self .k_factors ),
152
+ "constraints" : "Positive Semi-definite" ,
153
+ },
133
154
"factor_loadings" : {
134
155
"shape" : (self .k_endog , self .k_factors ),
135
156
"constraints" : None ,
@@ -138,6 +159,10 @@ def param_info(self) -> dict[str, dict[str, Any]]:
138
159
"shape" : (self .k_factors , self .factor_order , self .k_factors ),
139
160
"constraints" : None ,
140
161
},
162
+ "factor_sigma" : {
163
+ "shape" : (self .k_factors ,),
164
+ "constraints" : "Positive" ,
165
+ },
141
166
"error_ar" : {
142
167
"shape" : (self .k_endog , self .error_order , self .k_endog )
143
168
if self .error_var
@@ -167,9 +192,7 @@ def param_info(self) -> dict[str, dict[str, Any]]:
167
192
168
193
@property
169
194
def state_names (self ):
170
- # Initialize state names based on the endogenous variables
171
- state_names = self .endog_names .copy ()
172
-
195
+ state_names = []
173
196
# Add names for the factor loadings (one per observation and factor)
174
197
for i in range (self .k_endog ):
175
198
for j in range (self .k_factors ):
@@ -216,10 +239,6 @@ def state_names(self):
216
239
217
240
return state_names
218
241
219
- @property
220
- def observed_states (self ):
221
- return self .endog_names
222
-
223
242
@property
224
243
def shock_names (self ):
225
244
shock_names = []
@@ -235,39 +254,86 @@ def shock_names(self):
235
254
236
255
return shock_names
237
256
257
+ @property
258
+ def param_dims (self ):
259
+ """
260
+ Define parameter dimensions for the Dynamic Factor Model (DFM).
261
+
262
+ Returns
263
+ -------
264
+ dict
265
+ Dictionary mapping parameter names to their respective dimensions.
266
+ """
267
+ coord_map = {
268
+ "x0" : (ALL_STATE_DIM ,), # Initial state dimension
269
+ "P0" : (ALL_STATE_DIM , ALL_STATE_DIM ), # Initial state covariance dimension
270
+ "factor_loadings" : (OBS_STATE_DIM , ALL_STATE_AUX_DIM ), # Factor loadings dimension
271
+ "factor_sigma" : (ALL_STATE_DIM ,), # Factor variances dimension
272
+ }
238
273
239
- @property
240
- def param_dims (self ):
241
- """
242
- Define parameter dimensions for the Dynamic Factor Model (DFM).
274
+ # Factor AR coefficients if applicable
275
+ if self .factor_order > 0 :
276
+ coord_map ["factor_ar" ] = (AR_PARAM_DIM , SHOCK_DIM , SHOCK_DIM )
243
277
244
- Returns
245
- -------
246
- dict
247
- Dictionary mapping parameter names to their respective dimensions.
248
- """
249
- coord_map = {
250
- "factor_loadings" : (ALL_STATE_DIM , SHOCK_DIM ), # Factor loadings dimension
251
- "factor_sigma" : (SHOCK_DIM ,), # Factor shocks (one per factor)
252
- }
253
-
254
- # Factor AR coefficients if applicable
255
- if self .factor_order > 0 :
256
- coord_map ["factor_ar" ] = (AR_PARAM_DIM , SHOCK_DIM , SHOCK_DIM )
257
-
258
- # Error AR coefficients and variances
259
- if self .error_order > 0 :
260
- if self .error_cov_type == "diagonal" :
261
- coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM ) # AR for errors
262
- coord_map ["error_sigma" ] = (SHOCK_DIM ,) # One variance for each observed variable
263
- elif self .error_cov_type == "scalar" :
264
- coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM )
265
- coord_map ["error_sigma" ] = None # Single scalar for error variance
266
- elif self .error_cov_type == "unstructured" :
267
- coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM , SHOCK_DIM ) # AR for errors
268
- coord_map ["error_cov_L" ] = (SHOCK_DIM , SHOCK_DIM ) # Lower triangular Cholesky factor
269
- coord_map ["error_cov_sd" ] = (SHOCK_DIM ,) # Standard deviations for diagonal
270
- else :
271
- raise ValueError ("Invalid error covariance type." )
272
-
273
- return coord_map
278
+ # Error AR coefficients and variances
279
+ if self .error_order > 0 :
280
+ if self .error_cov_type == "diagonal" :
281
+ coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM ) # AR for errors
282
+ coord_map ["error_sigma" ] = (SHOCK_DIM ,) # One variance for each observed variable
283
+ elif self .error_cov_type == "scalar" :
284
+ coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM )
285
+ coord_map ["error_sigma" ] = None # Single scalar for error variance
286
+ elif self .error_cov_type == "unstructured" :
287
+ coord_map ["error_ar" ] = (MA_PARAM_DIM , SHOCK_DIM , SHOCK_DIM ) # AR for errors
288
+ coord_map ["error_cov_L" ] = (
289
+ SHOCK_DIM ,
290
+ SHOCK_DIM ,
291
+ ) # Lower triangular Cholesky factor
292
+ coord_map ["error_cov_sd" ] = (SHOCK_DIM ,) # Standard deviations for diagonal
293
+ else :
294
+ raise ValueError ("Invalid error covariance type." )
295
+
296
+ return coord_map
297
+
298
+ # def make_symbolic_graph(self):
299
+ # We will implement this in a moment. For now, we need to overwrite it with nothing to avoid a NotImplementedError
300
+ # when we initialize a class instance.
301
+ # pass
302
+
303
+ def make_symbolic_graph (self ):
304
+ """
305
+ Create the symbolic graph for the Dynamic Factor Model (DFM).
306
+ This method sets up the state space model, including the design, transition,
307
+ selection, and initial state matrices, as well as the parameters for the model.
308
+
309
+
310
+ Up to know just a draft implementation to test the working of the class and comparing
311
+ with the Custom model done in the Notebook (notebook/Making a Custom DFM.ipynb).
312
+ """
313
+
314
+ # Create symbolic variables for 1D state
315
+ x0 = self .make_and_register_variable ("x0" , shape = (1 ,))
316
+ P0 = self .make_and_register_variable ("P0" , shape = (1 , 1 ))
317
+ factor_loading = self .make_and_register_variable ("factor_loadings" , shape = (2 , 1 ))
318
+
319
+ factor_ar = self .make_and_register_variable ("factor_ar" , shape = ())
320
+ sigma_f = self .make_and_register_variable ("factor_sigma" , shape = ())
321
+
322
+ # Initialize matrices with correct dimensions
323
+ self .ssm ["design" , :, :] = np .array ([[0.0 ], [0.0 ]]) # 2x1 matrix
324
+ self .ssm ["transition" , :, :] = np .array ([[0.0 ]]) # 1x1 matrix
325
+ self .ssm ["selection" , :, :] = np .array ([[1.0 ]]) # 1x1 matrix
326
+
327
+ # Set initial state and covariance
328
+ self .ssm ["initial_state" , :] = x0
329
+ self .ssm ["initial_state_cov" , :, :] = P0
330
+
331
+ # Set design matrix parameters
332
+ self .ssm ["design" , 0 , 0 ] = factor_loading [0 , 0 ] # First observation loading
333
+ self .ssm ["design" , 1 , 0 ] = factor_loading [1 , 0 ] # Second observation loading
334
+
335
+ # Set transition parameter (AR coefficient)
336
+ self .ssm ["transition" , 0 , 0 ] = factor_ar
337
+
338
+ # Set state covariance
339
+ self .ssm ["state_cov" , 0 , 0 ] = sigma_f
0 commit comments