dfm-python 0.2.8__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 (101) hide show
  1. {dfm_python-0.2.8 → dfm_python-0.3.0}/.gitignore +1 -1
  2. {dfm_python-0.2.8 → dfm_python-0.3.0}/PKG-INFO +426 -58
  3. {dfm_python-0.2.8 → dfm_python-0.3.0}/README.md +420 -47
  4. dfm_python-0.3.0/config/blocks/default.yaml +29 -0
  5. dfm_python-0.3.0/config/blocks/sample_spec.yaml +29 -0
  6. dfm_python-0.3.0/config/blocks/sample_spec2.yaml +29 -0
  7. {dfm_python-0.2.8 → dfm_python-0.3.0}/config/default.yaml +3 -2
  8. dfm_python-0.3.0/config/experiment/exp1.yaml +14 -0
  9. dfm_python-0.3.0/config/experiment/exp2.yaml +14 -0
  10. dfm_python-0.3.0/config/experiment/exp3.yaml +14 -0
  11. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +29 -0
  12. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +29 -0
  13. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +29 -0
  14. dfm_python-0.3.0/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +29 -0
  15. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +29 -0
  16. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +29 -0
  17. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +29 -0
  18. dfm_python-0.3.0/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +29 -0
  19. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +29 -0
  20. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +29 -0
  21. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +29 -0
  22. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +29 -0
  23. dfm_python-0.3.0/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +29 -0
  24. dfm_python-0.3.0/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +29 -0
  25. dfm_python-0.3.0/config/generated/series/spec_20251118_194140_025b98_series.yaml +749 -0
  26. dfm_python-0.3.0/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +749 -0
  27. dfm_python-0.3.0/config/generated/series/spec_20251118_194423_013beb_series.yaml +749 -0
  28. dfm_python-0.3.0/config/generated/series/spec_20251118_194532_190712_series.yaml +749 -0
  29. dfm_python-0.3.0/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +749 -0
  30. dfm_python-0.3.0/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +749 -0
  31. dfm_python-0.3.0/config/generated/series/spec_20251118_195439_12eea1_series.yaml +749 -0
  32. dfm_python-0.3.0/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +749 -0
  33. dfm_python-0.3.0/config/generated/series/spec_20251118_200209_8b6463_series.yaml +749 -0
  34. dfm_python-0.3.0/config/generated/series/spec_20251118_200319_c62fea_series.yaml +749 -0
  35. dfm_python-0.3.0/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +749 -0
  36. dfm_python-0.3.0/config/generated/series/spec_20251118_200838_320d08_series.yaml +749 -0
  37. dfm_python-0.3.0/config/generated/series/spec_20251118_200934_65c460_series.yaml +749 -0
  38. dfm_python-0.3.0/config/generated/series/spec_20251118_201046_8e7482_series.yaml +749 -0
  39. dfm_python-0.3.0/config/generated/spec_20251118_194140_025b98.yaml +29 -0
  40. dfm_python-0.3.0/config/generated/spec_20251118_194248_82a2a3.yaml +29 -0
  41. dfm_python-0.3.0/config/generated/spec_20251118_194423_013beb.yaml +29 -0
  42. dfm_python-0.3.0/config/generated/spec_20251118_194532_190712.yaml +29 -0
  43. dfm_python-0.3.0/config/generated/spec_20251118_195001_ef69d1.yaml +29 -0
  44. dfm_python-0.3.0/config/generated/spec_20251118_195110_eb12bc.yaml +29 -0
  45. dfm_python-0.3.0/config/generated/spec_20251118_195439_12eea1.yaml +29 -0
  46. dfm_python-0.3.0/config/generated/spec_20251118_195547_0ecebd.yaml +29 -0
  47. dfm_python-0.3.0/config/generated/spec_20251118_200209_8b6463.yaml +29 -0
  48. dfm_python-0.3.0/config/generated/spec_20251118_200319_c62fea.yaml +29 -0
  49. dfm_python-0.3.0/config/generated/spec_20251118_200722_7d6a26.yaml +29 -0
  50. dfm_python-0.3.0/config/generated/spec_20251118_200838_320d08.yaml +29 -0
  51. dfm_python-0.3.0/config/generated/spec_20251118_200934_65c460.yaml +29 -0
  52. dfm_python-0.3.0/config/generated/spec_20251118_201046_8e7482.yaml +29 -0
  53. dfm_python-0.3.0/config/series/default.yaml +749 -0
  54. dfm_python-0.3.0/config/series/sample_spec.yaml +749 -0
  55. dfm_python-0.3.0/config/series/sample_spec2.yaml +699 -0
  56. dfm_python-0.3.0/full_tutorial_example.ipynb +2223 -0
  57. {dfm_python-0.2.8 → dfm_python-0.3.0}/pyproject.toml +12 -11
  58. dfm_python-0.3.0/src/adapters/__init__.py +52 -0
  59. dfm_python-0.3.0/src/adapters/data_view.py +230 -0
  60. dfm_python-0.3.0/src/adapters/database.py +161 -0
  61. dfm_python-0.3.0/src/adapters/model_result.py +200 -0
  62. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/__init__.py +13 -34
  63. dfm_python-0.3.0/src/dfm_python/api.py +1071 -0
  64. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/config.py +17 -83
  65. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/config_sources.py +426 -98
  66. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/diagnostics.py +39 -30
  67. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/em.py +72 -52
  68. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/helpers.py +345 -6
  69. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/numeric.py +2 -2
  70. dfm_python-0.3.0/src/dfm_python/core/time.py +775 -0
  71. {dfm_python-0.2.8/src/dfm_python → dfm_python-0.3.0/src/dfm_python/core}/utils.py +70 -8
  72. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/data.py +257 -103
  73. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/dfm.py +71 -26
  74. dfm_python-0.3.0/src/dfm_python/nowcast.py +1519 -0
  75. dfm_python-0.3.0/src/test/test_api.py +466 -0
  76. dfm_python-0.3.0/src/test/test_core.py +339 -0
  77. dfm_python-0.3.0/src/test/test_integration.py +183 -0
  78. dfm_python-0.3.0/src/test/test_nowcasting.py +1186 -0
  79. dfm_python-0.3.0/tutorial/basic_tutorial.py +1069 -0
  80. {dfm_python-0.2.8 → dfm_python-0.3.0}/uv.lock +520 -981
  81. dfm_python-0.2.8/config/blocks/default.yaml +0 -63
  82. dfm_python-0.2.8/config/series/default.yaml +0 -699
  83. dfm_python-0.2.8/full_tutorial_example.ipynb +0 -2014
  84. dfm_python-0.2.8/src/dfm_python/api.py +0 -420
  85. dfm_python-0.2.8/src/dfm_python/news.py +0 -782
  86. dfm_python-0.2.8/src/test/__init__.py +0 -1255
  87. dfm_python-0.2.8/src/test/test_api.py +0 -704
  88. dfm_python-0.2.8/src/test/test_dfm.py +0 -1397
  89. dfm_python-0.2.8/src/test/test_factor.py +0 -118
  90. dfm_python-0.2.8/src/test/test_numeric.py +0 -478
  91. dfm_python-0.2.8/src/test/test_stability.py +0 -222
  92. dfm_python-0.2.8/tutorial/basic_tutorial.py +0 -599
  93. dfm_python-0.2.8/tutorial/hydra_tutorial.py +0 -252
  94. {dfm_python-0.2.8 → dfm_python-0.3.0}/.python-version +0 -0
  95. {dfm_python-0.2.8 → dfm_python-0.3.0}/LICENSE +0 -0
  96. {dfm_python-0.2.8 → dfm_python-0.3.0}/MANIFEST.in +0 -0
  97. {dfm_python-0.2.8 → dfm_python-0.3.0}/data/sample_data.csv +0 -0
  98. {dfm_python-0.2.8 → dfm_python-0.3.0}/data/sample_spec.csv +0 -0
  99. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/__init__.py +0 -0
  100. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/kalman.py +0 -0
  101. {dfm_python-0.2.8 → dfm_python-0.3.0}/src/test/test_kalman.py +0 -0
@@ -214,4 +214,4 @@ __marimo__/
214
214
  Nowcasting/
215
215
  agent/
216
216
 
217
- mybooks/
217
+ *.db
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.2.8
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,21 +22,16 @@ 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
- Requires-Dist: httpx>=0.24.0; extra == 'all'
29
29
  Requires-Dist: hydra-core>=1.3.0; extra == 'all'
30
30
  Requires-Dist: omegaconf>=2.3.0; extra == 'all'
31
- Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
32
- Requires-Dist: pydantic-settings>=2.11.0; extra == 'all'
33
31
  Requires-Dist: pytest>=7.0.0; extra == 'all'
34
- Requires-Dist: supabase>=2.0.0; extra == 'all'
35
- Provides-Extra: database
36
- Requires-Dist: httpx>=0.24.0; extra == 'database'
37
- Requires-Dist: psycopg2-binary>=2.9.0; extra == 'database'
38
- Requires-Dist: pydantic-settings>=2.11.0; extra == 'database'
39
- Requires-Dist: supabase>=2.0.0; extra == 'database'
32
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'all'
33
+ Provides-Extra: db
34
+ Requires-Dist: sqlalchemy>=2.0.0; extra == 'db'
40
35
  Provides-Extra: dev
41
36
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
42
37
  Provides-Extra: hydra
@@ -61,13 +56,13 @@ A Dynamic Factor Model is a powerful statistical framework that:
61
56
  - **Provides nowcasts** (current-quarter GDP estimates before official release)
62
57
  - **Forecasts** future values of both factors and observed series
63
58
 
64
- **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.
65
60
 
66
61
  ## Features
67
62
 
68
63
  ### Core Capabilities
69
64
  - ✅ **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual series in one model
70
- - ✅ **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)
71
66
  - ✅ **Block structure**: Flexible factor organization (global common factor + sector-specific factors)
72
67
  - ✅ **Idiosyncratic components**: Per-series state augmentation for better fit (AR(1) for clock-frequency, tent-length chains for slower frequencies)
73
68
  - ✅ **Robust missing data**: Spline interpolation preprocessing + Kalman filter handling during estimation
@@ -94,7 +89,7 @@ pip install dfm-python
94
89
  **Requirements**:
95
90
  - Python >= 3.10
96
91
  - numpy >= 1.24.0
97
- - pandas >= 2.0.0
92
+ - polars >= 0.20.0
98
93
  - scipy >= 1.10.0
99
94
 
100
95
  Optional dependencies:
@@ -108,7 +103,8 @@ Optional dependencies:
108
103
  ```python
109
104
  import dfm_python as dfm
110
105
  import numpy as np
111
- import pandas as pd
106
+ from datetime import datetime
107
+ import polars as pl
112
108
 
113
109
  # Generate or load your data (T × N: time periods × series)
114
110
  # Data should be standardized or will be standardized automatically
@@ -132,7 +128,7 @@ result = model.fit(X, config, max_iter=100, threshold=1e-4)
132
128
  factors = result.Z # (101 × 1) Smoothed common factor
133
129
  loadings = result.C # (10 × 1) Factor loadings
134
130
  smoothed_data = result.X_sm # (100 × 10) Smoothed series
135
- forecasts = result.forecast(horizon=12) # 12-step ahead forecasts
131
+ forecasts = result.forecast(horizon=None) # Default: 1 year of periods based on clock frequency
136
132
  ```
137
133
 
138
134
  ### Example 2: Mixed-Frequency Data (Monthly + Quarterly)
@@ -166,8 +162,8 @@ dfm.load_data('data/mixed_frequency_data.csv', sample_start='2000-01-01')
166
162
  # Train
167
163
  dfm.train(max_iter=5000, threshold=1e-5)
168
164
 
169
- # Forecast
170
- 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
171
167
  ```
172
168
 
173
169
  ### Example 3: Using Spec CSV (Recommended for CSV Workflows)
@@ -175,6 +171,7 @@ X_forecast, Z_forecast = dfm.predict(horizon=12)
175
171
  ```python
176
172
  import dfm_python as dfm
177
173
  from dfm_python.config import Params
174
+ import polars as pl
178
175
 
179
176
  # 1. Create spec CSV (see Data Format section)
180
177
  # 2. Load configuration
@@ -185,7 +182,8 @@ params = Params(
185
182
  augment_idio=True, # Enable idio components
186
183
  idio_rho0=0.1 # Initial AR coefficient for idio
187
184
  )
188
- 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)
189
187
 
190
188
  # 3. Load data
191
189
  dfm.load_data('data/data.csv', sample_start='2020-01-01', sample_end='2023-12-31')
@@ -193,9 +191,9 @@ dfm.load_data('data/data.csv', sample_start='2020-01-01', sample_end='2023-12-31
193
191
  # 4. Train
194
192
  dfm.train()
195
193
 
196
- # 5. Forecast and visualize
197
- X_forecast, Z_forecast = dfm.predict(horizon=12)
198
- 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')
199
197
 
200
198
  # 6. Access results
201
199
  result = dfm.get_result()
@@ -203,6 +201,47 @@ print(f"Converged: {result.converged}, Iterations: {result.num_iter}")
203
201
  print(f"Log-likelihood: {result.loglik:.2f}")
204
202
  ```
205
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
+
206
245
  ## Configuration Guide
207
246
 
208
247
  ### Configuration Methods
@@ -215,6 +254,7 @@ The package supports multiple ways to configure your model:
215
254
 
216
255
  ```python
217
256
  from dfm_python.config import Params
257
+ import polars as pl
218
258
 
219
259
  params = Params(
220
260
  max_iter=5000,
@@ -225,7 +265,8 @@ params = Params(
225
265
  idio_rho0=0.1,
226
266
  idio_min_var=1e-8
227
267
  )
228
- 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)
229
270
  ```
230
271
 
231
272
  #### 2. YAML Configuration
@@ -256,7 +297,9 @@ def main(cfg: DictConfig) -> None:
256
297
  dfm.load_config(hydra=cfg)
257
298
  dfm.load_data(str(get_original_cwd() / "data" / "sample_data.csv"))
258
299
  dfm.train(max_iter=cfg.max_iter)
259
- 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)
260
303
 
261
304
  if __name__ == "__main__":
262
305
  main()
@@ -288,6 +331,14 @@ config = DFMConfig(
288
331
  - **`clock`**: Base frequency for all latent factors (default: `'m'` for monthly)
289
332
  - Options: `'d'` (daily), `'w'` (weekly), `'m'` (monthly), `'q'` (quarterly), `'sa'` (semi-annual), `'a'` (annual)
290
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)
291
342
 
292
343
  - **`blocks`**: Factor block structure
293
344
  - `Block_Global`: Common factor affecting all series
@@ -440,28 +491,227 @@ idio_component = result.X_sm[:, series_idx] - series_factor_component
440
491
 
441
492
  ## Advanced Features
442
493
 
443
- ### 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
444
525
 
445
526
  Decompose forecast updates into contributions from individual data releases:
446
527
 
447
528
  ```python
448
- 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
449
703
 
450
704
  # Before new data release
451
705
  result_old = model.fit(X_old, config)
452
706
 
453
707
  # After new data release
454
708
  X_new = ... # Updated data
455
- result_new = model.fit(X_new, config)
456
709
 
457
- # Decompose forecast update
458
- y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
459
- X_old, result_old, t_fcst=100, v_news=0
460
- )
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)
461
713
 
462
- # Forecast update
463
- forecast_update = y_new - y_old
464
- # singlenews contains contribution from each series
714
+ # Recommended: Use Nowcast class instead (see Nowcasting API section above)
465
715
  ```
466
716
 
467
717
  ### Custom Block Structure
@@ -557,9 +807,11 @@ config = DFMConfig(series=series, blocks=blocks)
557
807
 
558
808
  ```python
559
809
  import dfm_python as dfm
810
+ import polars as pl
560
811
 
561
812
  # Configuration
562
- 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
563
815
  dfm.from_yaml(yaml_path) # Load from YAML file
564
816
  dfm.from_dict(config_dict) # Load from dictionary
565
817
  dfm.load_config(hydra=cfg) # Load from Hydra DictConfig
@@ -570,12 +822,19 @@ dfm.load_data(data_path, sample_start=None, sample_end=None)
570
822
  # Estimation
571
823
  dfm.train(max_iter=None, threshold=None, **kwargs)
572
824
 
573
- # Forecasting
574
- 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)
575
828
 
576
829
  # Visualization
577
830
  dfm.plot(kind='factor', factor_index=0, forecast_horizon=None, save_path=None)
578
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
+
579
838
  # Results
580
839
  dfm.get_result() # Returns DFMResult object
581
840
  dfm.reset() # Reset singleton state
@@ -628,25 +887,34 @@ Complete reference for `DFMResult`:
628
887
  - **`dfm.py`**: Core estimation (`DFM` class, `_dfm_core` function, EM algorithm orchestration)
629
888
  - **`core/em.py`**: EM algorithm implementation (`init_conditions`, `em_step`, `em_converged`)
630
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)
631
892
  - **`kalman.py`**: Kalman filter and smoother (`run_kf`, `skf`, `fis`)
632
893
  - **`data.py`**: Data loading and preprocessing (`load_data`, `transform_data`, `rem_nans_spline`)
633
- - **`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)
634
895
  - **`api.py`**: High-level convenience API and module-level functions
635
- - **`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
636
900
 
637
901
  ### Key Design Principles
638
902
 
639
- 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)
640
904
  2. **Tent kernel aggregation**: Deterministic weights for slower-frequency series
641
905
  3. **Idiosyncratic components**: Per-series state augmentation for better fit
642
- 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**:
643
911
  - Adaptive ridge regularization (condition-number-based)
644
912
  - Q matrix floor (0.01 for factors) to prevent scale issues
645
913
  - C matrix normalization (||C[:,j]|| = 1) for clock-frequency factors
646
914
  - Spectral radius capping (< 0.99) for stationarity
647
915
  - Variance floors for all covariance matrices
648
- 5. **Flexible configuration**: Multiple input methods (YAML, CSV, Dict, Hydra)
649
- 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
650
918
 
651
919
  ## Testing
652
920
 
@@ -683,38 +951,138 @@ pytest src/test/test_dfm.py -k "idio" -v
683
951
 
684
952
  ## Tutorials
685
953
 
686
- ### Basic Tutorial (Spec CSV Approach)
954
+ ### Basic Tutorial (Hydra-based)
955
+
956
+ The tutorial demonstrates the complete DFM workflow using Hydra for configuration management:
687
957
 
688
958
  ```bash
689
- 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
690
980
  ```
691
981
 
692
982
  Comprehensive tutorial covering:
693
- - Loading configuration from spec CSV
694
- - Data preparation and transformation
695
- - Model training and convergence
696
- - Interpreting results
697
- - 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
698
1017
 
699
- ### Hydra Tutorial (Advanced Configuration)
1018
+ For database integration, set these environment variables:
700
1019
 
701
1020
  ```bash
702
- 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
703
1034
  ```
704
1035
 
705
- Demonstrates:
706
- - Hydra-based configuration management
707
- - CLI parameter overrides
708
- - 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.
709
1037
 
710
1038
  ## Project Status
711
1039
 
712
- **Version**: 0.2.7
1040
+ **Version**: 0.3.0
713
1041
  **Status**: Stable and production-ready
714
1042
  **Python**: 3.10+
715
1043
  **PyPI**: https://pypi.org/project/dfm-python/
716
1044
 
717
- ### 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)
718
1086
 
719
1087
  - ✅ **Fixed C normalization issue**: C normalization now only applies when C norm is in reasonable range (0.1 ~ 10)
720
1088
  - Prevents Q from becoming too small when C norm is very small