dfm-python 0.4.3__py3-none-any.whl → 0.4.5__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.
- dfm_python/__init__.py +1 -1
- dfm_python/lightning/__init__.py +0 -7
- dfm_python/models/base.py +0 -37
- dfm_python/nowcast/helpers.py +0 -2
- dfm_python/nowcast/splitters.py +4 -4
- dfm_python/nowcast/utils.py +0 -4
- {dfm_python-0.4.3.dist-info → dfm_python-0.4.5.dist-info}/METADATA +1 -1
- {dfm_python-0.4.3.dist-info → dfm_python-0.4.5.dist-info}/RECORD +10 -13
- dfm_python/lightning/callbacks.py +0 -0
- dfm_python/lightning/utils.py +0 -0
- dfm_python/models/estimation.py +0 -371
- {dfm_python-0.4.3.dist-info → dfm_python-0.4.5.dist-info}/WHEEL +0 -0
- {dfm_python-0.4.3.dist-info → dfm_python-0.4.5.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py
CHANGED
|
@@ -67,7 +67,7 @@ Note: DFMConfig and SeriesConfig are internal implementation details.
|
|
|
67
67
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
-
__version__ = "0.4.
|
|
70
|
+
__version__ = "0.4.5"
|
|
71
71
|
|
|
72
72
|
# ============================================================================
|
|
73
73
|
# PUBLIC API DEFINITION
|
dfm_python/lightning/__init__.py
CHANGED
|
@@ -31,13 +31,6 @@ from .scaling import (
|
|
|
31
31
|
# Note: dfm_module.py has been removed - DFM class implements EM directly
|
|
32
32
|
from ..models.dfm import DFMTrainingState
|
|
33
33
|
|
|
34
|
-
# DDFMLightningModule is deprecated and unused - DDFM class uses DDFMModel directly.
|
|
35
|
-
# Keeping ddfm_module.py file for reference only, but not exporting it.
|
|
36
|
-
# from .ddfm_module import (
|
|
37
|
-
# DDFMLightningModule, # Deprecated: not used (DDFM uses DDFMModel)
|
|
38
|
-
# DDFMTrainingState, # Duplicate: also defined in models.ddfm
|
|
39
|
-
# )
|
|
40
|
-
|
|
41
34
|
__all__ = [
|
|
42
35
|
# Kalman filter
|
|
43
36
|
'KalmanFilter', # New module class
|
dfm_python/models/base.py
CHANGED
|
@@ -499,43 +499,6 @@ class BaseFactorModel(pl.LightningModule):
|
|
|
499
499
|
self.training_state = None
|
|
500
500
|
return self
|
|
501
501
|
|
|
502
|
-
def plot(self, **kwargs) -> 'BaseFactorModel':
|
|
503
|
-
"""Plot common visualizations (not yet implemented).
|
|
504
|
-
|
|
505
|
-
.. note::
|
|
506
|
-
This method is a placeholder for future visualization features.
|
|
507
|
-
For now, use external plotting libraries (matplotlib, plotly) with
|
|
508
|
-
model results: ``result = model.get_result()`` then plot ``result.Z``
|
|
509
|
-
(factors) or ``result.X_sm`` (smoothed data).
|
|
510
|
-
|
|
511
|
-
Parameters
|
|
512
|
-
----------
|
|
513
|
-
**kwargs
|
|
514
|
-
Additional keyword arguments (reserved for future use)
|
|
515
|
-
|
|
516
|
-
Returns
|
|
517
|
-
-------
|
|
518
|
-
BaseFactorModel
|
|
519
|
-
Self for method chaining
|
|
520
|
-
|
|
521
|
-
Raises
|
|
522
|
-
------
|
|
523
|
-
ValueError
|
|
524
|
-
If model has not been trained yet
|
|
525
|
-
"""
|
|
526
|
-
if self.training_state is None:
|
|
527
|
-
error_msg = self._format_error_message(
|
|
528
|
-
operation="plotting",
|
|
529
|
-
reason="model has not been trained yet",
|
|
530
|
-
guidance="Please call trainer.fit(model, data_module) first"
|
|
531
|
-
)
|
|
532
|
-
raise ValueError(error_msg)
|
|
533
|
-
_logger.warning(
|
|
534
|
-
"Plot functionality not yet implemented. Use external plotting libraries "
|
|
535
|
-
"with model results: result = model.get_result(); plot(result.Z)"
|
|
536
|
-
)
|
|
537
|
-
return self
|
|
538
|
-
|
|
539
502
|
def load_pickle(self, path: Union[str, Path], **kwargs) -> 'BaseFactorModel':
|
|
540
503
|
"""Load a saved model from pickle file.
|
|
541
504
|
|
dfm_python/nowcast/helpers.py
CHANGED
|
@@ -236,8 +236,6 @@ from .utils import (
|
|
|
236
236
|
extract_news,
|
|
237
237
|
)
|
|
238
238
|
|
|
239
|
-
# Backward compatibility aliases removed - use direct imports from nowcast.utils
|
|
240
|
-
|
|
241
239
|
# ============================================================================
|
|
242
240
|
# Backtest result classes (merged from backtest.py)
|
|
243
241
|
# ============================================================================
|
dfm_python/nowcast/splitters.py
CHANGED
|
@@ -4,19 +4,19 @@ This module provides custom splitter implementations for nowcasting evaluation
|
|
|
4
4
|
that respect publication lags and backward-looking nature of nowcasting.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import Iterator, Tuple, List, Optional, Union
|
|
7
|
+
from typing import Iterator, Tuple, List, Optional, Union, Any
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
9
|
import numpy as np
|
|
10
10
|
import pandas as pd
|
|
11
11
|
|
|
12
|
-
from sktime.split import BaseSplitter
|
|
12
|
+
from sktime.split.base import BaseSplitter
|
|
13
13
|
from sktime.forecasting.base import BaseForecaster
|
|
14
14
|
|
|
15
15
|
from ..config import DFMConfig
|
|
16
16
|
from ..utils.time import TimeIndex, parse_timestamp, to_python_datetime
|
|
17
17
|
from ..utils.data import create_data_view, calculate_release_date
|
|
18
|
-
from ..utils.helpers import get_series_ids
|
|
19
|
-
from .utils import calc_backward_date, get_higher_frequency
|
|
18
|
+
from ..utils.helpers import get_series_ids, get_clock_frequency
|
|
19
|
+
from .utils import calc_backward_date, get_higher_frequency
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class NowcastingSplitter(BaseSplitter):
|
dfm_python/nowcast/utils.py
CHANGED
|
@@ -213,10 +213,6 @@ def check_config(saved_config: Any, current_config: DFMConfig) -> None:
|
|
|
213
213
|
# If comparison fails, continue anyway
|
|
214
214
|
|
|
215
215
|
|
|
216
|
-
# transform_data and _transform_series removed - use DataModule with custom transformers instead
|
|
217
|
-
# transform_series alias removed
|
|
218
|
-
|
|
219
|
-
|
|
220
216
|
def extract_news(
|
|
221
217
|
singlenews: np.ndarray,
|
|
222
218
|
weight: np.ndarray,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
4
4
|
Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
dfm_python/__init__.py,sha256=
|
|
1
|
+
dfm_python/__init__.py,sha256=wSJzWhUQkHZKVcDGx1W36QzTj6zZ-OgZ82qiTMWf0qg,6159
|
|
2
2
|
dfm_python/config/__init__.py,sha256=NrTwjNzQRcYlk5Ht0hTF8o-6CCl5e4S_JxQAp1mNyCs,1507
|
|
3
3
|
dfm_python/config/adapter.py,sha256=KS9C35A1FMRjpU-yJ19sYciurIy0KWtrcJxosoIzOCE,27575
|
|
4
4
|
dfm_python/config/results.py,sha256=k_WY8v_rJyA8e5zr6Pq6KwNLcq1CbCpavndgBYyaqLs,13778
|
|
@@ -12,27 +12,24 @@ dfm_python/encoder/__init__.py,sha256=ljPTXq04cpOsGXNnma2EJ447kPFlytT6_mToLyKok7
|
|
|
12
12
|
dfm_python/encoder/base.py,sha256=MOSn_bsQP-GVonGprJymQq-_Q6tmCQex8VOWUJjyxNA,3029
|
|
13
13
|
dfm_python/encoder/pca.py,sha256=FsvCqXD0AuASxymV2-5oAM2QVv_syhSyL3CmxVOXxgM,10584
|
|
14
14
|
dfm_python/encoder/vae.py,sha256=a5LYl9bb2j2kcZL-irPMTlwHTotCAMOCNA6BmG0xqw4,20431
|
|
15
|
-
dfm_python/lightning/__init__.py,sha256=
|
|
16
|
-
dfm_python/lightning/callbacks.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
dfm_python/lightning/__init__.py,sha256=S73pzk72n7Mk8to6urty_PULKyo83Y4FiXNWSScI2-4,1522
|
|
17
16
|
dfm_python/lightning/data_module.py,sha256=GKXNUgTb4Hivs1NFrj05YVH7UEwIt8o4sa_DEOuoYSI,30478
|
|
18
17
|
dfm_python/lightning/scaling.py,sha256=o6DlimsZjf0Z3zosezURin6NBTOE9kAfdcdXKHbZdz4,16359
|
|
19
|
-
dfm_python/lightning/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
18
|
dfm_python/logger/__init__.py,sha256=2GwtfOG2ttbtJ5CrO6338R6GIao3YuY0HLiBeCoC0Ns,1066
|
|
21
19
|
dfm_python/logger/inference_logger.py,sha256=EeFfux4UxXhIosg8dEjbkkdm38_4LKZdGS_6p_xpzWI,10118
|
|
22
20
|
dfm_python/logger/logger.py,sha256=cledaY-VvZx_HzsV4Ukuw5tP5sEvkxiAqOKJlwbDWXA,3768
|
|
23
21
|
dfm_python/logger/train_logger.py,sha256=7ufZqDxexRmTI_kokoOyAGYQBmipRb0dALLXawJqq4E,17759
|
|
24
22
|
dfm_python/models/__init__.py,sha256=mrs2N5OoFeNWCjS6luPjmwzFosNDkpdMiBoA24NMrE4,640
|
|
25
|
-
dfm_python/models/base.py,sha256=
|
|
23
|
+
dfm_python/models/base.py,sha256=QG3Mc2c2ccZx_Zmr1CnNO8B3Ek6yCm86EoKQccycsBA,19718
|
|
26
24
|
dfm_python/models/ddfm.py,sha256=SbVva4gwYM8dN3ibf725Svqv4jyeTZEFOja87ZHeJqk,107381
|
|
27
25
|
dfm_python/models/dfm.py,sha256=pfBMI5-vxLPzYkG7xa6yWY1TkDqpBdtGAdv0rRazKpA,25382
|
|
28
|
-
dfm_python/models/estimation.py,sha256=hc10Dw4E7reBF2dXDjqyY2Z9_RDLNM4QBZIYSFJMdds,14005
|
|
29
26
|
dfm_python/nowcast/__init__.py,sha256=AI51eoxdpKh-7EksDHdGcQple6bN_Gferp8VRCNmpYU,954
|
|
30
27
|
dfm_python/nowcast/dataview.py,sha256=KpLZHVu9QPFDmTWWxZdlTTQ8Z1Q-676NJ47TpWKr2-I,4439
|
|
31
|
-
dfm_python/nowcast/helpers.py,sha256=
|
|
28
|
+
dfm_python/nowcast/helpers.py,sha256=pUWVrMMrc2N8MwD6qQw_a-kXUAw_t8CKe4Mjnf-z96U,13767
|
|
32
29
|
dfm_python/nowcast/nowcast.py,sha256=bXlYUVZ-Lx05u2eqD42OtFhGpYAecx2GFQ2c9xpuueg,50746
|
|
33
|
-
dfm_python/nowcast/splitters.py,sha256=
|
|
30
|
+
dfm_python/nowcast/splitters.py,sha256=3RWhRxInoLVW6Oh34Shu88IT3WBM9TK3Wpig2cpQ_oI,15778
|
|
34
31
|
dfm_python/nowcast/transformers.py,sha256=Kf_Wd3_xeCKqpddsNnoPOhip1sWF7AhuzcsKSzaHMQo,10645
|
|
35
|
-
dfm_python/nowcast/utils.py,sha256=
|
|
32
|
+
dfm_python/nowcast/utils.py,sha256=aFbXTWbBAYebhznfa15X8kL8drAe7i-yECMtnjMDRvc,9230
|
|
36
33
|
dfm_python/ssm/__init__.py,sha256=dFpt8_3jb_foISDNhma_JUAzch0VrKZcaeWP9zwpwoM,1161
|
|
37
34
|
dfm_python/ssm/em.py,sha256=N1cGTpsE1A6phLWDHYB6mYm8RYd09FZSwOweGjoSa8Y,34760
|
|
38
35
|
dfm_python/ssm/kalman.py,sha256=USWXDJ5nj1sk6FXgG2zXe-VYGdKDYj-kGruIvy8cfHU,24501
|
|
@@ -46,7 +43,7 @@ dfm_python/utils/diagnostics.py,sha256=UwrJfnn8VACATfQEkTcWw9eEJlTik-VCgTZ1dJABZ
|
|
|
46
43
|
dfm_python/utils/helpers.py,sha256=t-hJNgmPbk5KV5nGP7z_MF0sVh-VJMdQyoDvRBzwTV8,13020
|
|
47
44
|
dfm_python/utils/statespace.py,sha256=pyKy1D8bLMmd7sDvAVSb2n74U0IfH2YuYreIfyg3MAA,43823
|
|
48
45
|
dfm_python/utils/time.py,sha256=l9xu1nwLquWpRgWayRELPhTymV9P47s3s4Q8b-gl0IM,35745
|
|
49
|
-
dfm_python-0.4.
|
|
50
|
-
dfm_python-0.4.
|
|
51
|
-
dfm_python-0.4.
|
|
52
|
-
dfm_python-0.4.
|
|
46
|
+
dfm_python-0.4.5.dist-info/METADATA,sha256=0Av0uXKYn-qwlaLTxBPo-K5_Evv56v2j1nkzyXxk2pY,48890
|
|
47
|
+
dfm_python-0.4.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
dfm_python-0.4.5.dist-info/licenses/LICENSE,sha256=6e87br6LCDHjy2hdIR1-sdBQy351qdmSEbOg6jQ7ZXY,1063
|
|
49
|
+
dfm_python-0.4.5.dist-info/RECORD,,
|
|
File without changes
|
dfm_python/lightning/utils.py
DELETED
|
File without changes
|
dfm_python/models/estimation.py
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
"""Dynamic Factor Model (DFM) estimation using PyTorch Lightning.
|
|
2
|
-
|
|
3
|
-
This module provides the core DFM estimation function (_dfm_core) which
|
|
4
|
-
uses PyTorch Lightning modules for training.
|
|
5
|
-
|
|
6
|
-
The implementation uses a clock-based approach, where all latent factors
|
|
7
|
-
evolve at a common clock frequency, with lower-frequency observations
|
|
8
|
-
mapped to higher-frequency latent states via deterministic tent kernels.
|
|
9
|
-
|
|
10
|
-
Note: This module now acts as a wrapper around Lightning modules. The actual
|
|
11
|
-
estimation logic is in dfm_python.lightning modules.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import numpy as np
|
|
15
|
-
from typing import Tuple, Optional, Any, Dict, Union
|
|
16
|
-
import warnings
|
|
17
|
-
import logging
|
|
18
|
-
import pandas as pd
|
|
19
|
-
from ..logger import get_logger
|
|
20
|
-
|
|
21
|
-
from ..config import DFMConfig
|
|
22
|
-
from ..utils.time import calculate_rmse
|
|
23
|
-
from ..utils.diagnostics import (
|
|
24
|
-
_display_dfm_tables,
|
|
25
|
-
)
|
|
26
|
-
from ..utils.helpers import (
|
|
27
|
-
safe_get_method, safe_get_attr, resolve_param,
|
|
28
|
-
get_series_names, get_frequencies, ParameterResolver
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
from ..config.utils import (
|
|
32
|
-
get_agg_structure,
|
|
33
|
-
FREQUENCY_HIERARCHY,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
from ..config.results import DFMResult, FitParams
|
|
37
|
-
|
|
38
|
-
_logger = get_logger(__name__)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _prepare_data(
|
|
42
|
-
X: np.ndarray,
|
|
43
|
-
config: DFMConfig,
|
|
44
|
-
params: Optional[FitParams] = None,
|
|
45
|
-
) -> Tuple[np.ndarray, np.ndarray, Dict[str, Any]]:
|
|
46
|
-
"""Prepare data and resolve all parameters from config and overrides.
|
|
47
|
-
|
|
48
|
-
Parameters
|
|
49
|
-
----------
|
|
50
|
-
X : np.ndarray
|
|
51
|
-
Input data matrix (T x N)
|
|
52
|
-
config : DFMConfig
|
|
53
|
-
Configuration object
|
|
54
|
-
params : FitParams, optional
|
|
55
|
-
Parameter overrides. If None, all values from config are used.
|
|
56
|
-
|
|
57
|
-
Returns
|
|
58
|
-
-------
|
|
59
|
-
X_clean : np.ndarray
|
|
60
|
-
Cleaned input data (Inf replaced with NaN)
|
|
61
|
-
blocks : np.ndarray
|
|
62
|
-
Block structure array (N x n_blocks)
|
|
63
|
-
params_dict : dict
|
|
64
|
-
Dictionary of resolved parameters
|
|
65
|
-
"""
|
|
66
|
-
# Clean input data
|
|
67
|
-
inf_mask = np.isinf(X)
|
|
68
|
-
if np.any(inf_mask):
|
|
69
|
-
X = np.where(inf_mask, np.nan, X)
|
|
70
|
-
warnings.warn("Data contains Inf values, replaced with NaN", UserWarning)
|
|
71
|
-
|
|
72
|
-
blocks = config.get_blocks_array()
|
|
73
|
-
T, N = X.shape
|
|
74
|
-
|
|
75
|
-
# Initialize params if not provided
|
|
76
|
-
if params is None:
|
|
77
|
-
params = FitParams()
|
|
78
|
-
|
|
79
|
-
# Use ParameterResolver for consistent parameter resolution
|
|
80
|
-
resolver = ParameterResolver(config, params)
|
|
81
|
-
params_dict = resolver.resolve_estimation_params()
|
|
82
|
-
|
|
83
|
-
# Add model structure parameters
|
|
84
|
-
params_dict['r'] = (np.array(config.factors_per_block)
|
|
85
|
-
if config.factors_per_block is not None
|
|
86
|
-
else np.ones(blocks.shape[1]))
|
|
87
|
-
params_dict['T'] = T
|
|
88
|
-
params_dict['N'] = N
|
|
89
|
-
|
|
90
|
-
# Display blocks structure if debug logging enabled
|
|
91
|
-
if _logger.isEnabledFor(logging.DEBUG):
|
|
92
|
-
try:
|
|
93
|
-
series_names = get_series_names(config)
|
|
94
|
-
block_names = (config.block_names if len(config.block_names) == blocks.shape[1]
|
|
95
|
-
else [f'Block_{i+1}' for i in range(blocks.shape[1])])
|
|
96
|
-
# Create pandas DataFrame
|
|
97
|
-
df_dict = {block_names[i]: blocks[:, i].tolist() for i in range(blocks.shape[1])}
|
|
98
|
-
df_dict['series'] = [name.replace(' ', '_') for name in series_names]
|
|
99
|
-
df = pd.DataFrame(df_dict)
|
|
100
|
-
_logger.debug('Block Loading Structure')
|
|
101
|
-
_logger.debug(f'\n{df}')
|
|
102
|
-
_logger.debug(f'Blocks shape: {blocks.shape}')
|
|
103
|
-
except Exception as e:
|
|
104
|
-
_logger.debug(f'Error displaying block structure: {e}')
|
|
105
|
-
|
|
106
|
-
return X, blocks, params_dict
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _prepare_agg_structure(
|
|
110
|
-
config: DFMConfig,
|
|
111
|
-
clock: str
|
|
112
|
-
) -> Tuple[Dict[str, np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], np.ndarray, int, np.ndarray]:
|
|
113
|
-
"""Prepare aggregation structure for mixed-frequency handling.
|
|
114
|
-
|
|
115
|
-
Returns
|
|
116
|
-
-------
|
|
117
|
-
tent_weights_dict : dict
|
|
118
|
-
Dictionary mapping frequency pairs to tent weights
|
|
119
|
-
R_mat : np.ndarray or None
|
|
120
|
-
Constraint matrix for tent kernel aggregation
|
|
121
|
-
q : np.ndarray or None
|
|
122
|
-
Constraint vector for tent kernel aggregation
|
|
123
|
-
frequencies : np.ndarray or None
|
|
124
|
-
Array of frequencies for each series
|
|
125
|
-
i_idio : np.ndarray
|
|
126
|
-
Indicator array (1 for clock frequency, 0 for slower frequencies)
|
|
127
|
-
nQ : int
|
|
128
|
-
Number of slower-frequency series
|
|
129
|
-
idio_chain_lengths : np.ndarray
|
|
130
|
-
Array of idiosyncratic chain lengths per series (0, 1, or tent length)
|
|
131
|
-
"""
|
|
132
|
-
from ..config.utils import compute_idio_lengths
|
|
133
|
-
|
|
134
|
-
agg_info = get_agg_structure(config, clock=clock)
|
|
135
|
-
tent_weights_dict = agg_info.get('tent_weights', {})
|
|
136
|
-
frequencies = np.array(get_frequencies(config)) if config.series else None
|
|
137
|
-
|
|
138
|
-
# Find R_mat and q for tent kernel constraints
|
|
139
|
-
R_mat = None
|
|
140
|
-
q = None
|
|
141
|
-
if agg_info['structures']:
|
|
142
|
-
max_periods = 0
|
|
143
|
-
for (slower_freq, clock_freq), (R, q_vec) in agg_info['structures'].items():
|
|
144
|
-
if R is not None:
|
|
145
|
-
n_periods = R.shape[1]
|
|
146
|
-
if n_periods > max_periods:
|
|
147
|
-
max_periods = n_periods
|
|
148
|
-
R_mat = R
|
|
149
|
-
q = q_vec
|
|
150
|
-
|
|
151
|
-
# Compute i_idio and nQ
|
|
152
|
-
if frequencies is not None:
|
|
153
|
-
clock_hierarchy = FREQUENCY_HIERARCHY.get(clock, 3)
|
|
154
|
-
N = len(frequencies)
|
|
155
|
-
i_idio = np.array([
|
|
156
|
-
1 if j >= len(frequencies) or FREQUENCY_HIERARCHY.get(frequencies[j], 3) <= clock_hierarchy
|
|
157
|
-
else 0
|
|
158
|
-
for j in range(N)
|
|
159
|
-
])
|
|
160
|
-
nQ = N - np.sum(i_idio)
|
|
161
|
-
else:
|
|
162
|
-
i_idio = np.ones(config.get_blocks_array().shape[0])
|
|
163
|
-
nQ = 0
|
|
164
|
-
|
|
165
|
-
# Compute idio chain lengths
|
|
166
|
-
idio_chain_lengths = compute_idio_lengths(config, clock, tent_weights_dict)
|
|
167
|
-
|
|
168
|
-
return tent_weights_dict, R_mat, q, frequencies, i_idio, nQ, idio_chain_lengths
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _dfm_core(
|
|
174
|
-
X: np.ndarray,
|
|
175
|
-
config: DFMConfig,
|
|
176
|
-
params: Optional[FitParams] = None,
|
|
177
|
-
Mx: Optional[np.ndarray] = None,
|
|
178
|
-
Wx: Optional[np.ndarray] = None,
|
|
179
|
-
**kwargs
|
|
180
|
-
) -> DFMResult:
|
|
181
|
-
"""Estimate dynamic factor model using EM algorithm.
|
|
182
|
-
|
|
183
|
-
This is the main function for estimating a Dynamic Factor Model (DFM). It implements
|
|
184
|
-
the complete EM algorithm workflow:
|
|
185
|
-
|
|
186
|
-
1. **Initialization**: Compute initial parameter estimates via PCA and OLS
|
|
187
|
-
2. **EM Iterations**: Iteratively update parameters until convergence
|
|
188
|
-
3. **Final Smoothing**: Run Kalman smoother with final parameters to obtain
|
|
189
|
-
smoothed factors and data
|
|
190
|
-
|
|
191
|
-
The DFM models observed time series as:
|
|
192
|
-
|
|
193
|
-
.. math::
|
|
194
|
-
y_t = C Z_t + e_t, e_t \\sim N(0, R)
|
|
195
|
-
Z_t = A Z_{t-1} + v_t, v_t \\sim N(0, Q)
|
|
196
|
-
|
|
197
|
-
where:
|
|
198
|
-
- y_t is the n x 1 vector of observed series at time t
|
|
199
|
-
- Z_t is the m x 1 vector of unobserved factors
|
|
200
|
-
- C is the n x m loading matrix
|
|
201
|
-
- A is the m x m transition matrix
|
|
202
|
-
- R and Q are covariance matrices
|
|
203
|
-
|
|
204
|
-
Parameters
|
|
205
|
-
----------
|
|
206
|
-
X : np.ndarray
|
|
207
|
-
Data matrix (T x N), where T is time periods and N is number of series.
|
|
208
|
-
Data should already be transformed and standardized (use DFMDataModule with custom transformer).
|
|
209
|
-
Data can contain missing values (NaN), which are handled via spline interpolation.
|
|
210
|
-
Missing values are allowed but excessive missing data (>50%) will trigger warnings.
|
|
211
|
-
config : DFMConfig
|
|
212
|
-
Unified DFM configuration object containing:
|
|
213
|
-
- Model structure: Blocks (N x n_blocks), Frequency (per series),
|
|
214
|
-
Transformation (per series), factors_per_block
|
|
215
|
-
- Estimation parameters: ar_lag, threshold, max_iter, nan_method, nan_k
|
|
216
|
-
Typically obtained from `load_config()`.
|
|
217
|
-
params : FitParams, optional
|
|
218
|
-
Parameter overrides. If None, all values from config are used.
|
|
219
|
-
All parameters in FitParams are optional and override corresponding config values.
|
|
220
|
-
Mx : np.ndarray, optional
|
|
221
|
-
Mean values used for standardization (N,). Required if data is already standardized.
|
|
222
|
-
Wx : np.ndarray, optional
|
|
223
|
-
Standard deviation values used for standardization (N,). Required if data is already standardized.
|
|
224
|
-
**kwargs
|
|
225
|
-
Additional parameter overrides (merged into params if provided).
|
|
226
|
-
Valid parameter names: threshold, max_iter, ar_lag, nan_method, nan_k,
|
|
227
|
-
clock, clip_ar_coefficients, ar_clip_min, ar_clip_max, clip_data_values,
|
|
228
|
-
data_clip_threshold, use_regularization, regularization_scale,
|
|
229
|
-
min_eigenvalue, max_eigenvalue, use_damped_updates, damping_factor.
|
|
230
|
-
|
|
231
|
-
Returns
|
|
232
|
-
-------
|
|
233
|
-
DFMResult
|
|
234
|
-
Dataclass containing all estimation results:
|
|
235
|
-
- x_sm, X_sm: Smoothed data (standardized and unstandardized)
|
|
236
|
-
- Z: Smoothed factor estimates
|
|
237
|
-
- C, A, Q, R: Estimated parameters
|
|
238
|
-
- Mx, Wx: Standardization parameters
|
|
239
|
-
- Z_0, V_0: Initial state and covariance
|
|
240
|
-
- r, p: Model structure parameters
|
|
241
|
-
|
|
242
|
-
Raises
|
|
243
|
-
------
|
|
244
|
-
ValueError
|
|
245
|
-
If inputs are invalid (dimensions, data quality, parameters).
|
|
246
|
-
Also raised during EM iterations if numerical issues occur (e.g., NaN/Inf).
|
|
247
|
-
TypeError
|
|
248
|
-
If input types are incorrect (e.g., X is not numpy array).
|
|
249
|
-
|
|
250
|
-
Notes
|
|
251
|
-
-----
|
|
252
|
-
- The function expects data to already be transformed and standardized.
|
|
253
|
-
Use DFMDataModule with custom sktime transformer to apply transformations and standardization.
|
|
254
|
-
- Initial conditions are computed via `init_conditions()`
|
|
255
|
-
- EM iterations continue until convergence or max_iter=5000
|
|
256
|
-
- Missing data is handled by the Kalman filter during estimation
|
|
257
|
-
- Convergence messages and progress are printed during execution
|
|
258
|
-
|
|
259
|
-
Examples
|
|
260
|
-
--------
|
|
261
|
-
>>> from dfm_python import DFM
|
|
262
|
-
>>> from dfm_python.config.adapter import YamlSource
|
|
263
|
-
>>> from dfm_python import DFMDataModule
|
|
264
|
-
>>> from dfm_python.data.utils import load_data
|
|
265
|
-
>>> from datetime import datetime
|
|
266
|
-
>>> # Load configuration from YAML or create DFMConfig directly
|
|
267
|
-
>>> config = YamlSource('config.yaml').load()
|
|
268
|
-
>>> # Load data from file
|
|
269
|
-
>>> X, Time, Z = load_data('data.csv', config, sample_start=datetime(2000, 1, 1))
|
|
270
|
-
>>> # Estimate DFM
|
|
271
|
-
>>> model = DFM()
|
|
272
|
-
>>> Res = model.fit(X, config, threshold=1e-4)
|
|
273
|
-
>>> # Access results
|
|
274
|
-
>>> factors = Res.Z # (T x m) factor estimates
|
|
275
|
-
>>> loadings = Res.C # (N x m) factor loadings
|
|
276
|
-
>>> smoothed_data = Res.X_sm # (T x N) smoothed data
|
|
277
|
-
>>> # Compute common factor (first factor)
|
|
278
|
-
>>> common_factor = Res.Z[:, 0]
|
|
279
|
-
>>> # Project factor onto a series
|
|
280
|
-
>>> series_idx = 0
|
|
281
|
-
>>> series_factor = Res.Z @ Res.C[series_idx, :].T
|
|
282
|
-
"""
|
|
283
|
-
_logger.info('Estimating the dynamic factor model (DFM)')
|
|
284
|
-
|
|
285
|
-
# Merge kwargs into params if provided
|
|
286
|
-
if kwargs:
|
|
287
|
-
if params is None:
|
|
288
|
-
params = FitParams.from_kwargs(**kwargs)
|
|
289
|
-
else:
|
|
290
|
-
# Update params with kwargs (only valid parameter names)
|
|
291
|
-
valid_params = {
|
|
292
|
-
'threshold', 'max_iter', 'ar_lag', 'nan_method', 'nan_k',
|
|
293
|
-
'clock', 'clip_ar_coefficients', 'ar_clip_min', 'ar_clip_max',
|
|
294
|
-
'clip_data_values', 'data_clip_threshold', 'use_regularization',
|
|
295
|
-
'regularization_scale', 'min_eigenvalue', 'max_eigenvalue',
|
|
296
|
-
'use_damped_updates', 'damping_factor'
|
|
297
|
-
}
|
|
298
|
-
for k, v in kwargs.items():
|
|
299
|
-
if k in valid_params and hasattr(params, k):
|
|
300
|
-
setattr(params, k, v)
|
|
301
|
-
|
|
302
|
-
# Step 1: Prepare data and resolve parameters
|
|
303
|
-
X, blocks, params_dict = _prepare_data(X, config, params)
|
|
304
|
-
|
|
305
|
-
# Extract key parameters
|
|
306
|
-
r = params_dict['r']
|
|
307
|
-
nan_method = params_dict['nan_method']
|
|
308
|
-
nan_k = params_dict['nan_k']
|
|
309
|
-
threshold = params_dict['threshold']
|
|
310
|
-
max_iter = params_dict['max_iter']
|
|
311
|
-
clock = params_dict['clock']
|
|
312
|
-
|
|
313
|
-
# Step 2: Prepare aggregation structure (for diagnostics only)
|
|
314
|
-
_, _, _, _, _, nQ, _ = _prepare_agg_structure(config, clock)
|
|
315
|
-
|
|
316
|
-
# Step 3: Validate preprocessing
|
|
317
|
-
if Mx is None or Wx is None:
|
|
318
|
-
raise ValueError(
|
|
319
|
-
"Mx and Wx must be provided. Data should be preprocessed using DFMDataModule with custom transformer. "
|
|
320
|
-
"before calling _dfm_core()."
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
# Step 3.5: Handle missing data implicitly
|
|
324
|
-
# Missing data (NaN) is handled automatically by the Kalman filter during estimation.
|
|
325
|
-
# The Kalman filter's handle_missing_data() method removes NaN observations at each time step,
|
|
326
|
-
# allowing the EM algorithm to work with incomplete data. This is the standard approach
|
|
327
|
-
# in state-space models (see Harvey 1989, Mariano & Murasawa 2003).
|
|
328
|
-
# Preprocessing (rem_nans_spline) handles leading/trailing NaNs, but remaining NaNs
|
|
329
|
-
# are handled implicitly during Kalman filtering.
|
|
330
|
-
if np.any(np.isnan(X)):
|
|
331
|
-
nan_ratio = np.sum(np.isnan(X)) / X.size
|
|
332
|
-
if nan_ratio > 0.5:
|
|
333
|
-
_logger.warning(
|
|
334
|
-
f"High proportion of missing data ({nan_ratio:.1%}). "
|
|
335
|
-
"Results may be unstable. Consider preprocessing data more thoroughly."
|
|
336
|
-
)
|
|
337
|
-
else:
|
|
338
|
-
_logger.debug(
|
|
339
|
-
f"Data contains {nan_ratio:.1%} missing values. "
|
|
340
|
-
"These will be handled implicitly by the Kalman filter during estimation."
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Step 4-8: Use Lightning module for estimation
|
|
344
|
-
import torch
|
|
345
|
-
|
|
346
|
-
# Convert to torch tensor
|
|
347
|
-
X_torch = torch.tensor(X, dtype=torch.float32)
|
|
348
|
-
|
|
349
|
-
# Create DFM model (Lightning module)
|
|
350
|
-
from ..models.dfm import DFM
|
|
351
|
-
model = DFM(
|
|
352
|
-
config=config,
|
|
353
|
-
num_factors=int(np.sum(r)),
|
|
354
|
-
threshold=threshold,
|
|
355
|
-
max_iter=max_iter,
|
|
356
|
-
nan_method=nan_method,
|
|
357
|
-
nan_k=nan_k
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
# Run EM algorithm
|
|
361
|
-
model.fit_em(X_torch, Mx=Mx, Wx=Wx)
|
|
362
|
-
|
|
363
|
-
# Extract results
|
|
364
|
-
Res = model.get_result()
|
|
365
|
-
|
|
366
|
-
# Display diagnostic tables if debug logging is enabled
|
|
367
|
-
if _logger.isEnabledFor(logging.DEBUG):
|
|
368
|
-
_display_dfm_tables(Res, config, nQ)
|
|
369
|
-
|
|
370
|
-
return Res
|
|
371
|
-
|
|
File without changes
|
|
File without changes
|