dfm-python 0.3.0__py3-none-any.whl → 0.4.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.
- dfm_python/__init__.py +168 -52
- dfm_python/config/__init__.py +65 -0
- dfm_python/{config_sources.py → config/io.py} +9 -9
- dfm_python/config/params.py +202 -0
- dfm_python/config/results.py +318 -0
- dfm_python/{config.py → config/schema.py} +270 -344
- dfm_python/{core/utils.py → config/structure.py} +7 -15
- dfm_python/config/utils.py +93 -0
- dfm_python/encoder/__init__.py +33 -0
- dfm_python/encoder/base.py +0 -0
- dfm_python/encoder/pca.py +157 -0
- dfm_python/encoder/vae.py +291 -0
- dfm_python/lightning/__init__.py +48 -0
- dfm_python/lightning/callbacks.py +0 -0
- dfm_python/lightning/data_module.py +364 -0
- dfm_python/lightning/ddfm_module.py +553 -0
- dfm_python/lightning/dfm_module.py +466 -0
- dfm_python/lightning/utils.py +0 -0
- dfm_python/logger/__init__.py +51 -0
- dfm_python/logger/inference_logger.py +270 -0
- dfm_python/logger/logger.py +110 -0
- dfm_python/logger/train_logger.py +309 -0
- dfm_python/models/__init__.py +36 -0
- dfm_python/models/base.py +94 -0
- dfm_python/models/ddfm.py +1120 -0
- dfm_python/models/dfm.py +810 -0
- dfm_python/models/estimation.py +352 -0
- dfm_python/nowcast/__init__.py +28 -0
- dfm_python/nowcast/dataview.py +143 -0
- dfm_python/nowcast/helpers.py +554 -0
- dfm_python/{nowcast.py → nowcast/nowcast.py} +287 -788
- dfm_python/nowcast/utils.py +290 -0
- dfm_python/ssm/__init__.py +47 -0
- dfm_python/ssm/em.py +488 -0
- dfm_python/ssm/kalman.py +604 -0
- dfm_python/ssm/utils.py +379 -0
- dfm_python/trainer/__init__.py +20 -0
- dfm_python/trainer/ddfm.py +196 -0
- dfm_python/trainer/dfm.py +153 -0
- dfm_python/transformations/__init__.py +35 -0
- dfm_python/transformations/sktime.py +40 -0
- dfm_python/transformations/transformers.py +320 -0
- dfm_python/transformations/utils.py +339 -0
- dfm_python/utils/__init__.py +201 -0
- dfm_python/utils/data.py +706 -0
- dfm_python/{core → utils}/diagnostics.py +32 -109
- dfm_python/utils/helpers.py +533 -0
- dfm_python/utils/statespace.py +1326 -0
- dfm_python/{core → utils}/time.py +373 -0
- {dfm_python-0.3.0.dist-info → dfm_python-0.4.0.dist-info}/METADATA +198 -33
- dfm_python-0.4.0.dist-info/RECORD +53 -0
- {dfm_python-0.3.0.dist-info → dfm_python-0.4.0.dist-info}/WHEEL +1 -1
- dfm_python/api.py +0 -1071
- dfm_python/core/__init__.py +0 -15
- dfm_python/core/em.py +0 -1384
- dfm_python/core/helpers.py +0 -1337
- dfm_python/core/numeric.py +0 -632
- dfm_python/data.py +0 -763
- dfm_python/dfm.py +0 -963
- dfm_python/kalman.py +0 -467
- dfm_python-0.3.0.dist-info/RECORD +0 -19
- {dfm_python-0.3.0.dist-info → dfm_python-0.4.0.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ This package implements a comprehensive Dynamic Factor Model framework with supp
|
|
|
7
7
|
- Expectation-Maximization (EM) algorithm for parameter estimation
|
|
8
8
|
- Kalman filtering and smoothing for factor extraction
|
|
9
9
|
- News decomposition for nowcasting
|
|
10
|
+
- Deep Dynamic Factor Models (DDFM) with nonlinear encoders (optional, requires PyTorch)
|
|
10
11
|
|
|
11
12
|
The package implements a clock-based approach to mixed-frequency DFMs, where all latent
|
|
12
13
|
factors (global and block-level) are synchronized to a common "clock" frequency, typically
|
|
@@ -25,16 +26,39 @@ Key Features:
|
|
|
25
26
|
|
|
26
27
|
Example (High-level API - Recommended):
|
|
27
28
|
>>> import dfm_python as dfm
|
|
28
|
-
>>>
|
|
29
|
-
>>>
|
|
30
|
-
>>>
|
|
31
|
-
>>>
|
|
32
|
-
>>>
|
|
33
|
-
>>>
|
|
29
|
+
>>> from dfm_python.lightning import DFMDataModule
|
|
30
|
+
>>> from sktime.transformations.compose import ColumnTransformer
|
|
31
|
+
>>>
|
|
32
|
+
>>> # Linear DFM
|
|
33
|
+
>>> model = dfm.DFM()
|
|
34
|
+
>>> model.load_config('config/default.yaml')
|
|
35
|
+
>>>
|
|
36
|
+
>>> # Create transformer (user must provide)
|
|
37
|
+
>>> transformer = ColumnTransformer([...]) # User-defined sktime transformer
|
|
38
|
+
>>>
|
|
39
|
+
>>> # Create DataModule
|
|
40
|
+
>>> data_module = DFMDataModule(config=model.config, transformer=transformer, data_path='data/sample_data.csv')
|
|
41
|
+
>>> data_module.setup()
|
|
42
|
+
>>>
|
|
43
|
+
>>> # Train
|
|
44
|
+
>>> model.train(data_module, max_iter=100)
|
|
45
|
+
>>> Xf, Zf = model.predict(horizon=6)
|
|
46
|
+
>>>
|
|
47
|
+
>>> # Or use DDFM (separate class)
|
|
48
|
+
>>> ddfm_model = dfm.DDFM(encoder_layers=[64, 32], num_factors=2)
|
|
49
|
+
>>> ddfm_model.load_config('config/default.yaml')
|
|
50
|
+
>>>
|
|
51
|
+
>>> # Create DataModule for DDFM
|
|
52
|
+
>>> data_module_ddfm = DFMDataModule(config=ddfm_model.config, transformer=transformer, data_path='data/sample_data.csv')
|
|
53
|
+
>>> data_module_ddfm.setup()
|
|
54
|
+
>>>
|
|
55
|
+
>>> # Train
|
|
56
|
+
>>> ddfm_model.train(data_module_ddfm, epochs=100)
|
|
57
|
+
>>> Xf, Zf = ddfm_model.predict(horizon=6)
|
|
34
58
|
|
|
35
59
|
Example (Low-level API - For advanced usage):
|
|
36
60
|
>>> from dfm_python import DFM, DFMConfig, SeriesConfig
|
|
37
|
-
>>> from dfm_python.
|
|
61
|
+
>>> from dfm_python.transformations.utils import read_data # Preferred import
|
|
38
62
|
>>> # Option 1: Load from YAML
|
|
39
63
|
>>> config = load_config('config.yaml')
|
|
40
64
|
>>> # Option 2: Create directly
|
|
@@ -42,62 +66,122 @@ Example (Low-level API - For advanced usage):
|
|
|
42
66
|
... series=[SeriesConfig(frequency='m', transformation='lin', blocks=[1], series_id='series1')],
|
|
43
67
|
... block_names=['Global']
|
|
44
68
|
... )
|
|
45
|
-
>>>
|
|
69
|
+
>>> from dfm_python.lightning import DFMDataModule
|
|
70
|
+
>>> from sktime.transformations.compose import ColumnTransformer
|
|
71
|
+
>>>
|
|
72
|
+
>>> # Create transformer (user must provide)
|
|
73
|
+
>>> transformer = ColumnTransformer([...]) # User-defined
|
|
74
|
+
>>>
|
|
75
|
+
>>> # Create DataModule
|
|
76
|
+
>>> data_module = DFMDataModule(config=config, transformer=transformer, data_path='data.csv')
|
|
77
|
+
>>> data_module.setup()
|
|
78
|
+
>>>
|
|
79
|
+
>>> # Fit model
|
|
46
80
|
>>> model = DFM()
|
|
47
|
-
>>> result = model.fit(
|
|
81
|
+
>>> result = model.fit(data_module, config)
|
|
48
82
|
>>> factors = result.Z # Extract estimated factors
|
|
49
83
|
|
|
50
84
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
51
85
|
"""
|
|
52
86
|
|
|
53
|
-
__version__ = "0.
|
|
54
|
-
|
|
87
|
+
__version__ = "0.4.0"
|
|
88
|
+
|
|
89
|
+
# ============================================================================
|
|
90
|
+
# PUBLIC API DEFINITION
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# This __init__.py is the single source of truth for the public API.
|
|
93
|
+
# All symbols exported here are considered stable public API.
|
|
94
|
+
# Internal reorganization should not break these imports.
|
|
95
|
+
#
|
|
96
|
+
# Public API categories:
|
|
97
|
+
# 1. Configuration: DFMConfig, SeriesConfig, BlockConfig, Params, config sources
|
|
98
|
+
# 2. High-level API: DFM, DDFM, module-level convenience functions
|
|
99
|
+
# 3. Core utilities: DFMCore, run_kf, TimeIndex, diagnostics
|
|
100
|
+
# 4. Models: BaseFactorModel, DFMLinear, DDFM (low-level)
|
|
101
|
+
# 5. Nowcasting: Nowcast, result classes, para_const
|
|
102
|
+
# 6. Data & Results: DFMResult, transform_data
|
|
103
|
+
# ============================================================================
|
|
104
|
+
|
|
105
|
+
# Configuration (from config/ subpackage)
|
|
55
106
|
from .config import (
|
|
56
107
|
DFMConfig, SeriesConfig, BlockConfig, Params, DEFAULT_GLOBAL_BLOCK_NAME,
|
|
57
108
|
ConfigSource, YamlSource, DictSource, HydraSource,
|
|
58
109
|
MergedConfigSource, make_config_source,
|
|
59
110
|
)
|
|
60
|
-
from .data import transform_data
|
|
61
|
-
from .dfm import DFMResult
|
|
62
|
-
from .core import calculate_rmse, diagnose_series, print_series_diagnosis
|
|
63
|
-
from .dfm import DFMCore # Core DFM class from dfm.py
|
|
64
|
-
from .kalman import run_kf, skf, fis, miss_data
|
|
65
|
-
from .nowcast import Nowcast, para_const, NowcastResult, NewsDecompResult, BacktestResult
|
|
66
|
-
|
|
67
|
-
# Import high-level API (extends core DFM with convenience methods)
|
|
68
|
-
# Import module-level convenience functions directly from api.py to avoid duplication
|
|
69
|
-
from .api import (
|
|
70
|
-
DFM, _dfm_instance, from_yaml, from_spec, from_spec_df, from_dict,
|
|
71
|
-
load_config, load_data, load_pickle, train, predict, plot, reset
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# Expose properties as module-level attributes
|
|
75
|
-
# Use property-like access via functions or direct attribute access
|
|
76
|
-
# Since 'config' conflicts with the config module, we'll use a different approach
|
|
77
|
-
# Users can access: dfm.get_config(), dfm.get_data(), dfm.get_result()
|
|
78
|
-
def get_config():
|
|
79
|
-
"""Get current configuration."""
|
|
80
|
-
return _dfm_instance.config
|
|
81
|
-
|
|
82
|
-
def get_data():
|
|
83
|
-
"""Get current data matrix."""
|
|
84
|
-
return _dfm_instance.data
|
|
85
|
-
|
|
86
|
-
def get_time():
|
|
87
|
-
"""Get current time index."""
|
|
88
|
-
return _dfm_instance.time
|
|
89
111
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
# Data utilities
|
|
113
|
+
# Note: transform_data and DFMScaler have been removed - users must provide their own sktime transformers to DFMDataModule
|
|
114
|
+
|
|
115
|
+
# Results
|
|
116
|
+
from .config.results import DFMResult, DDFMResult, BaseResult
|
|
117
|
+
|
|
118
|
+
# Utilities (from utils/ subpackage)
|
|
119
|
+
from .utils.diagnostics import diagnose_series, print_series_diagnosis
|
|
120
|
+
from .utils.time import calculate_rmse
|
|
121
|
+
# DFMCore is an alias for DFMLinear (backward compatibility)
|
|
122
|
+
from .models.dfm import DFMLinear
|
|
123
|
+
DFMCore = DFMLinear
|
|
124
|
+
|
|
125
|
+
# PyTorch Lightning modules (recommended)
|
|
126
|
+
try:
|
|
127
|
+
from .lightning import (
|
|
128
|
+
DFMLightningModule,
|
|
129
|
+
DDFMLightningModule,
|
|
130
|
+
DFMDataModule,
|
|
131
|
+
KalmanFilter, # New module class
|
|
132
|
+
EMAlgorithm, # New module class
|
|
133
|
+
)
|
|
134
|
+
_has_lightning = True
|
|
135
|
+
except ImportError:
|
|
136
|
+
_has_lightning = False
|
|
137
|
+
DFMLightningModule = None # type: ignore
|
|
138
|
+
DDFMLightningModule = None # type: ignore
|
|
139
|
+
DFMDataModule = None # type: ignore
|
|
140
|
+
KalmanFilter = None # type: ignore
|
|
141
|
+
EMAlgorithm = None # type: ignore
|
|
142
|
+
|
|
143
|
+
# Nowcasting (from nowcast/ subpackage)
|
|
144
|
+
from .nowcast import (
|
|
145
|
+
Nowcast,
|
|
146
|
+
NowcastResult,
|
|
147
|
+
)
|
|
148
|
+
from .nowcast.helpers import (
|
|
149
|
+
para_const,
|
|
150
|
+
NewsDecompResult,
|
|
151
|
+
BacktestResult,
|
|
152
|
+
)
|
|
93
153
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
154
|
+
# Model implementations
|
|
155
|
+
from .models.base import BaseFactorModel
|
|
156
|
+
from .models.dfm import DFMLinear, DFM
|
|
157
|
+
from .models.dfm import (
|
|
158
|
+
from_yaml, from_spec, from_spec_df, from_dict,
|
|
159
|
+
load_config, load_pickle, train, predict, plot, reset, create_model
|
|
160
|
+
)
|
|
161
|
+
# Note: load_data has been removed - use DFMDataModule instead
|
|
162
|
+
|
|
163
|
+
# DDFM high-level API and low-level model (both optional, requires PyTorch)
|
|
164
|
+
try:
|
|
165
|
+
from .models.ddfm import DDFM, DDFMModel
|
|
166
|
+
from .models.ddfm import load_config_ddfm, load_data_ddfm, train_ddfm, predict_ddfm, plot_ddfm, reset_ddfm
|
|
167
|
+
_has_ddfm = True
|
|
168
|
+
except ImportError:
|
|
169
|
+
_has_ddfm = False
|
|
170
|
+
DDFM = None # type: ignore
|
|
171
|
+
DDFMModel = None # type: ignore
|
|
172
|
+
load_config_ddfm = None # type: ignore
|
|
173
|
+
load_data_ddfm = None # type: ignore
|
|
174
|
+
train_ddfm = None # type: ignore
|
|
175
|
+
predict_ddfm = None # type: ignore
|
|
176
|
+
plot_ddfm = None # type: ignore
|
|
177
|
+
reset_ddfm = None # type: ignore
|
|
178
|
+
# DDFM module-level functions removed - create DDFM() instance directly
|
|
97
179
|
|
|
98
180
|
__all__ = [
|
|
99
181
|
# Core classes
|
|
100
182
|
'DFMConfig', 'SeriesConfig', 'BlockConfig', 'Params', 'DFM', 'DFMCore', 'Nowcast',
|
|
183
|
+
# Model base and implementations
|
|
184
|
+
'BaseFactorModel', 'DFMLinear',
|
|
101
185
|
# Nowcast result classes
|
|
102
186
|
'NowcastResult', 'NewsDecompResult', 'BacktestResult',
|
|
103
187
|
# Constants
|
|
@@ -107,14 +191,46 @@ __all__ = [
|
|
|
107
191
|
'MergedConfigSource', 'make_config_source',
|
|
108
192
|
# High-level API (module-level - recommended)
|
|
109
193
|
'load_config',
|
|
110
|
-
'
|
|
111
|
-
'get_config', 'get_data', 'get_time', 'get_result', 'get_original_data',
|
|
194
|
+
'load_pickle', 'train', 'predict', 'plot', 'reset', 'create_model',
|
|
112
195
|
# Convenience constructors (cleaner API)
|
|
113
196
|
'from_yaml', 'from_spec', 'from_spec_df', 'from_dict',
|
|
114
197
|
# Low-level API (functional interface - advanced usage)
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'run_kf', 'skf', 'fis', 'miss_data',
|
|
118
|
-
'para_const', # Internal utility, kept for backward compatibility
|
|
198
|
+
'BaseResult', 'DFMResult', 'DDFMResult', 'calculate_rmse', 'diagnose_series', 'print_series_diagnosis',
|
|
199
|
+
'para_const', # Internal utility for nowcasting
|
|
119
200
|
]
|
|
120
201
|
|
|
202
|
+
# Add DDFM high-level API and convenience functions if available
|
|
203
|
+
if _has_ddfm:
|
|
204
|
+
__all__.extend([
|
|
205
|
+
'DDFM', # High-level API class
|
|
206
|
+
'DDFMModel', # Low-level implementation
|
|
207
|
+
'load_config_ddfm', 'train_ddfm',
|
|
208
|
+
'predict_ddfm', 'plot_ddfm', 'reset_ddfm'
|
|
209
|
+
# Note: load_data_ddfm has been removed - use DFMDataModule instead
|
|
210
|
+
])
|
|
211
|
+
|
|
212
|
+
# Add Lightning modules if available
|
|
213
|
+
if _has_lightning:
|
|
214
|
+
__all__.extend([
|
|
215
|
+
'DFMLightningModule',
|
|
216
|
+
'DDFMLightningModule',
|
|
217
|
+
'DFMDataModule',
|
|
218
|
+
'KalmanFilter', # New module class
|
|
219
|
+
'EMAlgorithm', # New module class
|
|
220
|
+
])
|
|
221
|
+
|
|
222
|
+
# Add Trainer classes if available
|
|
223
|
+
try:
|
|
224
|
+
from .trainer import DFMTrainer, DDFMTrainer
|
|
225
|
+
_has_trainers = True
|
|
226
|
+
except ImportError:
|
|
227
|
+
_has_trainers = False
|
|
228
|
+
DFMTrainer = None # type: ignore
|
|
229
|
+
DDFMTrainer = None # type: ignore
|
|
230
|
+
|
|
231
|
+
if _has_trainers:
|
|
232
|
+
__all__.extend([
|
|
233
|
+
'DFMTrainer',
|
|
234
|
+
'DDFMTrainer',
|
|
235
|
+
])
|
|
236
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Configuration subpackage for DFM.
|
|
2
|
+
|
|
3
|
+
This subpackage provides:
|
|
4
|
+
- Schema (DFMConfig, SeriesConfig, BlockConfig, Params) in schema.py
|
|
5
|
+
- IO (ConfigSource, YamlSource, etc.) in io.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .schema import (
|
|
9
|
+
BaseModelConfig, DFMConfig, DDFMConfig, SeriesConfig, BlockConfig,
|
|
10
|
+
DEFAULT_GLOBAL_BLOCK_NAME,
|
|
11
|
+
)
|
|
12
|
+
from .params import Params, FitParams
|
|
13
|
+
from .results import BaseResult, DFMResult, DDFMResult, DFMParams
|
|
14
|
+
from .utils import validate_frequency, validate_transformation
|
|
15
|
+
from .io import (
|
|
16
|
+
ConfigSource,
|
|
17
|
+
YamlSource,
|
|
18
|
+
DictSource,
|
|
19
|
+
HydraSource,
|
|
20
|
+
MergedConfigSource,
|
|
21
|
+
make_config_source,
|
|
22
|
+
from_spec,
|
|
23
|
+
# Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
|
|
24
|
+
# are available from io module but not exported as public API
|
|
25
|
+
)
|
|
26
|
+
from .structure import (
|
|
27
|
+
FREQUENCY_HIERARCHY,
|
|
28
|
+
PERIODS_PER_YEAR,
|
|
29
|
+
get_periods_per_year,
|
|
30
|
+
compute_idio_chain_lengths,
|
|
31
|
+
get_tent_weights_for_pair,
|
|
32
|
+
generate_tent_weights,
|
|
33
|
+
generate_R_mat,
|
|
34
|
+
get_aggregation_structure,
|
|
35
|
+
group_series_by_frequency,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Schema
|
|
40
|
+
'BaseModelConfig', 'DFMConfig', 'DDFMConfig', 'SeriesConfig', 'BlockConfig',
|
|
41
|
+
'DEFAULT_GLOBAL_BLOCK_NAME',
|
|
42
|
+
# Parameters
|
|
43
|
+
'Params', 'FitParams',
|
|
44
|
+
# Results
|
|
45
|
+
'BaseResult', 'DFMResult', 'DDFMResult', 'DFMParams',
|
|
46
|
+
# Utilities
|
|
47
|
+
'validate_frequency', 'validate_transformation',
|
|
48
|
+
# IO
|
|
49
|
+
'ConfigSource', 'YamlSource', 'DictSource',
|
|
50
|
+
'HydraSource', 'MergedConfigSource', 'make_config_source',
|
|
51
|
+
'from_spec',
|
|
52
|
+
# Structure utilities
|
|
53
|
+
'FREQUENCY_HIERARCHY',
|
|
54
|
+
'PERIODS_PER_YEAR',
|
|
55
|
+
'get_periods_per_year',
|
|
56
|
+
'compute_idio_chain_lengths',
|
|
57
|
+
'get_tent_weights_for_pair',
|
|
58
|
+
'generate_tent_weights',
|
|
59
|
+
'generate_R_mat',
|
|
60
|
+
'get_aggregation_structure',
|
|
61
|
+
'group_series_by_frequency',
|
|
62
|
+
# Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
|
|
63
|
+
# are available from io module but not exported as public API
|
|
64
|
+
]
|
|
65
|
+
|
|
@@ -15,7 +15,7 @@ from typing import Protocol, Optional, Dict, Any, Union, Tuple
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from dataclasses import is_dataclass, asdict
|
|
17
17
|
|
|
18
|
-
from .
|
|
18
|
+
from .schema import DFMConfig, SeriesConfig, BlockConfig, DEFAULT_GLOBAL_BLOCK_NAME
|
|
19
19
|
import polars as pl
|
|
20
20
|
|
|
21
21
|
|
|
@@ -378,7 +378,7 @@ def _write_series_blocks_yaml(
|
|
|
378
378
|
else:
|
|
379
379
|
blocks_names = list(series.blocks)
|
|
380
380
|
else:
|
|
381
|
-
blocks_names = [
|
|
381
|
+
blocks_names = [DEFAULT_GLOBAL_BLOCK_NAME]
|
|
382
382
|
|
|
383
383
|
series_entry = {
|
|
384
384
|
'series_name': series.series_name,
|
|
@@ -482,7 +482,7 @@ def from_spec(
|
|
|
482
482
|
# Blocks are already names
|
|
483
483
|
blocks_names = list(series.blocks)
|
|
484
484
|
else:
|
|
485
|
-
blocks_names = [
|
|
485
|
+
blocks_names = [DEFAULT_GLOBAL_BLOCK_NAME] # Default
|
|
486
486
|
|
|
487
487
|
series_entry = {
|
|
488
488
|
'series_name': series.series_name,
|
|
@@ -764,7 +764,7 @@ def _load_config_from_dataframe(df: pl.DataFrame) -> DFMConfig:
|
|
|
764
764
|
blocks = []
|
|
765
765
|
|
|
766
766
|
if block_cols:
|
|
767
|
-
# CSV format:
|
|
767
|
+
# CSV format: global block, block_1, etc. columns with 1/0 values
|
|
768
768
|
for block_col in block_cols:
|
|
769
769
|
if block_col in df_dict:
|
|
770
770
|
block_value = df_dict[block_col][i]
|
|
@@ -789,13 +789,13 @@ def _load_config_from_dataframe(df: pl.DataFrame) -> DFMConfig:
|
|
|
789
789
|
elif isinstance(blocks_str, list):
|
|
790
790
|
blocks = blocks_str
|
|
791
791
|
else:
|
|
792
|
-
blocks = [
|
|
792
|
+
blocks = [DEFAULT_GLOBAL_BLOCK_NAME]
|
|
793
793
|
else:
|
|
794
|
-
blocks = [
|
|
794
|
+
blocks = [DEFAULT_GLOBAL_BLOCK_NAME]
|
|
795
795
|
|
|
796
|
-
# Ensure at least
|
|
796
|
+
# Ensure at least global block
|
|
797
797
|
if not blocks:
|
|
798
|
-
blocks = [
|
|
798
|
+
blocks = [DEFAULT_GLOBAL_BLOCK_NAME]
|
|
799
799
|
|
|
800
800
|
# Track block names
|
|
801
801
|
for block in blocks:
|
|
@@ -824,7 +824,7 @@ def _load_config_from_dataframe(df: pl.DataFrame) -> DFMConfig:
|
|
|
824
824
|
))
|
|
825
825
|
|
|
826
826
|
# Create default blocks if none specified
|
|
827
|
-
block_names = sorted(block_names_set) if block_names_set else [
|
|
827
|
+
block_names = sorted(block_names_set) if block_names_set else [DEFAULT_GLOBAL_BLOCK_NAME]
|
|
828
828
|
blocks = {}
|
|
829
829
|
for block_name in block_names:
|
|
830
830
|
blocks[block_name] = BlockConfig(factors=1, ar_lag=1, clock='m')
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Parameter dataclasses for DFM models.
|
|
2
|
+
|
|
3
|
+
This module contains parameter configuration classes:
|
|
4
|
+
- Params: Global estimation parameters with defaults
|
|
5
|
+
- FitParams: Optional parameter overrides for model fitting
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from .utils import validate_frequency
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Params:
|
|
15
|
+
"""Global estimation parameters for DFM (main settings only).
|
|
16
|
+
|
|
17
|
+
This dataclass contains only the global knobs that control the EM algorithm
|
|
18
|
+
and numerical stability. It does NOT include series or block definitions.
|
|
19
|
+
Use this with spec CSV files to provide main settings while the spec CSV
|
|
20
|
+
defines all series and their block memberships.
|
|
21
|
+
|
|
22
|
+
All parameters have defaults matching the standard YAML configuration.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
ar_lag : int
|
|
27
|
+
AR lag for factor transition equation (typically 1)
|
|
28
|
+
threshold : float
|
|
29
|
+
EM convergence threshold (default: 1e-5)
|
|
30
|
+
max_iter : int
|
|
31
|
+
Maximum EM iterations (default: 5000)
|
|
32
|
+
nan_method : int
|
|
33
|
+
Missing data handling method (1-5, default: 2 = spline interpolation).
|
|
34
|
+
This preprocessing step is followed by Kalman Filter-based missing data
|
|
35
|
+
handling during DFM estimation, following standard practice in state-space
|
|
36
|
+
models (Mariano & Murasawa 2003, Harvey 1989).
|
|
37
|
+
nan_k : int
|
|
38
|
+
Spline parameter for NaN interpolation (default: 3 for cubic spline)
|
|
39
|
+
clock : str
|
|
40
|
+
Base frequency for all latent factors (default: 'm' for monthly)
|
|
41
|
+
clip_ar_coefficients : bool
|
|
42
|
+
Enable AR coefficient clipping for stationarity (default: True)
|
|
43
|
+
ar_clip_min : float
|
|
44
|
+
Minimum AR coefficient (default: -0.99)
|
|
45
|
+
ar_clip_max : float
|
|
46
|
+
Maximum AR coefficient (default: 0.99)
|
|
47
|
+
warn_on_ar_clip : bool
|
|
48
|
+
Warn when AR coefficients are clipped (default: True)
|
|
49
|
+
clip_data_values : bool
|
|
50
|
+
Enable clipping of extreme data values (default: True)
|
|
51
|
+
data_clip_threshold : float
|
|
52
|
+
Clip values beyond this many standard deviations (default: 100.0)
|
|
53
|
+
warn_on_data_clip : bool
|
|
54
|
+
Warn when data values are clipped (default: True)
|
|
55
|
+
use_regularization : bool
|
|
56
|
+
Enable regularization for numerical stability (default: True)
|
|
57
|
+
regularization_scale : float
|
|
58
|
+
Scale factor for ridge regularization (default: 1e-5)
|
|
59
|
+
min_eigenvalue : float
|
|
60
|
+
Minimum eigenvalue for positive definite matrices (default: 1e-8)
|
|
61
|
+
max_eigenvalue : float
|
|
62
|
+
Maximum eigenvalue cap (default: 1e6)
|
|
63
|
+
warn_on_regularization : bool
|
|
64
|
+
Warn when regularization is applied (default: True)
|
|
65
|
+
use_damped_updates : bool
|
|
66
|
+
Enable damped updates when likelihood decreases (default: True)
|
|
67
|
+
damping_factor : float
|
|
68
|
+
Damping factor: 0.8 = 80% new, 20% old (default: 0.8)
|
|
69
|
+
warn_on_damped_update : bool
|
|
70
|
+
Warn when damped updates are used (default: True)
|
|
71
|
+
augment_idio : bool
|
|
72
|
+
Enable state augmentation with idiosyncratic components (default: True)
|
|
73
|
+
augment_idio_slow : bool
|
|
74
|
+
Enable tent-length chains for slower-frequency series (default: True)
|
|
75
|
+
idio_rho0 : float
|
|
76
|
+
Initial AR coefficient for idiosyncratic components (default: 0.1)
|
|
77
|
+
idio_min_var : float
|
|
78
|
+
Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
|
|
79
|
+
|
|
80
|
+
Examples
|
|
81
|
+
--------
|
|
82
|
+
>>> from dfm_python.config import Params
|
|
83
|
+
>>> params = Params(max_iter=100, threshold=1e-4)
|
|
84
|
+
>>> dfm.from_spec('data/spec.csv', params=params)
|
|
85
|
+
"""
|
|
86
|
+
# Estimation parameters
|
|
87
|
+
ar_lag: int = 1
|
|
88
|
+
threshold: float = 1e-5
|
|
89
|
+
max_iter: int = 5000
|
|
90
|
+
nan_method: int = 2
|
|
91
|
+
nan_k: int = 3
|
|
92
|
+
clock: str = 'm'
|
|
93
|
+
|
|
94
|
+
# Numerical stability - AR clipping
|
|
95
|
+
clip_ar_coefficients: bool = True
|
|
96
|
+
ar_clip_min: float = -0.99
|
|
97
|
+
ar_clip_max: float = 0.99
|
|
98
|
+
warn_on_ar_clip: bool = True
|
|
99
|
+
|
|
100
|
+
# Numerical stability - Data clipping
|
|
101
|
+
clip_data_values: bool = True
|
|
102
|
+
data_clip_threshold: float = 100.0
|
|
103
|
+
warn_on_data_clip: bool = True
|
|
104
|
+
|
|
105
|
+
# Numerical stability - Regularization
|
|
106
|
+
use_regularization: bool = True
|
|
107
|
+
regularization_scale: float = 1e-5
|
|
108
|
+
min_eigenvalue: float = 1e-8
|
|
109
|
+
max_eigenvalue: float = 1e6
|
|
110
|
+
warn_on_regularization: bool = True
|
|
111
|
+
|
|
112
|
+
# Numerical stability - Damped updates
|
|
113
|
+
use_damped_updates: bool = True
|
|
114
|
+
damping_factor: float = 0.8
|
|
115
|
+
warn_on_damped_update: bool = True
|
|
116
|
+
|
|
117
|
+
# Idiosyncratic component augmentation
|
|
118
|
+
augment_idio: bool = True # Enable state augmentation with idiosyncratic components (default: True)
|
|
119
|
+
augment_idio_slow: bool = True # Enable tent-length chains for slower-frequency series (default: True)
|
|
120
|
+
idio_rho0: float = 0.1 # Initial AR coefficient for idiosyncratic components (default: 0.1)
|
|
121
|
+
idio_min_var: float = 1e-8 # Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
|
|
122
|
+
|
|
123
|
+
def __post_init__(self):
|
|
124
|
+
"""Validate parameters."""
|
|
125
|
+
self.clock = validate_frequency(self.clock)
|
|
126
|
+
if self.threshold <= 0:
|
|
127
|
+
raise ValueError(f"threshold must be positive, got {self.threshold}")
|
|
128
|
+
if self.max_iter < 1:
|
|
129
|
+
raise ValueError(f"max_iter must be at least 1, got {self.max_iter}")
|
|
130
|
+
if self.ar_clip_min >= self.ar_clip_max:
|
|
131
|
+
raise ValueError(f"ar_clip_min ({self.ar_clip_min}) must be < ar_clip_max ({self.ar_clip_max})")
|
|
132
|
+
if not (-1 < self.ar_clip_min < 1):
|
|
133
|
+
raise ValueError(f"ar_clip_min must be in (-1, 1), got {self.ar_clip_min}")
|
|
134
|
+
if not (-1 < self.ar_clip_max < 1):
|
|
135
|
+
raise ValueError(f"ar_clip_max must be in (-1, 1), got {self.ar_clip_max}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass
|
|
139
|
+
class FitParams:
|
|
140
|
+
"""Parameters for fitting DFM models.
|
|
141
|
+
|
|
142
|
+
This dataclass groups all optional parameters that can override
|
|
143
|
+
config values during model fitting. This reduces method signature
|
|
144
|
+
complexity and improves code readability.
|
|
145
|
+
|
|
146
|
+
All parameters are optional. If None, the corresponding value
|
|
147
|
+
from DFMConfig will be used during parameter resolution.
|
|
148
|
+
"""
|
|
149
|
+
# Convergence parameters
|
|
150
|
+
threshold: Optional[float] = None
|
|
151
|
+
max_iter: Optional[int] = None
|
|
152
|
+
|
|
153
|
+
# Model structure
|
|
154
|
+
ar_lag: Optional[int] = None
|
|
155
|
+
num_factors: Optional[int] = None
|
|
156
|
+
|
|
157
|
+
# Missing data handling
|
|
158
|
+
nan_method: Optional[int] = None
|
|
159
|
+
nan_k: Optional[int] = None
|
|
160
|
+
|
|
161
|
+
# AR coefficient clipping
|
|
162
|
+
clip_ar_coefficients: Optional[bool] = None
|
|
163
|
+
ar_clip_min: Optional[float] = None
|
|
164
|
+
ar_clip_max: Optional[float] = None
|
|
165
|
+
|
|
166
|
+
# Data clipping
|
|
167
|
+
clip_data_values: Optional[bool] = None
|
|
168
|
+
data_clip_threshold: Optional[float] = None
|
|
169
|
+
|
|
170
|
+
# Regularization
|
|
171
|
+
use_regularization: Optional[bool] = None
|
|
172
|
+
regularization_scale: Optional[float] = None
|
|
173
|
+
min_eigenvalue: Optional[float] = None
|
|
174
|
+
max_eigenvalue: Optional[float] = None
|
|
175
|
+
|
|
176
|
+
# Damping
|
|
177
|
+
use_damped_updates: Optional[bool] = None
|
|
178
|
+
damping_factor: Optional[float] = None
|
|
179
|
+
|
|
180
|
+
def to_dict(self) -> dict:
|
|
181
|
+
"""Convert to dictionary, excluding None values."""
|
|
182
|
+
return {k: v for k, v in self.__dict__.items() if v is not None}
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_kwargs(cls, **kwargs) -> 'FitParams':
|
|
186
|
+
"""Create FitParams from keyword arguments.
|
|
187
|
+
|
|
188
|
+
Filters kwargs to only include valid parameter names,
|
|
189
|
+
ignoring any extra arguments.
|
|
190
|
+
"""
|
|
191
|
+
valid_params = {
|
|
192
|
+
'threshold', 'max_iter', 'ar_lag', 'num_factors',
|
|
193
|
+
'nan_method', 'nan_k',
|
|
194
|
+
'clip_ar_coefficients', 'ar_clip_min', 'ar_clip_max',
|
|
195
|
+
'clip_data_values', 'data_clip_threshold',
|
|
196
|
+
'use_regularization', 'regularization_scale',
|
|
197
|
+
'min_eigenvalue', 'max_eigenvalue',
|
|
198
|
+
'use_damped_updates', 'damping_factor'
|
|
199
|
+
}
|
|
200
|
+
filtered = {k: v for k, v in kwargs.items() if k in valid_params}
|
|
201
|
+
return cls(**filtered)
|
|
202
|
+
|