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.
Files changed (51) hide show
  1. dfm_python/__init__.py +13 -27
  2. dfm_python/config/adapter.py +34 -57
  3. dfm_python/config/results.py +55 -1
  4. dfm_python/config/schema.py +232 -389
  5. dfm_python/data/__init__.py +2 -0
  6. dfm_python/data/dataloader.py +3 -3
  7. dfm_python/data/utils.py +12 -15
  8. dfm_python/decoder/__init__.py +14 -0
  9. dfm_python/decoder/linear.py +68 -0
  10. dfm_python/decoder/mlp.py +130 -0
  11. dfm_python/encoder/__init__.py +7 -6
  12. dfm_python/encoder/{vae.py → autoencoder.py} +63 -184
  13. dfm_python/lightning/__init__.py +8 -11
  14. dfm_python/lightning/ddfm_dm.py +417 -0
  15. dfm_python/lightning/dfm_dm.py +379 -0
  16. dfm_python/lightning/utils.py +516 -0
  17. dfm_python/logger/inference_logger.py +1 -1
  18. dfm_python/logger/logger.py +23 -10
  19. dfm_python/logger/train_logger.py +1 -1
  20. dfm_python/models/__init__.py +3 -2
  21. dfm_python/models/base.py +475 -164
  22. dfm_python/models/ddfm.py +1077 -1763
  23. dfm_python/models/dfm.py +527 -101
  24. dfm_python/models/utils.py +470 -0
  25. dfm_python/ssm/em.py +535 -54
  26. dfm_python/ssm/kalman.py +336 -30
  27. dfm_python/ssm/utils.py +65 -17
  28. dfm_python/trainer/__init__.py +12 -3
  29. dfm_python/trainer/ddfm.py +7 -6
  30. dfm_python/trainer/denoising.py +490 -0
  31. dfm_python/trainer/dfm.py +3 -3
  32. dfm_python/utils/__init__.py +12 -35
  33. dfm_python/utils/data.py +68 -3
  34. dfm_python/utils/helpers.py +1 -1
  35. dfm_python/utils/statespace.py +10 -7
  36. dfm_python/utils/time.py +3 -3
  37. dfm_python-0.5.52.dist-info/METADATA +510 -0
  38. dfm_python-0.5.52.dist-info/RECORD +48 -0
  39. dfm_python/lightning/data_module.py +0 -686
  40. dfm_python/lightning/scaling.py +0 -432
  41. dfm_python/nowcast/__init__.py +0 -37
  42. dfm_python/nowcast/dataview.py +0 -143
  43. dfm_python/nowcast/helpers.py +0 -356
  44. dfm_python/nowcast/nowcast.py +0 -1263
  45. dfm_python/nowcast/splitters.py +0 -441
  46. dfm_python/nowcast/transformers.py +0 -330
  47. dfm_python/nowcast/utils.py +0 -285
  48. dfm_python-0.5.11.dist-info/METADATA +0 -1299
  49. dfm_python-0.5.11.dist-info/RECORD +0 -49
  50. {dfm_python-0.5.11.dist-info → dfm_python-0.5.52.dist-info}/WHEEL +0 -0
  51. {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.11"
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. Nowcasting: Nowcast, result classes, para_const
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, DictSource, HydraSource,
92
- MergedConfigSource, make_config_source,
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 and low-level model (PyTorch is mandatory)
129
- from .models.ddfm import DDFM, DDFMModel
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', 'Nowcast',
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', 'DictSource', 'HydraSource',
142
- 'MergedConfigSource', 'make_config_source',
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
  ])
@@ -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=series_data.get('series_name', series_id),
214
- frequency=series_data.get('frequency', 'm'),
215
- transformation=series_data.get('transformation', 'lin'),
216
- blocks=series_data.get('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', transformation='lin', blocks=[1]))
230
+ series_list.append(SeriesConfig(series_id=series_item, frequency='m'))
229
231
  elif isinstance(series_item, dict):
230
- series_list.append(SeriesConfig(**series_item))
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
- series_list.append(SeriesConfig(**series_item))
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', transformation='lin', blocks=[1]))
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
- # Derive blocks from series configs (new pattern: blocks discovered from series)
255
- # Collect all unique block names from series
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
- # Then, ensure all blocks referenced by series exist
301
- for block_name in block_names_from_series:
302
- if block_name not in blocks_dict:
303
- # Create block with defaults if not in model config
304
- blocks_dict[block_name] = {
305
- 'factors': 1,
306
- 'ar_lag': default_ar_lag,
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: str
653
- blocks: ListType[int]
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
@@ -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.