dfm-python 0.1.8__tar.gz → 0.2.0__tar.gz

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 (45) hide show
  1. dfm_python-0.2.0/PKG-INFO +647 -0
  2. dfm_python-0.2.0/README.md +603 -0
  3. {dfm_python-0.1.8 → dfm_python-0.2.0}/config/blocks/default.yaml +0 -7
  4. {dfm_python-0.1.8 → dfm_python-0.2.0}/config/default.yaml +1 -1
  5. {dfm_python-0.1.8 → dfm_python-0.2.0}/pyproject.toml +2 -1
  6. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/__init__.py +69 -31
  7. dfm_python-0.2.0/src/dfm_python/config.py +1295 -0
  8. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/data_loader.py +187 -225
  9. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/dfm.py +248 -145
  10. dfm_python-0.2.0/src/dfm_python/dfm_api.py +630 -0
  11. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/kalman.py +4 -0
  12. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/news.py +62 -12
  13. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/aggregation.py +7 -1
  14. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/data_utils.py +27 -4
  15. dfm_python-0.2.0/src/test/test_api_edge_cases.py +295 -0
  16. dfm_python-0.2.0/src/test/test_config_sources.py +369 -0
  17. dfm_python-0.2.0/src/test/test_convenience_api.py +160 -0
  18. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_dfm.py +60 -17
  19. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_kalman.py +4 -4
  20. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_news.py +2 -1
  21. dfm_python-0.2.0/src/test/test_tutorials.py +288 -0
  22. dfm_python-0.2.0/tutorial/basic_tutorial.py +236 -0
  23. dfm_python-0.2.0/tutorial/hydra_tutorial.py +252 -0
  24. {dfm_python-0.1.8 → dfm_python-0.2.0}/uv.lock +3 -1
  25. dfm_python-0.1.8/PKG-INFO +0 -343
  26. dfm_python-0.1.8/README.md +0 -300
  27. dfm_python-0.1.8/src/dfm_python/config.py +0 -746
  28. dfm_python-0.1.8/src/dfm_python/dfm_api.py +0 -328
  29. dfm_python-0.1.8/trash/simple_api_example.py +0 -41
  30. dfm_python-0.1.8/trash/test_dfm_edge_cases.py +0 -552
  31. dfm_python-0.1.8/trash/test_dfm_stress.py +0 -377
  32. dfm_python-0.1.8/trash/test_synthetic.py +0 -130
  33. dfm_python-0.1.8/tutorial/basic_tutorial.ipynb +0 -577
  34. dfm_python-0.1.8/tutorial/basic_tutorial.py +0 -342
  35. {dfm_python-0.1.8 → dfm_python-0.2.0}/.gitignore +0 -0
  36. {dfm_python-0.1.8 → dfm_python-0.2.0}/.python-version +0 -0
  37. {dfm_python-0.1.8 → dfm_python-0.2.0}/CHANGELOG.md +0 -0
  38. {dfm_python-0.1.8 → dfm_python-0.2.0}/LICENSE +0 -0
  39. {dfm_python-0.1.8 → dfm_python-0.2.0}/MANIFEST.in +0 -0
  40. {dfm_python-0.1.8 → dfm_python-0.2.0}/config/series/default.yaml +0 -0
  41. {dfm_python-0.1.8 → dfm_python-0.2.0}/data/sample_data.csv +0 -0
  42. {dfm_python-0.1.8 → dfm_python-0.2.0}/data/sample_spec.csv +0 -0
  43. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/__init__.py +0 -0
  44. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/__init__.py +0 -0
  45. {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/__init__.py +0 -0
@@ -0,0 +1,647 @@
1
+ Metadata-Version: 2.4
2
+ Name: dfm-python
3
+ Version: 0.2.0
4
+ Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
5
+ Project-URL: Homepage, https://github.com/yourusername/dfm-python
6
+ Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
7
+ Project-URL: Repository, https://github.com/yourusername/dfm-python
8
+ Project-URL: Issues, https://github.com/yourusername/dfm-python/issues
9
+ Author: DFM Python Contributors
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: dfm,dynamic-factor-model,econometrics,forecasting,nowcasting,time-series
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: hydra-core>=1.3.2
21
+ Requires-Dist: matplotlib>=3.10.7
22
+ Requires-Dist: numpy>=1.24.0
23
+ Requires-Dist: pandas>=2.0.0
24
+ Requires-Dist: scipy>=1.10.0
25
+ Provides-Extra: all
26
+ Requires-Dist: httpx>=0.24.0; extra == 'all'
27
+ Requires-Dist: hydra-core>=1.3.0; extra == 'all'
28
+ Requires-Dist: omegaconf>=2.3.0; extra == 'all'
29
+ Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
30
+ Requires-Dist: pydantic-settings>=2.11.0; extra == 'all'
31
+ Requires-Dist: pytest>=7.0.0; extra == 'all'
32
+ Requires-Dist: supabase>=2.0.0; extra == 'all'
33
+ Provides-Extra: database
34
+ Requires-Dist: httpx>=0.24.0; extra == 'database'
35
+ Requires-Dist: psycopg2-binary>=2.9.0; extra == 'database'
36
+ Requires-Dist: pydantic-settings>=2.11.0; extra == 'database'
37
+ Requires-Dist: supabase>=2.0.0; extra == 'database'
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
40
+ Provides-Extra: hydra
41
+ Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
42
+ Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # dfm-python: Dynamic Factor Models for Python
46
+
47
+ [![PyPI version](https://img.shields.io/pypi/v/dfm-python.svg)](https://pypi.org/project/dfm-python/)
48
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
49
+
50
+ A Python implementation of **Dynamic Factor Models (DFM)** for nowcasting and forecasting high-dimensional time series. Implements clock-based synchronization and tent kernel aggregation for mixed-frequency data.
51
+
52
+ ## Features
53
+
54
+ - **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual (no daily/weekly)
55
+ - **Clock-based framework**: All factors evolve at a common clock frequency
56
+ - **Block structure**: Flexible factor organization (global, sector-specific)
57
+ - **Robust missing data**: Spline interpolation and Kalman filter handling
58
+ - **News decomposition**: Attribute forecast changes to data releases
59
+ - **Generic design**: YAML configs or direct object creation, extensible via adapters
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ pip install dfm-python
65
+ ```
66
+
67
+ **Requirements**: Python >= 3.12, numpy >= 1.24.0, pandas >= 2.0.0, scipy >= 1.10.0
68
+
69
+ ## Quick Start
70
+
71
+ ### Class-Based API (Recommended)
72
+
73
+ The package is built around a `DFM` class that provides both core estimation (`fit()`) and convenience methods (`load_config()`, `load_data()`, `train()`, `predict()`, `plot()`).
74
+
75
+ #### Direct Class Usage
76
+
77
+ ```python
78
+ from dfm_python.dfm import DFM
79
+ from dfm_python.config import DFMConfig, SeriesConfig, BlockConfig
80
+
81
+ # Create config
82
+ config = DFMConfig(
83
+ series=[SeriesConfig(frequency='m', transformation='lin', blocks=[1], series_id='s1')],
84
+ blocks={'Block_Global': BlockConfig(factors=1, clock='m')}
85
+ )
86
+
87
+ # Create model and fit
88
+ model = DFM()
89
+ result = model.fit(X, config, max_iter=100)
90
+ factors = result.Z
91
+ ```
92
+
93
+ #### Module-Level Convenience API (Recommended for Most Users)
94
+
95
+ The package provides a singleton instance and convenient constructors for easy usage:
96
+
97
+ #### 1. From YAML (Simplest)
98
+
99
+ ```python
100
+ import dfm_python as dfm
101
+
102
+ # Load from YAML file
103
+ dfm.from_yaml('config/default.yaml')
104
+
105
+ # Load data, train, predict, plot
106
+ dfm.load_data('data/sample_data.csv', sample_start='2015-01-01', sample_end='2022-12-31')
107
+ dfm.train(max_iter=5000)
108
+ X_forecast, Z_forecast = dfm.predict(horizon=12)
109
+ dfm.plot(kind='factor', factor_index=0, forecast_horizon=12, save_path='outputs/factor_forecast.png')
110
+ ```
111
+
112
+ #### 2. From Spec CSV + Params (Ideal for CSV-based series definitions)
113
+
114
+ ```python
115
+ import dfm_python as dfm
116
+ from dfm_python.config import Params
117
+
118
+ # Create Params object with main settings
119
+ params = Params(
120
+ max_iter=5000,
121
+ threshold=1e-5,
122
+ regularization_scale=1e-5,
123
+ damping_factor=0.8
124
+ )
125
+
126
+ # Load from spec CSV (series definitions) + Params (main settings)
127
+ dfm.from_spec('data/sample_spec.csv', params=params)
128
+
129
+ # Or use defaults
130
+ dfm.from_spec('data/sample_spec.csv') # Uses Params() defaults
131
+
132
+ # Load data, train, predict
133
+ dfm.load_data('data/sample_data.csv')
134
+ dfm.train()
135
+ X_forecast, Z_forecast = dfm.predict(horizon=12)
136
+ ```
137
+
138
+ #### 3. From Dictionary (Programmatic)
139
+
140
+ ```python
141
+ import dfm_python as dfm
142
+
143
+ config_dict = {
144
+ 'clock': 'm',
145
+ 'max_iter': 5000,
146
+ 'blocks': {'Block_Global': {'factors': 1, 'clock': 'm'}},
147
+ 'series': [{'series_id': 's1', 'frequency': 'm', 'transformation': 'lin', 'blocks': [0]}]
148
+ }
149
+
150
+ dfm.from_dict(config_dict)
151
+ dfm.load_data('data/sample_data.csv')
152
+ dfm.train()
153
+ ```
154
+
155
+ #### Access Results
156
+
157
+ ```python
158
+ # Access results (DFMResult)
159
+ result = dfm.get_result()
160
+ factors = result.Z # (T × m) Smoothed factors
161
+ loadings = result.C # (N × m) Factor loadings
162
+ smoothed = result.X_sm # (T × N) Smoothed data
163
+ ```
164
+
165
+ ### Configuration Sources
166
+
167
+ The package supports multiple configuration sources. Choose based on your needs:
168
+
169
+ #### 1. Hydra Decorator (Recommended for Hydra users)
170
+
171
+ Use the `@hydra.main` decorator for powerful configuration composition and CLI overrides:
172
+
173
+ ```python
174
+ import hydra
175
+ from hydra.utils import get_original_cwd
176
+ from omegaconf import DictConfig
177
+ import dfm_python as dfm
178
+ from pathlib import Path
179
+
180
+ @hydra.main(config_path="../config", config_name="default", version_base="1.3")
181
+ def main(cfg: DictConfig) -> None:
182
+ # Access config values explicitly via cfg.*
183
+ print(f"max_iter: {cfg.max_iter}")
184
+ print(f"threshold: {cfg.threshold}")
185
+ print(f"blocks.Block_Global.factors: {cfg.blocks.Block_Global.factors}")
186
+
187
+ # Load configuration
188
+ dfm.load_config(hydra=cfg)
189
+
190
+ # Resolve paths relative to original working directory
191
+ original_cwd = Path(get_original_cwd())
192
+ data_path = original_cwd / "data" / "sample_data.csv"
193
+
194
+ # Load data, train, predict
195
+ dfm.load_data(str(data_path), sample_start="2021-01-01", sample_end="2022-12-31")
196
+ dfm.train(max_iter=cfg.max_iter) # Use cfg.max_iter explicitly
197
+ X_forecast, Z_forecast = dfm.predict(horizon=cfg.get('forecast_horizon', 12))
198
+
199
+ if __name__ == "__main__":
200
+ main()
201
+ ```
202
+
203
+ **CLI Overrides**:
204
+ ```bash
205
+ # Override parameters
206
+ python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4
207
+
208
+ # Override block settings
209
+ python tutorial/hydra_tutorial.py blocks.Block_Global.factors=2
210
+
211
+ # Multiple overrides
212
+ python tutorial/hydra_tutorial.py max_iter=10 damping_factor=0.9 regularization_scale=1e-6
213
+ ```
214
+
215
+ See `tutorial/hydra_tutorial.py` for a complete example.
216
+
217
+ #### 2. Spec CSV + Params (Ideal for CSV-based workflows)
218
+
219
+ ```python
220
+ import dfm_python as dfm
221
+ from dfm_python.config import Params
222
+
223
+ # Create Params object with all exposed parameters
224
+ params = Params(
225
+ max_iter=5000,
226
+ threshold=1e-5,
227
+ nan_method=2,
228
+ clock='m',
229
+ regularization_scale=1e-5,
230
+ damping_factor=0.8,
231
+ # ... all other parameters
232
+ )
233
+
234
+ # Load from spec CSV (series definitions) + Params (main settings)
235
+ dfm.from_spec('data/sample_spec.csv', params=params)
236
+
237
+ # Or use defaults
238
+ dfm.from_spec('data/sample_spec.csv') # Uses Params() defaults
239
+ ```
240
+
241
+ The `Params` dataclass contains all global estimation parameters:
242
+ - **Estimation**: `ar_lag`, `threshold`, `max_iter`, `nan_method`, `nan_k`, `clock`
243
+ - **AR Clipping**: `clip_ar_coefficients`, `ar_clip_min`, `ar_clip_max`, `warn_on_ar_clip`
244
+ - **Data Clipping**: `clip_data_values`, `data_clip_threshold`, `warn_on_data_clip`
245
+ - **Regularization**: `use_regularization`, `regularization_scale`, `min_eigenvalue`, `max_eigenvalue`, `warn_on_regularization`
246
+ - **Damped Updates**: `use_damped_updates`, `damping_factor`, `warn_on_damped_update`
247
+
248
+ See `tutorial/basic_tutorial.py` for a complete example with all parameters exposed.
249
+
250
+ #### 3. YAML File (Simplest)
251
+
252
+ ```python
253
+ import dfm_python as dfm
254
+
255
+ # Load from YAML file
256
+ dfm.from_yaml('config/default.yaml')
257
+
258
+ # Or use the generic load_config
259
+ dfm.load_config(yaml='config/default.yaml')
260
+ ```
261
+
262
+ #### 4. Dictionary (Programmatic)
263
+
264
+ ```python
265
+ import dfm_python as dfm
266
+
267
+ config_dict = {
268
+ 'clock': 'm',
269
+ 'max_iter': 5000,
270
+ 'threshold': 1e-5,
271
+ 'blocks': {'Block_Global': {'factors': 1, 'clock': 'm'}},
272
+ 'series': [{'series_id': 's1', 'frequency': 'm', 'transformation': 'lin', 'blocks': [0]}]
273
+ }
274
+
275
+ dfm.from_dict(config_dict)
276
+ ```
277
+
278
+ #### 5. Spec DataFrame (In-memory)
279
+
280
+ ```python
281
+ import pandas as pd
282
+ import dfm_python as dfm
283
+ from dfm_python.config import Params
284
+
285
+ # Load spec from DataFrame (useful when already in memory)
286
+ spec_df = pd.read_csv('data/sample_spec.csv')
287
+ params = Params(max_iter=100)
288
+
289
+ dfm.from_spec_df(spec_df, params=params)
290
+ ```
291
+
292
+ #### Functional API (Deprecated)
293
+
294
+ The `dfm()` function is deprecated but still supported for backward compatibility:
295
+
296
+ ```python
297
+ from dfm_python import dfm, load_config, load_data
298
+
299
+ config = load_config('config.yaml')
300
+ X, Time, _ = load_data('data.csv', config)
301
+ result = dfm(X, config, max_iter=100) # Deprecated: emits DeprecationWarning
302
+ ```
303
+
304
+ **Note**: For new code, prefer:
305
+ - `DFM().fit()` for direct class usage
306
+ - `dfm.load_config().train()` for module-level convenience API
307
+
308
+ #### Legacy Config Loading (Still Supported)
309
+
310
+ All the original `load_config()` methods still work:
311
+
312
+ ```python
313
+ import dfm_python as dfm
314
+
315
+ # All these still work (backward compatible)
316
+ dfm.load_config(yaml='config/default.yaml')
317
+ dfm.load_config(mapping={...})
318
+ dfm.load_config(spec='data/spec.csv')
319
+ dfm.load_config(hydra=hydra_cfg)
320
+ dfm.load_config(base='config/default.yaml', override='data/spec.csv')
321
+ ```
322
+
323
+ #### ConfigSource Adapters (Advanced)
324
+
325
+ ```python
326
+ from dfm_python.config_sources import make_config_source, YamlSource, DictSource, SpecCSVSource, HydraSource
327
+
328
+ # Factory creates the right adapter based on input
329
+ source = make_config_source('config/default.yaml') # YAML
330
+ source = make_config_source(mapping={'series': {...}}) # Dict
331
+ source = make_config_source(spec='data/spec.csv') # Spec CSV
332
+ source = make_config_source(hydra=hydra_cfg) # Hydra DictConfig
333
+ config = source.load() # -> DFMConfig
334
+ ```
335
+
336
+ #### Merging Configurations
337
+
338
+ Combine base config (YAML) with series from spec CSV:
339
+
340
+ ```python
341
+ import dfm_python as dfm
342
+
343
+ # Base config provides main settings, spec CSV provides series
344
+ dfm.load_config(
345
+ base='config/default.yaml',
346
+ override='data/sample_spec.csv'
347
+ )
348
+ ```
349
+
350
+ ### Low-Level API (Advanced)
351
+
352
+ For more control, use the `DFM` class directly:
353
+
354
+ ```python
355
+ from dfm_python.dfm import DFM
356
+ from dfm_python import DFMConfig, SeriesConfig, load_data, BlockConfig
357
+
358
+ # Create configuration programmatically
359
+ config = DFMConfig(
360
+ series=[
361
+ SeriesConfig(
362
+ series_id='gdp_real',
363
+ series_name='Real GDP',
364
+ frequency='q',
365
+ transformation='pca',
366
+ category='National Accounts',
367
+ units='Index',
368
+ blocks=[1] # Loads on first block (global)
369
+ )
370
+ ],
371
+ blocks={'Block_Global': BlockConfig(factors=1, clock='m')},
372
+ clock='m',
373
+ threshold=1e-5,
374
+ max_iter=5000
375
+ )
376
+
377
+ # Load data and estimate using DFM class
378
+ X, Time, _ = load_data('data.csv', config)
379
+ model = DFM()
380
+ result = model.fit(X, config) # Recommended: use DFM().fit()
381
+ # Or use deprecated functional API:
382
+ # result = dfm(X, config) # Deprecated: emits DeprecationWarning
383
+ ```
384
+
385
+ ## Configuration
386
+
387
+ ### YAML Format
388
+
389
+ ```yaml
390
+ dfm:
391
+ clock: "m" # Clock frequency (base for all factors)
392
+ threshold: 1e-5 # EM convergence threshold
393
+ max_iter: 5000 # Maximum EM iterations
394
+ ar_lag: 1 # AR lag for factors
395
+
396
+ series:
397
+ gdp_real:
398
+ series_name: "Real GDP"
399
+ frequency: "q"
400
+ transformation: "pca"
401
+ category: "National Accounts"
402
+ units: "Index"
403
+ blocks: [Global]
404
+
405
+ consumption:
406
+ series_name: "Consumption"
407
+ frequency: "m"
408
+ transformation: "pch"
409
+ category: "Consumption"
410
+ units: "Index"
411
+ blocks: [Global]
412
+ ```
413
+
414
+ ### Key Parameters
415
+
416
+ - **clock**: Base frequency for latent factors (typically "m" for monthly)
417
+ - **threshold**: EM convergence criterion (default: 1e-5)
418
+ - **max_iter**: Maximum EM iterations (default: 5000)
419
+ - **ar_lag**: Autoregressive lag for factors (default: 1)
420
+
421
+ ### Numerical Stability
422
+
423
+ All numerical stability techniques are configurable and transparent:
424
+
425
+ ```yaml
426
+ dfm:
427
+ clip_ar_coefficients: true # Ensure stationarity
428
+ clip_data_values: true # Clip extreme outliers
429
+ use_regularization: true # Prevent ill-conditioned matrices
430
+ regularization_scale: 1e-5 # Scale relative to matrix trace (default 1e-5)
431
+ use_damped_updates: true # Prevent likelihood decreases
432
+ ```
433
+
434
+ Warnings are logged when techniques are applied. All parameters can be disabled for research purposes.
435
+
436
+ Note:
437
+ - Default `regularization_scale` is 1e-5 across code and YAML.
438
+ - You can override via Hydra CLI (see above).
439
+
440
+ ## Data Format
441
+
442
+ ### File-Based (CSV)
443
+
444
+ Data file with series as columns and dates as rows:
445
+
446
+ ```csv
447
+ Date,gdp_real,consumption,investment
448
+ 2000-01-01,,98.5,95.0
449
+ 2000-02-01,,98.7,95.2
450
+ 2000-03-01,100.5,99.0,95.5
451
+ ```
452
+
453
+ **Requirements**:
454
+ - First column: `Date` (YYYY-MM-DD format)
455
+ - Column names: Must match `series_id` in configuration
456
+ - Missing values: Empty cells or NaN
457
+ - Mixed frequencies: Quarterly series only at quarter-end months
458
+
459
+ **Frequency Support**:
460
+ - **Supported**: Monthly (m), Quarterly (q), Semi-annual (sa), Annual (a)
461
+ - **Not supported**: Daily (d), Weekly (w) - raises ValueError
462
+ - All series must have frequency ≤ clock frequency
463
+
464
+ ### Database-Backed
465
+
466
+ For production applications, implement adapters that return:
467
+ - `X`: Transformed data matrix (T × N)
468
+ - `Time`: Time index (pandas DatetimeIndex)
469
+ - `Z`: Original untransformed data (T × N)
470
+
471
+ ## Usage Examples
472
+
473
+ ### Basic Estimation
474
+
475
+ ```python
476
+ from dfm_python import load_config, load_data, dfm
477
+
478
+ config = load_config('config.yaml')
479
+ X, Time, _ = load_data('data.csv', config)
480
+ result = dfm(X, config)
481
+
482
+ # Extract common factor
483
+ common_factor = result.Z[:, 0]
484
+
485
+ # Model fit
486
+ rmse = result.rmse
487
+ ```
488
+
489
+ ### News Decomposition
490
+
491
+ ```python
492
+ from dfm_python import news_dfm
493
+
494
+ # Estimate on old and new data
495
+ result_old = dfm(X_old, config)
496
+ result_new = dfm(X_new, config)
497
+
498
+ # Decompose news
499
+ y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
500
+ X_old, result_old, t_fcst=100, v_news=0
501
+ )
502
+
503
+ # Forecast update
504
+ forecast_update = y_new - y_old
505
+ ```
506
+
507
+ ## API Reference
508
+
509
+ ### `load_config(configfile) -> DFMConfig`
510
+
511
+ Load configuration from YAML file or return existing DFMConfig object.
512
+
513
+ **Parameters**:
514
+ - `configfile`: Path to YAML file (.yaml, .yml) or `DFMConfig` object
515
+
516
+ **Returns**: `DFMConfig` object
517
+
518
+ ### `load_data(datafile, config, sample_start=None) -> Tuple[np.ndarray, pd.DatetimeIndex, np.ndarray]`
519
+
520
+ Load and transform time series data.
521
+
522
+ **Parameters**:
523
+ - `datafile`: Path to data file (CSV supported)
524
+ - `config`: `DFMConfig` object
525
+ - `sample_start`: Optional start date (YYYY-MM-DD) to filter data
526
+
527
+ **Returns**: `(X, Time, Z)` where:
528
+ - `X`: Transformed data matrix (T × N)
529
+ - `Time`: Time index (DatetimeIndex)
530
+ - `Z`: Original untransformed data (T × N)
531
+
532
+ ### `DFM.fit(X, config, threshold=None, max_iter=None, ...) -> DFMResult`
533
+
534
+ **Recommended**: Estimate Dynamic Factor Model using EM algorithm via the `DFM` class.
535
+
536
+ ```python
537
+ from dfm_python.dfm import DFM
538
+ model = DFM()
539
+ result = model.fit(X, config, max_iter=100)
540
+ ```
541
+
542
+ ### `dfm(X, config, threshold=None, max_iter=None) -> DFMResult` (Deprecated)
543
+
544
+ **Deprecated**: Use `DFM().fit()` instead. This function emits a `DeprecationWarning`.
545
+
546
+ Estimate Dynamic Factor Model using EM algorithm.
547
+
548
+ **Parameters**:
549
+ - `X`: Data matrix (T × N) with possible missing values
550
+ - `config`: `DFMConfig` object
551
+ - `threshold`: Convergence threshold (default: from config)
552
+ - `max_iter`: Maximum iterations (default: from config)
553
+
554
+ **Returns**: `DFMResult` object
555
+
556
+ ### `DFMResult` Object
557
+
558
+ Contains all estimation outputs:
559
+
560
+ - **Factors**: `Z` (T × m), `C` (N × m)
561
+ - **Parameters**: `A` (m × m), `Q` (m × m), `R` (N × N)
562
+ - **Smoothed Data**: `X_sm` (T × N), `x_sm` (T × N)
563
+ - **Initial Conditions**: `Z_0` (m,), `V_0` (m × m)
564
+ - **Standardization**: `Mx` (N,), `Wx` (N,)
565
+ - **Convergence**: `converged`, `num_iter`, `loglik`
566
+ - **Model Fit**: `rmse`, `rmse_per_series`
567
+
568
+ ## Architecture
569
+
570
+ **Core Modules**:
571
+ - `config.py`: Configuration management
572
+ - `data_loader.py`: Data loading and transformation
573
+ - `dfm.py`: Core estimation (EM algorithm)
574
+ - `kalman.py`: Kalman filter and smoother
575
+ - `news.py`: News decomposition
576
+
577
+ **Design Principles**:
578
+ - Generic core (no application-specific code)
579
+ - Flexible configuration (YAML or direct object creation)
580
+ - Extensible via application-specific adapters
581
+ - Robust error handling and numerical stability
582
+ - Hydra-friendly: tutorial uses `@hydra.main` with `get_original_cwd()`; paths remain stable
583
+ - High-level API: `dfm.predict()`, `dfm.plot()` for concise forecasting and visualization
584
+
585
+ ### Tutorials
586
+
587
+ Two comprehensive tutorials demonstrate different approaches:
588
+
589
+ 1. **`tutorial/basic_tutorial.py`** - Spec CSV + Params approach
590
+ - No Hydra dependency
591
+ - Uses `Params` dataclass for main settings
592
+ - Spec CSV defines all series and block memberships
593
+ - All parameters exposed as CLI arguments
594
+ - Run: `python tutorial/basic_tutorial.py --spec data/sample_spec.csv --data data/sample_data.csv`
595
+
596
+ 2. **`tutorial/hydra_tutorial.py`** - Hydra decorator approach
597
+ - Uses `@hydra.main` decorator
598
+ - Access config via `cfg.*` (e.g., `cfg.max_iter`, `cfg.blocks.Block_Global.factors`)
599
+ - CLI overrides for experimentation
600
+ - Run: `python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4`
601
+
602
+ Both tutorials demonstrate:
603
+ - Configuration loading
604
+ - Data loading with date windowing
605
+ - Training with explicit parameters
606
+ - Forecasting and visualization
607
+ - Saving results
608
+
609
+ See the tutorial files for complete examples with all features.
610
+
611
+ ## Troubleshooting
612
+
613
+ **Convergence Issues**:
614
+ - Increase `max_iter` (try 10000)
615
+ - Relax `threshold` (try 1e-3)
616
+ - Check data quality and missing data patterns
617
+
618
+ **Dimension Mismatch**:
619
+ - Ensure `series_id` in config matches data column names
620
+ - Verify block structure consistency
621
+ - Check frequency codes are valid
622
+
623
+ **Numerical Instability**:
624
+ - Monitor warnings for stability techniques
625
+ - Adjust thresholds based on data characteristics
626
+ - Investigate data quality if techniques frequently needed
627
+
628
+ ## License
629
+
630
+ MIT License
631
+
632
+ ## Citation
633
+
634
+ ```bibtex
635
+ @software{dfm-python,
636
+ title = {dfm-python: Dynamic Factor Models for Nowcasting and Forecasting},
637
+ author = {DFM Python Contributors},
638
+ year = {2025},
639
+ url = {https://pypi.org/project/dfm-python/}
640
+ }
641
+ ```
642
+
643
+ ---
644
+
645
+ **Package Status**: Stable
646
+ **PyPI**: https://pypi.org/project/dfm-python/
647
+ **Python**: 3.12+