dfm-python 0.2.9__tar.gz → 0.3.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 (102) hide show
  1. {dfm_python-0.2.9 → dfm_python-0.3.0}/.gitignore +3 -1
  2. {dfm_python-0.2.9 → dfm_python-0.3.0}/PKG-INFO +423 -49
  3. {dfm_python-0.2.9 → dfm_python-0.3.0}/README.md +420 -47
  4. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +29 -0
  5. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +29 -0
  6. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +29 -0
  7. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +29 -0
  8. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +29 -0
  9. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +29 -0
  10. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +29 -0
  11. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +29 -0
  12. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +29 -0
  13. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +29 -0
  14. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +29 -0
  15. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +29 -0
  16. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +29 -0
  17. dfm_python-0.3.0/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +29 -0
  18. dfm_python-0.3.0/config/generated/series/spec_20251118_194423_013beb_series.yaml +749 -0
  19. dfm_python-0.3.0/config/generated/series/spec_20251118_194532_190712_series.yaml +749 -0
  20. dfm_python-0.3.0/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +749 -0
  21. dfm_python-0.3.0/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +749 -0
  22. dfm_python-0.3.0/config/generated/series/spec_20251118_195439_12eea1_series.yaml +749 -0
  23. dfm_python-0.3.0/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +749 -0
  24. dfm_python-0.3.0/config/generated/series/spec_20251118_200209_8b6463_series.yaml +749 -0
  25. dfm_python-0.3.0/config/generated/series/spec_20251118_200319_c62fea_series.yaml +749 -0
  26. dfm_python-0.3.0/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +749 -0
  27. dfm_python-0.3.0/config/generated/series/spec_20251118_200838_320d08_series.yaml +749 -0
  28. dfm_python-0.3.0/config/generated/series/spec_20251118_200934_65c460_series.yaml +749 -0
  29. dfm_python-0.3.0/config/generated/series/spec_20251118_201046_8e7482_series.yaml +749 -0
  30. dfm_python-0.3.0/config/generated/spec_20251118_194140_025b98.yaml +29 -0
  31. dfm_python-0.3.0/config/generated/spec_20251118_194248_82a2a3.yaml +29 -0
  32. dfm_python-0.3.0/config/generated/spec_20251118_194423_013beb.yaml +29 -0
  33. dfm_python-0.3.0/config/generated/spec_20251118_194532_190712.yaml +29 -0
  34. dfm_python-0.3.0/config/generated/spec_20251118_195001_ef69d1.yaml +29 -0
  35. dfm_python-0.3.0/config/generated/spec_20251118_195110_eb12bc.yaml +29 -0
  36. dfm_python-0.3.0/config/generated/spec_20251118_195439_12eea1.yaml +29 -0
  37. dfm_python-0.3.0/config/generated/spec_20251118_195547_0ecebd.yaml +29 -0
  38. dfm_python-0.3.0/config/generated/spec_20251118_200209_8b6463.yaml +29 -0
  39. dfm_python-0.3.0/config/generated/spec_20251118_200319_c62fea.yaml +29 -0
  40. dfm_python-0.3.0/config/generated/spec_20251118_200722_7d6a26.yaml +29 -0
  41. dfm_python-0.3.0/config/generated/spec_20251118_200838_320d08.yaml +29 -0
  42. dfm_python-0.3.0/config/generated/spec_20251118_200934_65c460.yaml +29 -0
  43. dfm_python-0.3.0/config/generated/spec_20251118_201046_8e7482.yaml +29 -0
  44. dfm_python-0.3.0/config/series/default.yaml +749 -0
  45. dfm_python-0.3.0/config/series/sample_spec.yaml +749 -0
  46. dfm_python-0.3.0/full_tutorial_example.ipynb +2223 -0
  47. {dfm_python-0.2.9 → dfm_python-0.3.0}/pyproject.toml +3 -2
  48. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/adapters/__init__.py +3 -1
  49. dfm_python-0.3.0/src/adapters/data_view.py +230 -0
  50. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/adapters/database.py +15 -9
  51. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/__init__.py +10 -8
  52. dfm_python-0.3.0/src/dfm_python/api.py +1071 -0
  53. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/config.py +5 -70
  54. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/config_sources.py +148 -115
  55. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/core/diagnostics.py +39 -30
  56. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/core/em.py +72 -52
  57. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/core/helpers.py +307 -44
  58. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/core/numeric.py +2 -2
  59. dfm_python-0.3.0/src/dfm_python/core/time.py +775 -0
  60. {dfm_python-0.2.9/src/dfm_python → dfm_python-0.3.0/src/dfm_python/core}/utils.py +68 -3
  61. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/data.py +256 -52
  62. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/dfm.py +67 -25
  63. dfm_python-0.3.0/src/dfm_python/nowcast.py +1519 -0
  64. dfm_python-0.3.0/src/test/test_api.py +466 -0
  65. dfm_python-0.3.0/src/test/test_core.py +339 -0
  66. dfm_python-0.3.0/src/test/test_integration.py +183 -0
  67. dfm_python-0.3.0/src/test/test_nowcasting.py +1186 -0
  68. dfm_python-0.3.0/tutorial/basic_tutorial.py +1069 -0
  69. {dfm_python-0.2.9 → dfm_python-0.3.0}/uv.lock +625 -3
  70. dfm_python-0.2.9/.env.example +0 -0
  71. dfm_python-0.2.9/full_tutorial_example.ipynb +0 -2014
  72. dfm_python-0.2.9/src/adapters/data_view.py +0 -156
  73. dfm_python-0.2.9/src/dfm_python/api.py +0 -439
  74. dfm_python-0.2.9/src/dfm_python/news.py +0 -774
  75. dfm_python-0.2.9/src/test/__init__.py +0 -1256
  76. dfm_python-0.2.9/src/test/test_api.py +0 -705
  77. dfm_python-0.2.9/src/test/test_config.py +0 -696
  78. dfm_python-0.2.9/src/test/test_dfm.py +0 -1398
  79. dfm_python-0.2.9/src/test/test_factor.py +0 -118
  80. dfm_python-0.2.9/src/test/test_numeric.py +0 -478
  81. dfm_python-0.2.9/src/test/test_stability.py +0 -222
  82. dfm_python-0.2.9/tutorial/basic_tutorial.py +0 -605
  83. dfm_python-0.2.9/tutorial/hydra_tutorial.py +0 -257
  84. {dfm_python-0.2.9 → dfm_python-0.3.0}/.python-version +0 -0
  85. {dfm_python-0.2.9 → dfm_python-0.3.0}/LICENSE +0 -0
  86. {dfm_python-0.2.9 → dfm_python-0.3.0}/MANIFEST.in +0 -0
  87. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/blocks/default.yaml +0 -0
  88. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/blocks/sample_spec.yaml +0 -0
  89. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/blocks/sample_spec2.yaml +0 -0
  90. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/default.yaml +0 -0
  91. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/experiment/exp1.yaml +0 -0
  92. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/experiment/exp2.yaml +0 -0
  93. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/experiment/exp3.yaml +0 -0
  94. /dfm_python-0.2.9/config/series/default.yaml → /dfm_python-0.3.0/config/generated/series/spec_20251118_194140_025b98_series.yaml +0 -0
  95. /dfm_python-0.2.9/config/series/sample_spec.yaml → /dfm_python-0.3.0/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +0 -0
  96. {dfm_python-0.2.9 → dfm_python-0.3.0}/config/series/sample_spec2.yaml +0 -0
  97. {dfm_python-0.2.9 → dfm_python-0.3.0}/data/sample_data.csv +0 -0
  98. {dfm_python-0.2.9 → dfm_python-0.3.0}/data/sample_spec.csv +0 -0
  99. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/adapters/model_result.py +0 -0
  100. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/core/__init__.py +0 -0
  101. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/dfm_python/kalman.py +0 -0
  102. {dfm_python-0.2.9 → dfm_python-0.3.0}/src/test/test_kalman.py +0 -0
@@ -212,4 +212,6 @@ __marimo__/
212
212
  *.sh
213
213
 
214
214
  Nowcasting/
215
- agent/
215
+ agent/
216
+
217
+ *.db
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.2.9
3
+ Version: 0.3.0
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
@@ -22,8 +22,9 @@ Requires-Python: >=3.10
22
22
  Requires-Dist: hydra-core>=1.3.2
23
23
  Requires-Dist: matplotlib>=3.5.0
24
24
  Requires-Dist: numpy>=1.24.0
25
- Requires-Dist: pandas>=2.0.0
25
+ Requires-Dist: polars>=0.20.0
26
26
  Requires-Dist: scipy>=1.10.0
27
+ Requires-Dist: twine>=6.2.0
27
28
  Provides-Extra: all
28
29
  Requires-Dist: hydra-core>=1.3.0; extra == 'all'
29
30
  Requires-Dist: omegaconf>=2.3.0; extra == 'all'
@@ -55,13 +56,13 @@ A Dynamic Factor Model is a powerful statistical framework that:
55
56
  - **Provides nowcasts** (current-quarter GDP estimates before official release)
56
57
  - **Forecasts** future values of both factors and observed series
57
58
 
58
- **Key Innovation**: All latent factors evolve at a common "clock" frequency (e.g., monthly), while observed series can be at different frequencies. This is achieved through deterministic tent kernel aggregation, allowing quarterly GDP to be modeled using monthly latent factors.
59
+ **Key Innovation**: All latent factors evolve at a common "clock" frequency (configurable: `'d'`, `'w'`, `'m'`, `'q'`, `'sa'`, `'a'`), while observed series can be at different frequencies. This is achieved through deterministic tent kernel aggregation, allowing quarterly GDP to be modeled using monthly latent factors, or any slower-frequency series to be modeled using higher-frequency latent factors.
59
60
 
60
61
  ## Features
61
62
 
62
63
  ### Core Capabilities
63
64
  - ✅ **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual series in one model
64
- - ✅ **Clock-based framework**: All factors evolve at a common clock frequency (typically monthly)
65
+ - ✅ **Clock-based framework**: All factors evolve at a common clock frequency (configurable: daily, weekly, monthly, quarterly, semi-annual, annual)
65
66
  - ✅ **Block structure**: Flexible factor organization (global common factor + sector-specific factors)
66
67
  - ✅ **Idiosyncratic components**: Per-series state augmentation for better fit (AR(1) for clock-frequency, tent-length chains for slower frequencies)
67
68
  - ✅ **Robust missing data**: Spline interpolation preprocessing + Kalman filter handling during estimation
@@ -88,7 +89,7 @@ pip install dfm-python
88
89
  **Requirements**:
89
90
  - Python >= 3.10
90
91
  - numpy >= 1.24.0
91
- - pandas >= 2.0.0
92
+ - polars >= 0.20.0
92
93
  - scipy >= 1.10.0
93
94
 
94
95
  Optional dependencies:
@@ -102,7 +103,8 @@ Optional dependencies:
102
103
  ```python
103
104
  import dfm_python as dfm
104
105
  import numpy as np
105
- import pandas as pd
106
+ from datetime import datetime
107
+ import polars as pl
106
108
 
107
109
  # Generate or load your data (T × N: time periods × series)
108
110
  # Data should be standardized or will be standardized automatically
@@ -126,7 +128,7 @@ result = model.fit(X, config, max_iter=100, threshold=1e-4)
126
128
  factors = result.Z # (101 × 1) Smoothed common factor
127
129
  loadings = result.C # (10 × 1) Factor loadings
128
130
  smoothed_data = result.X_sm # (100 × 10) Smoothed series
129
- forecasts = result.forecast(horizon=12) # 12-step ahead forecasts
131
+ forecasts = result.forecast(horizon=None) # Default: 1 year of periods based on clock frequency
130
132
  ```
131
133
 
132
134
  ### Example 2: Mixed-Frequency Data (Monthly + Quarterly)
@@ -160,8 +162,8 @@ dfm.load_data('data/mixed_frequency_data.csv', sample_start='2000-01-01')
160
162
  # Train
161
163
  dfm.train(max_iter=5000, threshold=1e-5)
162
164
 
163
- # Forecast
164
- X_forecast, Z_forecast = dfm.predict(horizon=12)
165
+ # Forecast (horizon=None defaults to 1 year of periods based on clock frequency)
166
+ X_forecast, Z_forecast = dfm.predict(horizon=None) # Or specify: horizon=12
165
167
  ```
166
168
 
167
169
  ### Example 3: Using Spec CSV (Recommended for CSV Workflows)
@@ -169,6 +171,7 @@ X_forecast, Z_forecast = dfm.predict(horizon=12)
169
171
  ```python
170
172
  import dfm_python as dfm
171
173
  from dfm_python.config import Params
174
+ import polars as pl
172
175
 
173
176
  # 1. Create spec CSV (see Data Format section)
174
177
  # 2. Load configuration
@@ -179,7 +182,8 @@ params = Params(
179
182
  augment_idio=True, # Enable idio components
180
183
  idio_rho0=0.1 # Initial AR coefficient for idio
181
184
  )
182
- dfm.from_spec('data/spec.csv', params=params)
185
+ spec_df = pl.read_csv('data/spec.csv')
186
+ dfm.from_spec_df(spec_df, params=params)
183
187
 
184
188
  # 3. Load data
185
189
  dfm.load_data('data/data.csv', sample_start='2020-01-01', sample_end='2023-12-31')
@@ -187,9 +191,9 @@ dfm.load_data('data/data.csv', sample_start='2020-01-01', sample_end='2023-12-31
187
191
  # 4. Train
188
192
  dfm.train()
189
193
 
190
- # 5. Forecast and visualize
191
- X_forecast, Z_forecast = dfm.predict(horizon=12)
192
- dfm.plot(kind='factor', factor_index=0, forecast_horizon=12, save_path='factor_forecast.png')
194
+ # 5. Forecast and visualize (horizon=None defaults to 1 year of periods)
195
+ X_forecast, Z_forecast = dfm.predict(horizon=None) # Or specify: horizon=12
196
+ dfm.plot(kind='factor', factor_index=0, forecast_horizon=None, save_path='factor_forecast.png')
193
197
 
194
198
  # 6. Access results
195
199
  result = dfm.get_result()
@@ -197,6 +201,47 @@ print(f"Converged: {result.converged}, Iterations: {result.num_iter}")
197
201
  print(f"Log-likelihood: {result.loglik:.2f}")
198
202
  ```
199
203
 
204
+ ### Example 4: Fast Reuse with `load_pickle`
205
+
206
+ Once a model has been trained you can reload it instantly for forecasting/nowcasting without
207
+ re-running EM:
208
+
209
+ ```python
210
+ import dfm_python as dfm
211
+ import pickle
212
+ from pathlib import Path
213
+
214
+ # After training
215
+ outputs_dir = Path("outputs")
216
+ outputs_dir.mkdir(parents=True, exist_ok=True)
217
+ save_path = outputs_dir / "model_result.pkl"
218
+ payload = {
219
+ 'result': dfm.get_result(),
220
+ 'config': dfm.get_config(),
221
+ }
222
+ with open(save_path, 'wb') as f:
223
+ pickle.dump(payload, f) # Or rely on the auto-save printed after train()
224
+
225
+ # Later (e.g., in production job)
226
+ model = dfm.DFM()
227
+ model.load_pickle(
228
+ save_path,
229
+ data=dfm.get_data(), # Or model.load_data(...)
230
+ time_index=dfm.get_time(), # Optional if stored in pickle
231
+ )
232
+
233
+ # Fast inference (milliseconds instead of minutes)
234
+ X_forecast, Z_forecast = model.predict(horizon=12)
235
+ gdp_nowcast = model.nowcast(
236
+ target_series='gdp',
237
+ view_date='2024-03-15'
238
+ )
239
+ ```
240
+
241
+ `DFM.load_pickle()` accepts either the auto-save payload (`{'result': ..., 'config': ...}`) or a
242
+ raw `DFMResult`. Provide `load_data_path` or `data`/`time_index` when you need nowcasting/news
243
+ decomposition abilities (which require the data view).
244
+
200
245
  ## Configuration Guide
201
246
 
202
247
  ### Configuration Methods
@@ -209,6 +254,7 @@ The package supports multiple ways to configure your model:
209
254
 
210
255
  ```python
211
256
  from dfm_python.config import Params
257
+ import polars as pl
212
258
 
213
259
  params = Params(
214
260
  max_iter=5000,
@@ -219,7 +265,8 @@ params = Params(
219
265
  idio_rho0=0.1,
220
266
  idio_min_var=1e-8
221
267
  )
222
- dfm.from_spec('data/spec.csv', params=params)
268
+ spec_df = pl.read_csv('data/spec.csv')
269
+ dfm.from_spec_df(spec_df, params=params)
223
270
  ```
224
271
 
225
272
  #### 2. YAML Configuration
@@ -250,7 +297,9 @@ def main(cfg: DictConfig) -> None:
250
297
  dfm.load_config(hydra=cfg)
251
298
  dfm.load_data(str(get_original_cwd() / "data" / "sample_data.csv"))
252
299
  dfm.train(max_iter=cfg.max_iter)
253
- X_forecast, Z_forecast = dfm.predict(horizon=cfg.get('forecast_horizon', 12))
300
+ # horizon=None defaults to 1 year of periods based on clock frequency
301
+ forecast_horizon = cfg.get('forecast_horizon', None)
302
+ X_forecast, Z_forecast = dfm.predict(horizon=forecast_horizon)
254
303
 
255
304
  if __name__ == "__main__":
256
305
  main()
@@ -282,6 +331,14 @@ config = DFMConfig(
282
331
  - **`clock`**: Base frequency for all latent factors (default: `'m'` for monthly)
283
332
  - Options: `'d'` (daily), `'w'` (weekly), `'m'` (monthly), `'q'` (quarterly), `'sa'` (semi-annual), `'a'` (annual)
284
333
  - All factors evolve at this frequency, regardless of observation frequencies
334
+ - **Generic support**: The package automatically adapts forecast horizons, period calculations, and date handling based on the clock frequency
335
+ - **Default behavior**: When `horizon=None` in `predict()`, defaults to 1 year of periods based on clock frequency:
336
+ - Monthly (`'m'`): 12 periods
337
+ - Quarterly (`'q'`): 4 periods
338
+ - Semi-annual (`'sa'`): 2 periods
339
+ - Annual (`'a'`): 1 period
340
+ - Daily (`'d'`): 365 periods (approximate)
341
+ - Weekly (`'w'`): 52 periods (approximate)
285
342
 
286
343
  - **`blocks`**: Factor block structure
287
344
  - `Block_Global`: Common factor affecting all series
@@ -434,28 +491,227 @@ idio_component = result.X_sm[:, series_idx] - series_factor_component
434
491
 
435
492
  ## Advanced Features
436
493
 
437
- ### News Decomposition
494
+ ### Nowcasting API
495
+
496
+ The package provides a comprehensive nowcasting API for pseudo real-time evaluation and downstream model integration:
497
+
498
+ #### Basic Nowcasting
499
+
500
+ ```python
501
+ import dfm_python as dfm
502
+ from dfm_python import Nowcast
503
+
504
+ # Load config and data, train model
505
+ dfm.from_yaml('config/default.yaml')
506
+ dfm.load_data('data/data.csv')
507
+ dfm.train()
508
+
509
+ # Get Nowcast instance
510
+ model = dfm.DFM()
511
+ nowcast = model.nowcast # or: nowcast = Nowcast(model)
512
+
513
+ # Calculate nowcast (simple callable interface)
514
+ value = nowcast('gdp', view_date='2024-01-15', target_period='2024Q1')
515
+ print(f"Nowcast value: {value:.4f}")
516
+
517
+ # Or get full result with metadata
518
+ result = nowcast('gdp', view_date='2024-01-15', target_period='2024Q1', return_result=True)
519
+ print(f"Nowcast: {result.nowcast_value:.4f}")
520
+ print(f"Data availability: {result.data_availability}")
521
+ print(f"Factors at view: {result.factors_at_view}")
522
+ ```
523
+
524
+ #### News Decomposition
438
525
 
439
526
  Decompose forecast updates into contributions from individual data releases:
440
527
 
441
528
  ```python
442
- from dfm_python import news_dfm
529
+ # News decomposition between two data views
530
+ news = nowcast.decompose(
531
+ target_series='gdp',
532
+ target_period='2024Q1',
533
+ view_date_old='2024-01-15',
534
+ view_date_new='2024-02-15'
535
+ )
536
+
537
+ # Access results (NewsDecompResult)
538
+ print(f"Old forecast: {news.y_old:.4f}")
539
+ print(f"New forecast: {news.y_new:.4f}")
540
+ print(f"Change: {news.change:.4f}")
541
+ print(f"Top contributors: {news.top_contributors}")
542
+
543
+ # Or get dictionary format (backward compatibility)
544
+ news_dict = nowcast.decompose(
545
+ target_series='gdp',
546
+ target_period='2024Q1',
547
+ view_date_old='2024-01-15',
548
+ view_date_new='2024-02-15',
549
+ return_dict=True
550
+ )
551
+ ```
552
+
553
+ #### Pseudo Real-Time Backtesting
554
+
555
+ Perform backtesting to evaluate model performance in pseudo real-time:
556
+
557
+ ```python
558
+ from datetime import datetime
559
+
560
+ # Perform backtest
561
+ backtest_result = nowcast.backtest(
562
+ target_series='gdp',
563
+ target_date='2024Q4',
564
+ backward_steps=20, # 20 backward steps
565
+ higher_freq=True, # Use frequency one step faster than clock
566
+ include_actual=True # Compare with actual values
567
+ )
568
+
569
+ # Access metrics
570
+ print(f"Overall RMSE: {backtest_result.overall_rmse:.4f}")
571
+ print(f"Overall MAE: {backtest_result.overall_mae:.4f}")
572
+ print(f"Failed steps: {backtest_result.failed_steps}")
573
+
574
+ # Access point-wise metrics
575
+ print(f"RMSE per step: {backtest_result.rmse_per_step}")
576
+ print(f"MAE per step: {backtest_result.mae_per_step}")
577
+
578
+ # Access individual nowcast results
579
+ for i, nowcast_result in enumerate(backtest_result.nowcast_results):
580
+ print(f"Step {i}: nowcast={nowcast_result.nowcast_value:.4f}, "
581
+ f"view_date={nowcast_result.view_date}")
582
+
583
+ # Access news decomposition between steps
584
+ for i, news_result in enumerate(backtest_result.news_results):
585
+ if news_result is not None:
586
+ print(f"Step {i}: forecast change={news_result.change:.4f}")
587
+
588
+ # Visualize results
589
+ backtest_result.plot(save_path='backtest_results.png', show=False)
590
+ ```
591
+
592
+ #### Pseudo Real-Time Evaluation (Alternative Method)
593
+
594
+ Generate datasets for model evaluation across multiple periods:
595
+
596
+ ```python
597
+ from datetime import datetime
598
+
599
+ # Define evaluation periods
600
+ from dfm_python.core.time import datetime_range
601
+ periods = datetime_range(start=datetime(2020, 1, 1), end=datetime(2023, 12, 31), freq='QE')
602
+
603
+ # Generate evaluation dataset
604
+ dataset = model.generate_dataset(
605
+ target_series='gdp',
606
+ periods=periods,
607
+ backward=4, # 4 backward data views per period
608
+ forward=0 # No forward forecast for evaluation
609
+ )
610
+
611
+ # Access features and targets
612
+ X_features = dataset['X'] # (n_samples, n_features)
613
+ y_baseline = dataset['y_baseline'] # DFM baseline predictions
614
+ y_actual = dataset['y_actual'] # Actual values
615
+ y_target = dataset['y_target'] # Prediction error (for downstream models)
616
+ ```
617
+
618
+ ### Model Result Storage
619
+
620
+ Save and load model results for later use:
621
+
622
+ #### File-Based Storage (Default)
623
+
624
+ ```python
625
+ from adapters import PickleModelResultSaver
626
+
627
+ # Initialize saver (defaults to './model_results')
628
+ saver = PickleModelResultSaver(base_dir='./model_results')
629
+
630
+ # Save model result
631
+ result_id = saver.save_model_result(
632
+ result=dfm.get_result(),
633
+ config=dfm.get_config(),
634
+ metadata={'tag': 'baseline', 'experiment': 'exp1'}
635
+ )
636
+
637
+ # Load model result
638
+ result, config, metadata = saver.load_model_result(result_id)
639
+
640
+ # List all saved results
641
+ result_ids = saver.list_model_results()
642
+ ```
643
+
644
+ #### SQLite Storage (Optional - Future Feature)
645
+
646
+ SQLite storage is planned for future releases. Currently, file-based storage is fully functional and recommended for production use.
647
+
648
+ ```python
649
+ # Note: SQLiteAdapter is a placeholder for future development
650
+ # File-based storage (PickleModelResultSaver) is the recommended approach
651
+
652
+ # When SQLite support is available (future release):
653
+ # from adapters import SQLiteAdapter
654
+ # adapter = SQLiteAdapter(database_path='./dfm_results.db')
655
+ # result_id = adapter.save_model_result(result, config, metadata)
656
+ # result, config, metadata = adapter.load_model_result(result_id)
657
+ ```
658
+
659
+ ### Data View Management
660
+
661
+ Manage time-point specific data views for pseudo real-time evaluation:
662
+
663
+ #### File-Based Data Views (Default)
664
+
665
+ ```python
666
+ from adapters import BasicDataViewManager
667
+
668
+ # Initialize with data
669
+ X, Time, Z = dfm.get_data(), dfm.get_time(), dfm.get_original_data()
670
+ manager = BasicDataViewManager(data_source=(X, Time, Z))
671
+
672
+ # Get data view at specific date
673
+ X_view, Time_view, Z_view = manager.get_data_view(
674
+ view_date='2024-01-15',
675
+ config=dfm.get_config()
676
+ )
677
+
678
+ # Data view filters data based on release_date in SeriesConfig
679
+ # Series with release_date > view_date are masked (set to NaN)
680
+ ```
681
+
682
+ #### SQLite Data Views (Optional - Future Feature)
683
+
684
+ SQLite data view storage is planned for future releases. Currently, `BasicDataViewManager` provides in-memory data views with caching, which is sufficient for most use cases.
685
+
686
+ ```python
687
+ # Note: SQLite data view storage is a placeholder for future development
688
+ # BasicDataViewManager with in-memory caching is the recommended approach
689
+
690
+ # When SQLite support is available (future release):
691
+ # from adapters import SQLiteAdapter
692
+ # adapter = SQLiteAdapter(database_path='./dfm_data.db')
693
+ # view_id = adapter.save_data_view(view_date, X_view, Time_view, config)
694
+ # X_view, Time_view, config = adapter.load_data_view(view_id)
695
+ ```
696
+
697
+ ### News Decomposition (Legacy API)
698
+
699
+ For advanced users, the low-level `news_dfm` function is still available:
700
+
701
+ ```python
702
+ from dfm_python.nowcast import para_const
443
703
 
444
704
  # Before new data release
445
705
  result_old = model.fit(X_old, config)
446
706
 
447
707
  # After new data release
448
708
  X_new = ... # Updated data
449
- result_new = model.fit(X_new, config)
450
709
 
451
- # Decompose forecast update
452
- y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
453
- X_old, result_old, t_fcst=100, v_news=0
454
- )
710
+ # Use para_const for news calculation
711
+ Res_old = para_const(X_old, result_old, lag=1)
712
+ # ... (see nowcast.py for full implementation)
455
713
 
456
- # Forecast update
457
- forecast_update = y_new - y_old
458
- # singlenews contains contribution from each series
714
+ # Recommended: Use Nowcast class instead (see Nowcasting API section above)
459
715
  ```
460
716
 
461
717
  ### Custom Block Structure
@@ -551,9 +807,11 @@ config = DFMConfig(series=series, blocks=blocks)
551
807
 
552
808
  ```python
553
809
  import dfm_python as dfm
810
+ import polars as pl
554
811
 
555
812
  # Configuration
556
- dfm.from_spec(spec_path, params=None) # Load from spec CSV + Params
813
+ spec_df = pl.read_csv(spec_path)
814
+ dfm.from_spec_df(spec_df, params=None) # Convert spec DataFrame to YAML + load
557
815
  dfm.from_yaml(yaml_path) # Load from YAML file
558
816
  dfm.from_dict(config_dict) # Load from dictionary
559
817
  dfm.load_config(hydra=cfg) # Load from Hydra DictConfig
@@ -564,12 +822,19 @@ dfm.load_data(data_path, sample_start=None, sample_end=None)
564
822
  # Estimation
565
823
  dfm.train(max_iter=None, threshold=None, **kwargs)
566
824
 
567
- # Forecasting
568
- dfm.predict(horizon=12) # Returns (X_forecast, Z_forecast)
825
+ # Forecasting (horizon=None defaults to 1 year of periods based on clock frequency)
826
+ dfm.predict(horizon=None) # Returns (X_forecast, Z_forecast)
827
+ # Or specify explicit horizon: dfm.predict(horizon=12)
569
828
 
570
829
  # Visualization
571
830
  dfm.plot(kind='factor', factor_index=0, forecast_horizon=None, save_path=None)
572
831
 
832
+ # Nowcasting
833
+ model = dfm.DFM()
834
+ nowcast = model.nowcast # Get Nowcast instance
835
+ value = nowcast('gdp', view_date='2024-01-15') # Simple nowcast
836
+ result = nowcast.backtest('gdp', '2024Q4', backward_steps=20) # Backtesting
837
+
573
838
  # Results
574
839
  dfm.get_result() # Returns DFMResult object
575
840
  dfm.reset() # Reset singleton state
@@ -622,25 +887,34 @@ Complete reference for `DFMResult`:
622
887
  - **`dfm.py`**: Core estimation (`DFM` class, `_dfm_core` function, EM algorithm orchestration)
623
888
  - **`core/em.py`**: EM algorithm implementation (`init_conditions`, `em_step`, `em_converged`)
624
889
  - **`core/numeric.py`**: Numerical utilities (covariance computation, regularization, stability)
890
+ - **`core/helpers.py`**: Helper functions (datetime conversion, time index extraction, config access)
891
+ - **`core/timestamp.py`**: Timestamp utilities (datetime operations, frequency mapping, period calculations)
625
892
  - **`kalman.py`**: Kalman filter and smoother (`run_kf`, `skf`, `fis`)
626
893
  - **`data.py`**: Data loading and preprocessing (`load_data`, `transform_data`, `rem_nans_spline`)
627
- - **`utils.py`**: Mixed-frequency utilities (tent weights, aggregation structure, idio chain lengths)
894
+ - **`utils.py`**: Mixed-frequency utilities (tent weights, aggregation structure, idio chain lengths, frequency helpers)
628
895
  - **`api.py`**: High-level convenience API and module-level functions
629
- - **`news.py`**: News decomposition
896
+ - **`nowcast.py`**: Nowcasting, news decomposition, and backtesting functionality
897
+ - `Nowcast` class: Unified interface for nowcasting operations
898
+ - `NowcastResult`, `NewsDecompResult`, `BacktestResult`: Structured result dataclasses
899
+ - `backtest()`: Pseudo real-time evaluation framework
630
900
 
631
901
  ### Key Design Principles
632
902
 
633
- 1. **Clock-based synchronization**: All factors evolve at a common frequency
903
+ 1. **Clock-based synchronization**: All factors evolve at a common frequency (generic support for any frequency)
634
904
  2. **Tent kernel aggregation**: Deterministic weights for slower-frequency series
635
905
  3. **Idiosyncratic components**: Per-series state augmentation for better fit
636
- 4. **Advanced numerical stability**:
906
+ 4. **Generic frequency handling**:
907
+ - No hardcoded assumptions about specific frequencies (e.g., monthly)
908
+ - All period calculations, forecast horizons, and date operations adapt to clock frequency
909
+ - Helper functions provide generic frequency operations (`get_periods_per_year()`, `get_annual_factor()`, `clock_to_datetime_freq()`)
910
+ 5. **Advanced numerical stability**:
637
911
  - Adaptive ridge regularization (condition-number-based)
638
912
  - Q matrix floor (0.01 for factors) to prevent scale issues
639
913
  - C matrix normalization (||C[:,j]|| = 1) for clock-frequency factors
640
914
  - Spectral radius capping (< 0.99) for stationarity
641
915
  - Variance floors for all covariance matrices
642
- 5. **Flexible configuration**: Multiple input methods (YAML, CSV, Dict, Hydra)
643
- 6. **Backward compatibility**: Old code continues to work, new features are opt-in
916
+ 6. **Flexible configuration**: Multiple input methods (YAML, CSV, Dict, Hydra)
917
+ 7. **Backward compatibility**: Old code continues to work, new features are opt-in
644
918
 
645
919
  ## Testing
646
920
 
@@ -677,38 +951,138 @@ pytest src/test/test_dfm.py -k "idio" -v
677
951
 
678
952
  ## Tutorials
679
953
 
680
- ### Basic Tutorial (Spec CSV Approach)
954
+ ### Basic Tutorial (Hydra-based)
955
+
956
+ The tutorial demonstrates the complete DFM workflow using Hydra for configuration management:
681
957
 
682
958
  ```bash
683
- python tutorial/basic_tutorial.py --spec data/sample_spec.csv --data data/sample_data.csv
959
+ # Using default YAML config
960
+ python tutorial/basic_tutorial.py \
961
+ --config-path config \
962
+ --config-name default \
963
+ data_path=data/sample_data.csv
964
+
965
+ # Convert spec CSV to YAML first, then use
966
+ python -c "import dfm_python as dfm; dfm.from_spec('data/sample_spec.csv')"
967
+ python tutorial/basic_tutorial.py \
968
+ --config-path config \
969
+ --config-name sample_spec \
970
+ data_path=data/sample_data.csv
971
+
972
+ # With CLI overrides
973
+ python tutorial/basic_tutorial.py \
974
+ --config-path config \
975
+ --config-name default \
976
+ data_path=data/sample_data.csv \
977
+ max_iter=10 \
978
+ threshold=1e-4 \
979
+ blocks.Block_Global.factors=2
684
980
  ```
685
981
 
686
982
  Comprehensive tutorial covering:
687
- - Loading configuration from spec CSV
688
- - Data preparation and transformation
689
- - Model training and convergence
690
- - Interpreting results
691
- - Forecasting and visualization
983
+ - **Hydra-based configuration**: All configs managed through YAML files
984
+ - **Spec CSV conversion**: Convert CSV to YAML using `dfm.from_spec()`, then load via Hydra
985
+ - **CLI parameter overrides**: Override any parameter via command line
986
+ - **Data preparation and transformation**
987
+ - **Model training and convergence**
988
+ - **Understanding DFM results**: Detailed explanation of all result components
989
+ - **Forecasting and visualization**
990
+ - **Model result storage**: Save/load model results
991
+ - **Data view management**: Pseudo real-time evaluation
992
+
993
+ ## Storage and Adapters
994
+
995
+ The package provides flexible storage adapters for model results and data views:
996
+
997
+ ### Model Result Storage
998
+
999
+ - **File-based (default)**: `PickleModelResultSaver` - Saves to pickle files in `./model_results/`
1000
+ - Fully functional and recommended for production use
1001
+ - No additional dependencies required
1002
+ - **SQLite (optional - future)**: `SQLiteAdapter` - Planned for future releases
1003
+ - Currently a placeholder for future development
1004
+ - Will require `dfm-python[db]` installation when available
1005
+
1006
+ ### Data View Management
1007
+
1008
+ - **File-based (default)**: `BasicDataViewManager` - In-memory data views with caching
1009
+ - Fully functional and recommended for production use
1010
+ - Efficient caching for repeated access
1011
+ - No additional dependencies required
1012
+ - **SQLite (optional - future)**: `SQLiteAdapter` - Planned for future releases
1013
+ - Currently a placeholder for future development
1014
+ - Will provide persistent data view storage when available
1015
+
1016
+ ### Environment Variables
692
1017
 
693
- ### Hydra Tutorial (Advanced Configuration)
1018
+ For database integration, set these environment variables:
694
1019
 
695
1020
  ```bash
696
- python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4
1021
+ # Database configuration
1022
+ DATABASE_TYPE=sqlite # or postgresql
1023
+ DATABASE_PATH=./dfm_results.db # For SQLite
1024
+ # For PostgreSQL:
1025
+ # DATABASE_HOST=localhost
1026
+ # DATABASE_PORT=5432
1027
+ # DATABASE_NAME=dfm_db
1028
+ # DATABASE_USER=user
1029
+ # DATABASE_PASSWORD=password
1030
+
1031
+ # Storage type selection
1032
+ MODEL_RESULT_STORAGE_TYPE=file # or database
1033
+ DATA_VIEW_SOURCE=file # or database
697
1034
  ```
698
1035
 
699
- Demonstrates:
700
- - Hydra-based configuration management
701
- - CLI parameter overrides
702
- - Complex multi-block configurations
1036
+ **Note**: File-based storage works out of the box with no configuration needed. Database storage requires `dfm-python[db]` installation and environment variable configuration.
703
1037
 
704
1038
  ## Project Status
705
1039
 
706
- **Version**: 0.2.7
1040
+ **Version**: 0.3.0
707
1041
  **Status**: Stable and production-ready
708
1042
  **Python**: 3.10+
709
1043
  **PyPI**: https://pypi.org/project/dfm-python/
710
1044
 
711
- ### Recent Improvements (v0.2.7)
1045
+ ### Recent Improvements (v0.3.0)
1046
+
1047
+ - ✅ **Polars Migration**: Core data processing migrated to Polars for improved performance and modern data manipulation
1048
+ - ✅ **Polars Data Views + Kalman Cache (v0.3.1)**:
1049
+ - `create_data_view()` now uses Polars masking and avoids per-view numpy copies
1050
+ - `DFM.load_data()` stores a Polars view for reuse; `Nowcast` reuses cached Kalman states
1051
+ - Enable `logging.getLogger('dfm_python.nowcast').setLevel('DEBUG')` to see per-view timing (data view vs. Kalman)
1052
+ - ✅ **Full Nowcast Implementation**: Complete nowcasting API with `nowcast.py` module consolidating all nowcasting and news decomposition functionality
1053
+ - ✅ **Backtesting Implementation**: Pseudo real-time evaluation framework for model validation
1054
+ - `Nowcast.backtest()` method for comprehensive backtesting with point-wise metrics
1055
+ - `BacktestResult` dataclass with visualization capabilities
1056
+ - Support for higher-frequency backward steps and flexible date handling
1057
+ - Automatic error handling and failed step tracking
1058
+ - ✅ **Enhanced Nowcast API**:
1059
+ - `NowcastResult` and `NewsDecompResult` dataclasses for structured results
1060
+ - `return_result` parameter in `__call__` method for full metadata
1061
+ - Improved date parsing and error handling
1062
+ - ✅ **Module Reorganization**: `news.py` functionality integrated into `nowcast.py` for clearer module structure
1063
+ - All nowcasting functions now available via `from dfm_python.nowcast import ...`
1064
+ - `news.py` module removed (functionality fully migrated to `nowcast.py`)
1065
+
1066
+ ### Previous Improvements (v0.2.9)
1067
+
1068
+ - ✅ **Generic Clock Frequency Support**: Full support for any clock frequency (daily, weekly, monthly, quarterly, semi-annual, annual)
1069
+ - Forecast horizons default to 1 year of periods based on clock frequency (generic, not hardcoded)
1070
+ - All period calculations and date handling are clock-frequency-agnostic
1071
+ - Helper functions for generic frequency operations (`get_periods_per_year()`, `get_annual_factor()`, `clock_to_datetime_freq()`)
1072
+ - ✅ **Nowcasting API**: Comprehensive nowcasting API with `nowcast()`, `generate_dataset()`, and `get_state()` methods
1073
+ - ✅ **Model Result Storage**: File-based and SQLite adapters for saving/loading model results
1074
+ - ✅ **Data View Management**: Time-point specific data views for pseudo real-time evaluation
1075
+ - ✅ **Helper Function Consolidation**: Reduced code duplication with consolidated helper functions
1076
+ - `to_python_datetime()`: Generic datetime conversion (handles polars datetime, strings, etc.)
1077
+ - `extract_last_date()`: Generic time index extraction
1078
+ - `clock_to_datetime_freq()`: Shared clock-to-datetime frequency mapping
1079
+ - `get_periods_per_year()`: Generic periods-per-year calculation
1080
+ - `get_annual_factor()`: Generic annualization factor calculation
1081
+ - `get_next_period_end()`: Generic period end calculation
1082
+ - ✅ **Generic Naming**: All docstrings and naming made generic (no feature-specific references)
1083
+ - ✅ **Code Refactoring**: Removed all hardcoded month assumptions and magic numbers
1084
+
1085
+ ### Previous Improvements (v0.2.7)
712
1086
 
713
1087
  - ✅ **Fixed C normalization issue**: C normalization now only applies when C norm is in reasonable range (0.1 ~ 10)
714
1088
  - Prevents Q from becoming too small when C norm is very small