dfm-python 0.5.11__py3-none-any.whl → 0.5.52__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 +13 -27
- dfm_python/config/adapter.py +34 -57
- dfm_python/config/results.py +55 -1
- dfm_python/config/schema.py +232 -389
- dfm_python/data/__init__.py +2 -0
- dfm_python/data/dataloader.py +3 -3
- dfm_python/data/utils.py +12 -15
- dfm_python/decoder/__init__.py +14 -0
- dfm_python/decoder/linear.py +68 -0
- dfm_python/decoder/mlp.py +130 -0
- dfm_python/encoder/__init__.py +7 -6
- dfm_python/encoder/{vae.py → autoencoder.py} +63 -184
- dfm_python/lightning/__init__.py +8 -11
- dfm_python/lightning/ddfm_dm.py +417 -0
- dfm_python/lightning/dfm_dm.py +379 -0
- dfm_python/lightning/utils.py +516 -0
- dfm_python/logger/inference_logger.py +1 -1
- dfm_python/logger/logger.py +23 -10
- dfm_python/logger/train_logger.py +1 -1
- dfm_python/models/__init__.py +3 -2
- dfm_python/models/base.py +475 -164
- dfm_python/models/ddfm.py +1077 -1763
- dfm_python/models/dfm.py +527 -101
- dfm_python/models/utils.py +470 -0
- dfm_python/ssm/em.py +535 -54
- dfm_python/ssm/kalman.py +336 -30
- dfm_python/ssm/utils.py +65 -17
- dfm_python/trainer/__init__.py +12 -3
- dfm_python/trainer/ddfm.py +7 -6
- dfm_python/trainer/denoising.py +490 -0
- dfm_python/trainer/dfm.py +3 -3
- dfm_python/utils/__init__.py +12 -35
- dfm_python/utils/data.py +68 -3
- dfm_python/utils/helpers.py +1 -1
- dfm_python/utils/statespace.py +10 -7
- dfm_python/utils/time.py +3 -3
- dfm_python-0.5.52.dist-info/METADATA +510 -0
- dfm_python-0.5.52.dist-info/RECORD +48 -0
- dfm_python/lightning/data_module.py +0 -686
- dfm_python/lightning/scaling.py +0 -432
- dfm_python/nowcast/__init__.py +0 -37
- dfm_python/nowcast/dataview.py +0 -143
- dfm_python/nowcast/helpers.py +0 -356
- dfm_python/nowcast/nowcast.py +0 -1263
- dfm_python/nowcast/splitters.py +0 -441
- dfm_python/nowcast/transformers.py +0 -330
- dfm_python/nowcast/utils.py +0 -285
- dfm_python-0.5.11.dist-info/METADATA +0 -1299
- dfm_python-0.5.11.dist-info/RECORD +0 -49
- {dfm_python-0.5.11.dist-info → dfm_python-0.5.52.dist-info}/WHEEL +0 -0
- {dfm_python-0.5.11.dist-info → dfm_python-0.5.52.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py
CHANGED
|
@@ -6,7 +6,6 @@ This package implements a comprehensive Dynamic Factor Model framework with supp
|
|
|
6
6
|
- Tent kernel aggregation for low-to-high frequency mapping
|
|
7
7
|
- Expectation-Maximization (EM) algorithm for parameter estimation
|
|
8
8
|
- Kalman filtering and smoothing for factor extraction
|
|
9
|
-
- News decomposition for nowcasting
|
|
10
9
|
- Deep Dynamic Factor Models (DDFM) with nonlinear encoders (requires PyTorch)
|
|
11
10
|
|
|
12
11
|
The package implements a clock-based approach to mixed-frequency DFMs, where all latent
|
|
@@ -23,7 +22,6 @@ Key Features:
|
|
|
23
22
|
- Flexible block structure for factor modeling
|
|
24
23
|
- Robust handling of missing data (internal spline interpolation)
|
|
25
24
|
- Automatic standardization and data clipping
|
|
26
|
-
- News decomposition for forecast updates
|
|
27
25
|
|
|
28
26
|
Example (Standard Lightning Pattern):
|
|
29
27
|
>>> from dfm_python import DFM, DFMDataModule, DFMTrainer
|
|
@@ -67,7 +65,7 @@ Note: DFMConfig and SeriesConfig are internal implementation details.
|
|
|
67
65
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
68
66
|
"""
|
|
69
67
|
|
|
70
|
-
__version__ = "0.5.
|
|
68
|
+
__version__ = "0.5.5"
|
|
71
69
|
|
|
72
70
|
# ============================================================================
|
|
73
71
|
# PUBLIC API DEFINITION
|
|
@@ -81,15 +79,14 @@ __version__ = "0.5.11"
|
|
|
81
79
|
# 2. High-level API: DFM, DDFM, module-level convenience functions
|
|
82
80
|
# 3. Core utilities: TimeIndex, diagnostics
|
|
83
81
|
# 4. Models: BaseFactorModel, DDFM (low-level)
|
|
84
|
-
# 5.
|
|
85
|
-
# 6. Data & Results: DFMResult
|
|
82
|
+
# 5. Data & Results: DFMResult
|
|
86
83
|
# ============================================================================
|
|
87
84
|
|
|
88
85
|
# Configuration (from config/ subpackage)
|
|
89
86
|
from .config import (
|
|
90
87
|
DEFAULT_BLOCK_NAME,
|
|
91
|
-
ConfigSource, YamlSource,
|
|
92
|
-
|
|
88
|
+
ConfigSource, YamlSource, HydraSource,
|
|
89
|
+
make_config_source,
|
|
93
90
|
)
|
|
94
91
|
# Internal imports (for backward compatibility, but not recommended)
|
|
95
92
|
from .config import DFMConfig, SeriesConfig # Internal use only
|
|
@@ -105,56 +102,45 @@ from .utils.time import calculate_rmse
|
|
|
105
102
|
# Users can import these directly from dfm_python
|
|
106
103
|
from .lightning import (
|
|
107
104
|
DFMDataModule,
|
|
105
|
+
DDFMDataModule, # DDFM-specific DataModule
|
|
108
106
|
DFMDataset, # Dataset class (usually not needed directly)
|
|
107
|
+
DDFMDataset, # DDFM Dataset class (usually not needed directly)
|
|
109
108
|
KalmanFilter, # Module class
|
|
110
109
|
EMAlgorithm, # Module class
|
|
111
110
|
)
|
|
112
111
|
|
|
113
|
-
# Nowcasting (from nowcast/ subpackage)
|
|
114
|
-
from .nowcast import (
|
|
115
|
-
Nowcast,
|
|
116
|
-
NowcastResult,
|
|
117
|
-
)
|
|
118
|
-
from .nowcast.helpers import (
|
|
119
|
-
para_const,
|
|
120
|
-
NewsDecompResult,
|
|
121
|
-
BacktestResult,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
112
|
# Model implementations
|
|
125
113
|
from .models.base import BaseFactorModel
|
|
126
114
|
from .models.dfm import DFM
|
|
127
115
|
|
|
128
|
-
# DDFM high-level API
|
|
129
|
-
from .models.ddfm import DDFM
|
|
116
|
+
# DDFM high-level API (PyTorch is mandatory)
|
|
117
|
+
from .models.ddfm import DDFM
|
|
130
118
|
|
|
131
119
|
__all__ = [
|
|
132
120
|
# Core classes
|
|
133
|
-
'DFM',
|
|
121
|
+
'DFM',
|
|
134
122
|
# Model base and implementations
|
|
135
123
|
'BaseFactorModel',
|
|
136
|
-
# Nowcast result classes
|
|
137
|
-
'NowcastResult', 'NewsDecompResult', 'BacktestResult',
|
|
138
124
|
# Constants
|
|
139
125
|
'DEFAULT_BLOCK_NAME',
|
|
140
126
|
# Config sources
|
|
141
|
-
'ConfigSource', 'YamlSource', '
|
|
142
|
-
'
|
|
127
|
+
'ConfigSource', 'YamlSource', 'HydraSource',
|
|
128
|
+
'make_config_source',
|
|
143
129
|
# Low-level API (functional interface - advanced usage)
|
|
144
130
|
'BaseResult', 'DFMResult', 'DDFMResult', 'calculate_rmse', 'diagnose_series', 'print_series_diagnosis',
|
|
145
|
-
'para_const', # Internal utility for nowcasting
|
|
146
131
|
]
|
|
147
132
|
|
|
148
133
|
# DDFM high-level API (PyTorch is mandatory)
|
|
149
134
|
__all__.extend([
|
|
150
135
|
'DDFM', # High-level API class
|
|
151
|
-
'DDFMModel', # Low-level implementation (used internally)
|
|
152
136
|
])
|
|
153
137
|
|
|
154
138
|
# Lightning modules (mandatory dependency)
|
|
155
139
|
__all__.extend([
|
|
156
140
|
'DFMDataModule',
|
|
141
|
+
'DDFMDataModule', # DDFM-specific DataModule
|
|
157
142
|
'DFMDataset', # Dataset class (usually not needed directly)
|
|
143
|
+
'DDFMDataset', # DDFM Dataset class (usually not needed directly)
|
|
158
144
|
'KalmanFilter', # Module class
|
|
159
145
|
'EMAlgorithm', # Module class
|
|
160
146
|
])
|
dfm_python/config/adapter.py
CHANGED
|
@@ -208,14 +208,16 @@ class YamlSource:
|
|
|
208
208
|
except (ValueError, TypeError):
|
|
209
209
|
release_date = None
|
|
210
210
|
|
|
211
|
+
# Remove transformation and blocks if present (not in SeriesConfig)
|
|
212
|
+
series_data_clean = {k: v for k, v in series_data.items()
|
|
213
|
+
if k not in ['transformation', 'blocks']}
|
|
211
214
|
series_list.append(SeriesConfig(
|
|
212
215
|
series_id=series_id,
|
|
213
|
-
series_name=
|
|
214
|
-
frequency=
|
|
215
|
-
transformation
|
|
216
|
-
blocks
|
|
217
|
-
release_date=release_date,
|
|
218
|
-
scaler=series_data.get('scaler', None) # Optional, for per-series scaling
|
|
216
|
+
series_name=series_data_clean.get('series_name', series_id),
|
|
217
|
+
frequency=series_data_clean.get('frequency', 'm'),
|
|
218
|
+
# transformation removed - handled by preprocessing pipeline
|
|
219
|
+
# blocks removed - defined in DFMConfig
|
|
220
|
+
release_date=series_data_clean.get('release_date', release_date)
|
|
219
221
|
))
|
|
220
222
|
|
|
221
223
|
# If no series loaded from separate files, try to get from main config
|
|
@@ -225,17 +227,23 @@ class YamlSource:
|
|
|
225
227
|
for series_item in series_data:
|
|
226
228
|
if isinstance(series_item, str):
|
|
227
229
|
# Series is just a string ID - create minimal config
|
|
228
|
-
series_list.append(SeriesConfig(series_id=series_item, frequency='m'
|
|
230
|
+
series_list.append(SeriesConfig(series_id=series_item, frequency='m'))
|
|
229
231
|
elif isinstance(series_item, dict):
|
|
230
|
-
|
|
232
|
+
# Remove transformation and blocks if present (not in SeriesConfig)
|
|
233
|
+
series_item_clean = {k: v for k, v in series_item.items()
|
|
234
|
+
if k not in ['transformation', 'blocks']}
|
|
235
|
+
series_list.append(SeriesConfig(**series_item_clean))
|
|
231
236
|
elif isinstance(series_data, dict):
|
|
232
237
|
for series_id, series_item in series_data.items():
|
|
233
238
|
if isinstance(series_item, dict):
|
|
234
239
|
series_item['series_id'] = series_id
|
|
235
|
-
|
|
240
|
+
# Remove transformation and blocks if present (not in SeriesConfig)
|
|
241
|
+
series_item_clean = {k: v for k, v in series_item.items()
|
|
242
|
+
if k not in ['transformation', 'blocks']}
|
|
243
|
+
series_list.append(SeriesConfig(**series_item_clean))
|
|
236
244
|
elif isinstance(series_item, str):
|
|
237
245
|
# Series value is just a string - create minimal config
|
|
238
|
-
series_list.append(SeriesConfig(series_id=series_id, frequency='m'
|
|
246
|
+
series_list.append(SeriesConfig(series_id=series_id, frequency='m'))
|
|
239
247
|
|
|
240
248
|
# Load block properties from model config (factors, ar_lag, clock)
|
|
241
249
|
# Model config defines all blocks and their properties
|
|
@@ -251,28 +259,8 @@ class YamlSource:
|
|
|
251
259
|
if blocks_dict_raw is not None:
|
|
252
260
|
blocks_properties_from_config.update(blocks_dict_raw)
|
|
253
261
|
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
block_names_from_series = set()
|
|
257
|
-
for series in series_list:
|
|
258
|
-
if hasattr(series, 'blocks') and series.blocks:
|
|
259
|
-
# Handle both list of strings and list of ints
|
|
260
|
-
if isinstance(series.blocks, list):
|
|
261
|
-
for block in series.blocks:
|
|
262
|
-
if isinstance(block, str):
|
|
263
|
-
block_names_from_series.add(block)
|
|
264
|
-
|
|
265
|
-
# If no blocks specified in series, use default single block
|
|
266
|
-
# Use first block from model config if available, otherwise create default
|
|
267
|
-
if not block_names_from_series:
|
|
268
|
-
# Check if model config defines blocks
|
|
269
|
-
if blocks_properties_from_config:
|
|
270
|
-
# Use first block from model config
|
|
271
|
-
first_block_name = list(blocks_properties_from_config.keys())[0]
|
|
272
|
-
block_names_from_series.add(first_block_name)
|
|
273
|
-
else:
|
|
274
|
-
# Create default generic block
|
|
275
|
-
block_names_from_series.add(DEFAULT_BLOCK_NAME)
|
|
262
|
+
# Note: SeriesConfig no longer contains blocks information.
|
|
263
|
+
# Blocks are defined in DFMConfig, not in SeriesConfig.
|
|
276
264
|
|
|
277
265
|
# Create block dicts for each block
|
|
278
266
|
# Priority: 1) Model config properties, 2) Defaults from main_settings
|
|
@@ -297,24 +285,13 @@ class YamlSource:
|
|
|
297
285
|
'clock': default_clock
|
|
298
286
|
}
|
|
299
287
|
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
'clock': default_clock
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
# Convert block names to list for from_dict() if series use integer block indices
|
|
311
|
-
# Check if series use integer indices instead of block names
|
|
312
|
-
block_names_list = None
|
|
313
|
-
if series_list:
|
|
314
|
-
first_series_blocks = series_list[0].blocks if hasattr(series_list[0], 'blocks') else []
|
|
315
|
-
if first_series_blocks and isinstance(first_series_blocks[0], int):
|
|
316
|
-
# Series use integer indices, need block_names for from_dict()
|
|
317
|
-
block_names_list = list(blocks_dict.keys())
|
|
288
|
+
# If no blocks specified in config, use default single block
|
|
289
|
+
if not blocks_dict:
|
|
290
|
+
blocks_dict[DEFAULT_BLOCK_NAME] = {
|
|
291
|
+
'factors': 1,
|
|
292
|
+
'ar_lag': default_ar_lag,
|
|
293
|
+
'clock': default_clock
|
|
294
|
+
}
|
|
318
295
|
|
|
319
296
|
# Build dictionary for from_dict() which handles type detection
|
|
320
297
|
# Include all main_settings (which may contain DDFM parameters from model config)
|
|
@@ -324,10 +301,6 @@ class YamlSource:
|
|
|
324
301
|
**main_settings
|
|
325
302
|
}
|
|
326
303
|
|
|
327
|
-
# Add block_names if series use integer indices
|
|
328
|
-
if block_names_list is not None:
|
|
329
|
-
config_dict['block_names'] = block_names_list
|
|
330
|
-
|
|
331
304
|
# Use from_dict() which now correctly detects DDFM configs
|
|
332
305
|
# The detection bug has been fixed in schema.py
|
|
333
306
|
return DFMConfig.from_dict(config_dict)
|
|
@@ -645,12 +618,16 @@ if HYDRA_AVAILABLE and ConfigStore is not None:
|
|
|
645
618
|
|
|
646
619
|
@schema_dataclass
|
|
647
620
|
class SeriesConfigSchema:
|
|
648
|
-
"""Schema for SeriesConfig validation in Hydra.
|
|
621
|
+
"""Schema for SeriesConfig validation in Hydra.
|
|
622
|
+
|
|
623
|
+
Note: transformation is handled by preprocessing pipeline, not in SeriesConfig.
|
|
624
|
+
Note: blocks are defined in DFMConfig, not in SeriesConfig.
|
|
625
|
+
"""
|
|
649
626
|
series_id: str
|
|
650
627
|
series_name: str
|
|
651
628
|
frequency: str
|
|
652
|
-
transformation
|
|
653
|
-
blocks
|
|
629
|
+
# transformation removed - handled by preprocessing pipeline
|
|
630
|
+
# blocks removed - defined in DFMConfig
|
|
654
631
|
units: Optional[str] = None # Optional, for display only
|
|
655
632
|
|
|
656
633
|
@schema_dataclass
|
dfm_python/config/results.py
CHANGED
|
@@ -6,8 +6,9 @@ This module contains dataclasses for storing DFM estimation results and paramete
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import warnings
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Optional, List, Dict, Any
|
|
9
|
+
from typing import Optional, List, Dict, Any, Tuple
|
|
10
10
|
from abc import ABC
|
|
11
|
+
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
from .schema import DFMConfig
|
|
13
14
|
|
|
@@ -278,6 +279,59 @@ class DDFMResult(BaseResult):
|
|
|
278
279
|
use_idiosyncratic: Optional[bool] = None # Whether idio components were used
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
@dataclass
|
|
283
|
+
class NowcastResult:
|
|
284
|
+
"""Result from a single nowcast calculation.
|
|
285
|
+
|
|
286
|
+
This dataclass contains all information from a nowcast operation,
|
|
287
|
+
including the nowcast value, metadata about the data view, and
|
|
288
|
+
optional diagnostic information.
|
|
289
|
+
|
|
290
|
+
Attributes
|
|
291
|
+
----------
|
|
292
|
+
target_series : str
|
|
293
|
+
Target series ID that was nowcasted.
|
|
294
|
+
target_period : datetime
|
|
295
|
+
Target period for the nowcast (the period being estimated).
|
|
296
|
+
view_date : datetime
|
|
297
|
+
View date (when data is available). This determines which
|
|
298
|
+
data points are masked/unmasked in the nowcast calculation.
|
|
299
|
+
nowcast_value : float
|
|
300
|
+
The calculated nowcast value for the target series.
|
|
301
|
+
confidence_interval : Tuple[float, float], optional
|
|
302
|
+
Confidence interval (lower, upper) for the nowcast, if available.
|
|
303
|
+
factors_at_view : np.ndarray, optional
|
|
304
|
+
Factor values at the view_date (m,). These are the updated
|
|
305
|
+
factor states after applying the data view masking.
|
|
306
|
+
dfm_result : BaseResult, optional
|
|
307
|
+
Full DFM/DDFM result for this view. Can be used for further
|
|
308
|
+
analysis or diagnostics.
|
|
309
|
+
data_availability : Dict[str, int], optional
|
|
310
|
+
Dictionary with keys 'n_available' and 'n_missing' indicating
|
|
311
|
+
how many data points were available vs missing in the data view.
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
--------
|
|
315
|
+
>>> from dfm_python import DFM
|
|
316
|
+
>>> import numpy as np
|
|
317
|
+
>>> model = DFM()
|
|
318
|
+
>>> trainer.fit(model, data_module)
|
|
319
|
+
>>> # Update state with new data, then predict
|
|
320
|
+
>>> X_std = np.random.randn(10, 5) # Standardized data
|
|
321
|
+
>>> model.update(X_std)
|
|
322
|
+
>>> forecast = model.predict(horizon=1)
|
|
323
|
+
>>> print(f"Forecast: {forecast[0, 0]}")
|
|
324
|
+
"""
|
|
325
|
+
target_series: str
|
|
326
|
+
target_period: datetime
|
|
327
|
+
view_date: datetime
|
|
328
|
+
nowcast_value: float
|
|
329
|
+
confidence_interval: Optional[Tuple[float, float]] = None # (lower, upper)
|
|
330
|
+
factors_at_view: Optional[np.ndarray] = None # Factor values at view_date
|
|
331
|
+
dfm_result: Optional[BaseResult] = None # Full DFM/DDFM result for this view
|
|
332
|
+
data_availability: Optional[Dict[str, int]] = None # n_available, n_missing
|
|
333
|
+
|
|
334
|
+
|
|
281
335
|
@dataclass
|
|
282
336
|
class FitParams:
|
|
283
337
|
"""Parameter overrides for DFM model fitting.
|