dfm-python 0.3.0__tar.gz → 0.3.1__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 (109) hide show
  1. dfm_python-0.3.1/.git +1 -0
  2. {dfm_python-0.3.0 → dfm_python-0.3.1}/PKG-INFO +190 -27
  3. {dfm_python-0.3.0 → dfm_python-0.3.1}/README.md +184 -26
  4. dfm_python-0.3.1/config/experiment/ddfm_example.yaml +26 -0
  5. dfm_python-0.3.1/data/mdfred_snapshot_monthly.csv +780 -0
  6. dfm_python-0.3.1/data/mdfred_snapshot_quarterly.csv +262 -0
  7. {dfm_python-0.3.0 → dfm_python-0.3.1}/full_tutorial_example.ipynb +223 -181
  8. {dfm_python-0.3.0 → dfm_python-0.3.1}/pyproject.toml +6 -2
  9. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/dfm_python/__init__.py +91 -18
  10. dfm_python-0.3.1/src/dfm_python/api/__init__.py +44 -0
  11. dfm_python-0.3.0/src/dfm_python/api.py → dfm_python-0.3.1/src/dfm_python/api/model_api.py +348 -271
  12. dfm_python-0.3.1/src/dfm_python/api/session_api.py +402 -0
  13. dfm_python-0.3.1/src/dfm_python/config/__init__.py +35 -0
  14. dfm_python-0.3.0/src/dfm_python/config_sources.py → dfm_python-0.3.1/src/dfm_python/config/io.py +9 -9
  15. dfm_python-0.3.0/src/dfm_python/config.py → dfm_python-0.3.1/src/dfm_python/config/schema.py +36 -39
  16. dfm_python-0.3.1/src/dfm_python/core/__init__.py +139 -0
  17. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/dfm_python/core/diagnostics.py +22 -23
  18. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/dfm_python/core/em.py +10 -9
  19. dfm_python-0.3.0/src/dfm_python/dfm.py → dfm_python-0.3.1/src/dfm_python/core/estimation.py +22 -432
  20. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/dfm_python/core/helpers.py +10 -9
  21. dfm_python-0.3.1/src/dfm_python/core/results.py +293 -0
  22. dfm_python-0.3.0/src/dfm_python/core/numeric.py → dfm_python-0.3.1/src/dfm_python/core/state_space.py +630 -269
  23. dfm_python-0.3.0/src/dfm_python/core/utils.py → dfm_python-0.3.1/src/dfm_python/core/structure.py +6 -14
  24. dfm_python-0.3.1/src/dfm_python/dataloader/__init__.py +30 -0
  25. dfm_python-0.3.0/src/dfm_python/data.py → dfm_python-0.3.1/src/dfm_python/dataloader/loader.py +18 -18
  26. dfm_python-0.3.1/src/dfm_python/dataloader/preprocess.py +11 -0
  27. dfm_python-0.3.1/src/dfm_python/models/__init__.py +19 -0
  28. dfm_python-0.3.1/src/dfm_python/models/base.py +98 -0
  29. dfm_python-0.3.1/src/dfm_python/models/ddfm.py +978 -0
  30. dfm_python-0.3.1/src/dfm_python/models/dfm.py +213 -0
  31. dfm_python-0.3.1/src/dfm_python/nowcasting/__init__.py +13 -0
  32. {dfm_python-0.3.0/src/dfm_python → dfm_python-0.3.1/src/dfm_python/nowcasting}/nowcast.py +303 -325
  33. dfm_python-0.3.1/src/dfm_python/utils/__init__.py +38 -0
  34. dfm_python-0.3.1/src/dfm_python/utils/convert.py +342 -0
  35. dfm_python-0.3.1/src/dfm_python/utils/loss.py +184 -0
  36. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/test/test_api.py +3 -3
  37. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/test/test_core.py +1 -1
  38. dfm_python-0.3.1/src/test/test_ddfm.py +645 -0
  39. dfm_python-0.3.1/src/test/test_dfm_regression.py +172 -0
  40. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/test/test_kalman.py +2 -2
  41. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/test/test_nowcasting.py +102 -8
  42. {dfm_python-0.3.0 → dfm_python-0.3.1}/tutorial/basic_tutorial.py +68 -19
  43. {dfm_python-0.3.0 → dfm_python-0.3.1}/uv.lock +482 -85
  44. dfm_python-0.3.0/src/dfm_python/core/__init__.py +0 -15
  45. dfm_python-0.3.0/src/dfm_python/kalman.py +0 -467
  46. {dfm_python-0.3.0 → dfm_python-0.3.1}/.gitignore +0 -0
  47. {dfm_python-0.3.0 → dfm_python-0.3.1}/.python-version +0 -0
  48. {dfm_python-0.3.0 → dfm_python-0.3.1}/LICENSE +0 -0
  49. {dfm_python-0.3.0 → dfm_python-0.3.1}/MANIFEST.in +0 -0
  50. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/blocks/default.yaml +0 -0
  51. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/blocks/sample_spec.yaml +0 -0
  52. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/blocks/sample_spec2.yaml +0 -0
  53. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/default.yaml +0 -0
  54. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/experiment/exp1.yaml +0 -0
  55. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/experiment/exp2.yaml +0 -0
  56. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/experiment/exp3.yaml +0 -0
  57. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +0 -0
  58. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +0 -0
  59. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +0 -0
  60. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +0 -0
  61. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +0 -0
  62. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +0 -0
  63. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +0 -0
  64. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +0 -0
  65. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +0 -0
  66. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +0 -0
  67. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +0 -0
  68. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +0 -0
  69. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +0 -0
  70. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +0 -0
  71. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_194140_025b98_series.yaml +0 -0
  72. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +0 -0
  73. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_194423_013beb_series.yaml +0 -0
  74. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_194532_190712_series.yaml +0 -0
  75. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +0 -0
  76. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +0 -0
  77. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_195439_12eea1_series.yaml +0 -0
  78. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +0 -0
  79. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_200209_8b6463_series.yaml +0 -0
  80. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_200319_c62fea_series.yaml +0 -0
  81. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +0 -0
  82. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_200838_320d08_series.yaml +0 -0
  83. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_200934_65c460_series.yaml +0 -0
  84. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/series/spec_20251118_201046_8e7482_series.yaml +0 -0
  85. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_194140_025b98.yaml +0 -0
  86. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_194248_82a2a3.yaml +0 -0
  87. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_194423_013beb.yaml +0 -0
  88. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_194532_190712.yaml +0 -0
  89. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_195001_ef69d1.yaml +0 -0
  90. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_195110_eb12bc.yaml +0 -0
  91. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_195439_12eea1.yaml +0 -0
  92. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_195547_0ecebd.yaml +0 -0
  93. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_200209_8b6463.yaml +0 -0
  94. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_200319_c62fea.yaml +0 -0
  95. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_200722_7d6a26.yaml +0 -0
  96. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_200838_320d08.yaml +0 -0
  97. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_200934_65c460.yaml +0 -0
  98. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/generated/spec_20251118_201046_8e7482.yaml +0 -0
  99. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/series/default.yaml +0 -0
  100. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/series/sample_spec.yaml +0 -0
  101. {dfm_python-0.3.0 → dfm_python-0.3.1}/config/series/sample_spec2.yaml +0 -0
  102. {dfm_python-0.3.0 → dfm_python-0.3.1}/data/sample_data.csv +0 -0
  103. {dfm_python-0.3.0 → dfm_python-0.3.1}/data/sample_spec.csv +0 -0
  104. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/adapters/__init__.py +0 -0
  105. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/adapters/data_view.py +0 -0
  106. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/adapters/database.py +0 -0
  107. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/adapters/model_result.py +0 -0
  108. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/dfm_python/core/time.py +0 -0
  109. {dfm_python-0.3.0 → dfm_python-0.3.1}/src/test/test_integration.py +0 -0
dfm_python-0.3.1/.git ADDED
@@ -0,0 +1 @@
1
+ gitdir: ../../../../../kaggle_projects/hull-tactical/.git/worktrees/qui/modules/dfm-python
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -29,11 +29,16 @@ Provides-Extra: all
29
29
  Requires-Dist: hydra-core>=1.3.0; extra == 'all'
30
30
  Requires-Dist: omegaconf>=2.3.0; extra == 'all'
31
31
  Requires-Dist: pytest>=7.0.0; extra == 'all'
32
+ Requires-Dist: scikit-learn>=1.0.0; extra == 'all'
32
33
  Requires-Dist: sqlalchemy>=2.0.0; extra == 'all'
34
+ Requires-Dist: torch>=2.0.0; extra == 'all'
33
35
  Provides-Extra: db
34
36
  Requires-Dist: sqlalchemy>=2.0.0; extra == 'db'
37
+ Provides-Extra: deep
38
+ Requires-Dist: torch>=2.0.0; extra == 'deep'
35
39
  Provides-Extra: dev
36
40
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
41
+ Requires-Dist: scikit-learn>=1.0.0; extra == 'dev'
37
42
  Provides-Extra: hydra
38
43
  Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
39
44
  Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
@@ -68,6 +73,7 @@ A Dynamic Factor Model is a powerful statistical framework that:
68
73
  - ✅ **Robust missing data**: Spline interpolation preprocessing + Kalman filter handling during estimation
69
74
  - ✅ **News decomposition**: Attribute forecast changes to specific data releases
70
75
  - ✅ **Nowcasting & Forecasting**: Generate predictions for any horizon
76
+ - ✅ **Deep Dynamic Factor Models (DDFM)**: Nonlinear encoder with PyTorch for capturing complex factor structures (optional, requires `dfm-python[deep]`)
71
77
 
72
78
  ### Technical Features
73
79
  - ✅ **Multiple configuration methods**: YAML files, CSV specs, Python dictionaries, or Hydra
@@ -95,6 +101,7 @@ pip install dfm-python
95
101
  Optional dependencies:
96
102
  - `hydra-core` (for Hydra configuration management)
97
103
  - `matplotlib` (for plotting)
104
+ - `torch>=2.0.0` (for Deep Dynamic Factor Models - install with `pip install dfm-python[deep]`)
98
105
 
99
106
  ## Quick Start
100
107
 
@@ -131,7 +138,41 @@ smoothed_data = result.X_sm # (100 × 10) Smoothed series
131
138
  forecasts = result.forecast(horizon=None) # Default: 1 year of periods based on clock frequency
132
139
  ```
133
140
 
134
- ### Example 2: Mixed-Frequency Data (Monthly + Quarterly)
141
+ ### Example 2: Deep Dynamic Factor Model (DDFM)
142
+
143
+ ```python
144
+ import dfm_python as dfm
145
+ from dfm_python import create_model, DFMConfig, SeriesConfig, BlockConfig
146
+ import numpy as np
147
+
148
+ # Generate or load your data
149
+ X = np.random.randn(100, 10) # 100 months, 10 series
150
+
151
+ # Create configuration
152
+ series = [
153
+ SeriesConfig(series_id=f'series_{i}', frequency='m', transformation='lin', blocks=[1])
154
+ for i in range(10)
155
+ ]
156
+ config = DFMConfig(
157
+ series=series,
158
+ blocks=[BlockConfig(block_name='Global', factors=1)],
159
+ clock='m',
160
+ factors_per_block=[1],
161
+ )
162
+
163
+ # Create and fit DDFM model
164
+ ddfm = create_model('ddfm', encoder_layers=[64, 32], num_factors=1, epochs=100)
165
+ result = ddfm.fit(X, config)
166
+
167
+ # Access results (same structure as DFM)
168
+ factors = result.Z # (100 × 1) Extracted factors
169
+ loadings = result.C # (10 × 1) Factor loadings
170
+ X_forecast, Z_forecast = ddfm.predict(horizon=12)
171
+ ```
172
+
173
+ **Note**: DDFM requires PyTorch. Install with `pip install dfm-python[deep]`.
174
+
175
+ ### Example 3: Mixed-Frequency Data (Monthly + Quarterly)
135
176
 
136
177
  ```python
137
178
  import dfm_python as dfm
@@ -597,7 +638,7 @@ Generate datasets for model evaluation across multiple periods:
597
638
  from datetime import datetime
598
639
 
599
640
  # Define evaluation periods
600
- from dfm_python.core.time import datetime_range
641
+ from dfm_python.engine.time import datetime_range
601
642
  periods = datetime_range(start=datetime(2020, 1, 1), end=datetime(2023, 12, 31), freq='QE')
602
643
 
603
644
  # Generate evaluation dataset
@@ -916,6 +957,132 @@ Complete reference for `DFMResult`:
916
957
  6. **Flexible configuration**: Multiple input methods (YAML, CSV, Dict, Hydra)
917
958
  7. **Backward compatibility**: Old code continues to work, new features are opt-in
918
959
 
960
+ ## Deep Dynamic Factor Models (DDFM)
961
+
962
+ The package includes support for **Deep Dynamic Factor Models (DDFM)**, which use nonlinear encoders to extract factors from observed data. DDFM is particularly useful when the relationship between observed variables and latent factors is nonlinear.
963
+
964
+ **Note**: DDFM has a **separate high-level API class** (`dfm_python.DDFM`) from the linear DFM class (`dfm_python.DFM`). Both classes share the same interface (load_config, load_data, train, predict, plot) but use different underlying models.
965
+
966
+ ### When to Use DDFM vs Linear DFM
967
+
968
+ - **Use Linear DFM** when:
969
+ - The relationship between factors and observations is approximately linear
970
+ - You need interpretable factor loadings
971
+ - Computational speed is important
972
+ - You have limited data
973
+
974
+ - **Use DDFM** when:
975
+ - The relationship between factors and observations is nonlinear
976
+ - You suspect complex interactions between variables
977
+ - You have sufficient data for training neural networks
978
+ - You want to capture non-Gaussian factor structures
979
+
980
+ ### DDFM Architecture
981
+
982
+ The DDFM implementation uses:
983
+ - **Nonlinear Encoder**: Multi-layer perceptron (MLP) to extract factors from observations
984
+ - **Linear Decoder**: Linear transformation from factors back to observations (for interpretability)
985
+ - **Linear Dynamics**: VAR(1) or VAR(2) dynamics for factors (estimated via OLS)
986
+ - **Idiosyncratic Modeling**: AR(1) dynamics for residual components (optional)
987
+ - **Kalman Smoothing**: Final smoothing step using Kalman filter with full state-space (factors + idio)
988
+
989
+ **Key Features:**
990
+ - **VAR(2) Support**: Can model factor dynamics with up to 2 lags using companion form
991
+ - **Idiosyncratic Components**: Models residual components with AR(1) dynamics for better fit
992
+ - **Decoder Parameter Extraction**: Directly extracts observation matrix from trained decoder (no OLS re-estimation)
993
+ - **Full State-Space**: Complete state-space model with factors and idio in state vector
994
+
995
+ ### DDFM Examples
996
+
997
+ #### Example 1: Basic DDFM Usage
998
+
999
+ ```python
1000
+ from dfm_python import create_model, DFMConfig, SeriesConfig, BlockConfig
1001
+ import numpy as np
1002
+
1003
+ # Create data
1004
+ X = np.random.randn(200, 15) # 200 time periods, 15 series
1005
+
1006
+ # Create config
1007
+ series = [
1008
+ SeriesConfig(series_id=f'series_{i}', frequency='m', transformation='lin', blocks=[1])
1009
+ for i in range(15)
1010
+ ]
1011
+ config = DFMConfig(
1012
+ series=series,
1013
+ blocks={'Block_Global': BlockConfig(factors=2, ar_lag=1, clock='m')},
1014
+ clock='m',
1015
+ )
1016
+
1017
+ # Create DDFM model
1018
+ ddfm = create_model(
1019
+ 'ddfm',
1020
+ encoder_layers=[64, 32, 16], # Encoder architecture
1021
+ num_factors=2, # Number of factors
1022
+ activation='tanh', # Activation function
1023
+ epochs=100, # Training epochs
1024
+ batch_size=32, # Batch size
1025
+ learning_rate=0.001, # Learning rate
1026
+ )
1027
+
1028
+ # Fit model
1029
+ result = ddfm.fit(X, config)
1030
+
1031
+ # Use results (same API as linear DFM)
1032
+ factors = result.Z
1033
+ loadings = result.C
1034
+ X_forecast, Z_forecast = ddfm.predict(horizon=12)
1035
+ ```
1036
+
1037
+ ### Testing DDFM
1038
+
1039
+ The package includes synthetic DGP tests for validating DDFM:
1040
+
1041
+ ```python
1042
+ from dfm_python.engine.synthetic_dgp import SyntheticDGP
1043
+ from dfm_python import create_model
1044
+
1045
+ # Create synthetic data with known factors
1046
+ dgp = SyntheticDGP(seed=42, n=10, r=1, poly_degree=2) # Nonlinear factors
1047
+ X = dgp.simulate(200)
1048
+
1049
+ # Fit DDFM
1050
+ ddfm = create_model('ddfm', encoder_layers=[64, 32], num_factors=1)
1051
+ result = ddfm.fit(X, config)
1052
+
1053
+ # Evaluate factor recovery
1054
+ r2 = dgp.evaluate(result.Z) # Trace R² metric
1055
+ print(f"Factor recovery R²: {r2:.3f}")
1056
+ ```
1057
+
1058
+ ## Package Structure
1059
+
1060
+ The package is organized into clear layers for better maintainability and extensibility:
1061
+
1062
+ - **`engine/`**: Pure mathematical/algorithmic code shared by all factor models
1063
+ - EM algorithm, Kalman filter, numerical utilities
1064
+ - Time series utilities, diagnostics, synthetic DGP
1065
+ - Model-agnostic low-level engine code
1066
+
1067
+ - **`models/`**: Factor model implementations
1068
+ - `base.py`: BaseFactorModel interface
1069
+ - `dfm.py`: Linear Dynamic Factor Model (EM-based)
1070
+ - `ddfm.py`: Deep Dynamic Factor Model (PyTorch-based, fully integrated)
1071
+
1072
+ **Note**: The original DDFM implementation (TensorFlow/Keras) has been archived to `archive/DDFM_original/` for reference. The current implementation is fully integrated in `src/dfm_python/models/ddfm.py`.
1073
+
1074
+ - **`nowcasting/`**: Nowcasting and news decomposition domain logic
1075
+ - `nowcast.py`: Nowcast class and core logic
1076
+ - News decomposition and backtest functionality
1077
+
1078
+ - **`api.py` / `__init__.py`**: Public high-level API
1079
+ - `DFM` class, `create_model()` factory
1080
+ - Module-level convenience functions
1081
+
1082
+ - **`core/`**: Backward compatibility layer (re-exports from `engine/`)
1083
+ - Maintains old import paths for existing code
1084
+ - New code should use `engine.*` directly
1085
+
919
1086
  ## Testing
920
1087
 
921
1088
  Run the full test suite:
@@ -928,15 +1095,19 @@ Run specific test categories:
928
1095
 
929
1096
  ```bash
930
1097
  # Core DFM estimation
931
- pytest src/test/test_dfm.py -v
1098
+ pytest src/test/test_api.py -v
932
1099
 
933
1100
  # Kalman filter/smoother
934
1101
  pytest src/test/test_kalman.py -v
935
1102
 
936
- # Numerical stability
937
- pytest src/test/test_numeric.py -v
1103
+ # DDFM tests (requires PyTorch)
1104
+ pytest src/test/test_ddfm.py -v
1105
+
1106
+ # Regression tests (ensure backward compatibility)
1107
+ pytest src/test/test_dfm_regression.py -v
938
1108
 
939
- # Idiosyncratic components (new)
1109
+ # Integration tests
1110
+ pytest src/test/test_integration.py -v
940
1111
  pytest src/test/test_dfm.py -k "idio" -v
941
1112
  ```
942
1113
 
@@ -1042,26 +1213,18 @@ DATA_VIEW_SOURCE=file # or database
1042
1213
  **Python**: 3.10+
1043
1214
  **PyPI**: https://pypi.org/project/dfm-python/
1044
1215
 
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`)
1216
+ ### Recent Improvements (v0.3.1)
1217
+
1218
+ - ✅ **Polars-backed Data Views + Kalman Cache**
1219
+ `create_data_view()` now applies release-date masks via Polars, avoiding per-view NumPy copies. `Nowcast.backtest()` reuses cached Kalman filter/smoother states across successive data views, giving large speed-ups for 12-step pseudo real-time tests.
1220
+ - ✅ **Backtest Trajectory Plot**
1221
+ `BacktestResult.plot_trajectory()` draws 이전 vs. 업데이트 nowcast curves plus monthly actuals along a relative week axis (`-12주 … 현재`). The tutorial simply calls this helper—no more hand-built plotting code.
1222
+ - ✅ **Higher-Frequency Backsteps**
1223
+ `Nowcast.backtest(..., backward_steps=12, higher_freq=True)` now emits weekly snapshots even when the model clock is monthly, so you can inspect 12주 worth of “pseudo releases” immediately.
1224
+ - ✅ **Reload & Reuse**
1225
+ `DFM.load_pickle()` restores config, time index, and cached Polars data views so you can call `predict()`/`nowcast()` right after loading without re-reading CSVs.
1226
+ - ✅ **API + Docs Refresh**
1227
+ README/tutorial sections now highlight the faster backtests, new trajectory plots, and guidelines for interpreting flattened Q-matrix values or “strange” news decomposition outputs.
1065
1228
 
1066
1229
  ### Previous Improvements (v0.2.9)
1067
1230
 
@@ -27,6 +27,7 @@ A Dynamic Factor Model is a powerful statistical framework that:
27
27
  - ✅ **Robust missing data**: Spline interpolation preprocessing + Kalman filter handling during estimation
28
28
  - ✅ **News decomposition**: Attribute forecast changes to specific data releases
29
29
  - ✅ **Nowcasting & Forecasting**: Generate predictions for any horizon
30
+ - ✅ **Deep Dynamic Factor Models (DDFM)**: Nonlinear encoder with PyTorch for capturing complex factor structures (optional, requires `dfm-python[deep]`)
30
31
 
31
32
  ### Technical Features
32
33
  - ✅ **Multiple configuration methods**: YAML files, CSV specs, Python dictionaries, or Hydra
@@ -54,6 +55,7 @@ pip install dfm-python
54
55
  Optional dependencies:
55
56
  - `hydra-core` (for Hydra configuration management)
56
57
  - `matplotlib` (for plotting)
58
+ - `torch>=2.0.0` (for Deep Dynamic Factor Models - install with `pip install dfm-python[deep]`)
57
59
 
58
60
  ## Quick Start
59
61
 
@@ -90,7 +92,41 @@ smoothed_data = result.X_sm # (100 × 10) Smoothed series
90
92
  forecasts = result.forecast(horizon=None) # Default: 1 year of periods based on clock frequency
91
93
  ```
92
94
 
93
- ### Example 2: Mixed-Frequency Data (Monthly + Quarterly)
95
+ ### Example 2: Deep Dynamic Factor Model (DDFM)
96
+
97
+ ```python
98
+ import dfm_python as dfm
99
+ from dfm_python import create_model, DFMConfig, SeriesConfig, BlockConfig
100
+ import numpy as np
101
+
102
+ # Generate or load your data
103
+ X = np.random.randn(100, 10) # 100 months, 10 series
104
+
105
+ # Create configuration
106
+ series = [
107
+ SeriesConfig(series_id=f'series_{i}', frequency='m', transformation='lin', blocks=[1])
108
+ for i in range(10)
109
+ ]
110
+ config = DFMConfig(
111
+ series=series,
112
+ blocks=[BlockConfig(block_name='Global', factors=1)],
113
+ clock='m',
114
+ factors_per_block=[1],
115
+ )
116
+
117
+ # Create and fit DDFM model
118
+ ddfm = create_model('ddfm', encoder_layers=[64, 32], num_factors=1, epochs=100)
119
+ result = ddfm.fit(X, config)
120
+
121
+ # Access results (same structure as DFM)
122
+ factors = result.Z # (100 × 1) Extracted factors
123
+ loadings = result.C # (10 × 1) Factor loadings
124
+ X_forecast, Z_forecast = ddfm.predict(horizon=12)
125
+ ```
126
+
127
+ **Note**: DDFM requires PyTorch. Install with `pip install dfm-python[deep]`.
128
+
129
+ ### Example 3: Mixed-Frequency Data (Monthly + Quarterly)
94
130
 
95
131
  ```python
96
132
  import dfm_python as dfm
@@ -556,7 +592,7 @@ Generate datasets for model evaluation across multiple periods:
556
592
  from datetime import datetime
557
593
 
558
594
  # Define evaluation periods
559
- from dfm_python.core.time import datetime_range
595
+ from dfm_python.engine.time import datetime_range
560
596
  periods = datetime_range(start=datetime(2020, 1, 1), end=datetime(2023, 12, 31), freq='QE')
561
597
 
562
598
  # Generate evaluation dataset
@@ -875,6 +911,132 @@ Complete reference for `DFMResult`:
875
911
  6. **Flexible configuration**: Multiple input methods (YAML, CSV, Dict, Hydra)
876
912
  7. **Backward compatibility**: Old code continues to work, new features are opt-in
877
913
 
914
+ ## Deep Dynamic Factor Models (DDFM)
915
+
916
+ The package includes support for **Deep Dynamic Factor Models (DDFM)**, which use nonlinear encoders to extract factors from observed data. DDFM is particularly useful when the relationship between observed variables and latent factors is nonlinear.
917
+
918
+ **Note**: DDFM has a **separate high-level API class** (`dfm_python.DDFM`) from the linear DFM class (`dfm_python.DFM`). Both classes share the same interface (load_config, load_data, train, predict, plot) but use different underlying models.
919
+
920
+ ### When to Use DDFM vs Linear DFM
921
+
922
+ - **Use Linear DFM** when:
923
+ - The relationship between factors and observations is approximately linear
924
+ - You need interpretable factor loadings
925
+ - Computational speed is important
926
+ - You have limited data
927
+
928
+ - **Use DDFM** when:
929
+ - The relationship between factors and observations is nonlinear
930
+ - You suspect complex interactions between variables
931
+ - You have sufficient data for training neural networks
932
+ - You want to capture non-Gaussian factor structures
933
+
934
+ ### DDFM Architecture
935
+
936
+ The DDFM implementation uses:
937
+ - **Nonlinear Encoder**: Multi-layer perceptron (MLP) to extract factors from observations
938
+ - **Linear Decoder**: Linear transformation from factors back to observations (for interpretability)
939
+ - **Linear Dynamics**: VAR(1) or VAR(2) dynamics for factors (estimated via OLS)
940
+ - **Idiosyncratic Modeling**: AR(1) dynamics for residual components (optional)
941
+ - **Kalman Smoothing**: Final smoothing step using Kalman filter with full state-space (factors + idio)
942
+
943
+ **Key Features:**
944
+ - **VAR(2) Support**: Can model factor dynamics with up to 2 lags using companion form
945
+ - **Idiosyncratic Components**: Models residual components with AR(1) dynamics for better fit
946
+ - **Decoder Parameter Extraction**: Directly extracts observation matrix from trained decoder (no OLS re-estimation)
947
+ - **Full State-Space**: Complete state-space model with factors and idio in state vector
948
+
949
+ ### DDFM Examples
950
+
951
+ #### Example 1: Basic DDFM Usage
952
+
953
+ ```python
954
+ from dfm_python import create_model, DFMConfig, SeriesConfig, BlockConfig
955
+ import numpy as np
956
+
957
+ # Create data
958
+ X = np.random.randn(200, 15) # 200 time periods, 15 series
959
+
960
+ # Create config
961
+ series = [
962
+ SeriesConfig(series_id=f'series_{i}', frequency='m', transformation='lin', blocks=[1])
963
+ for i in range(15)
964
+ ]
965
+ config = DFMConfig(
966
+ series=series,
967
+ blocks={'Block_Global': BlockConfig(factors=2, ar_lag=1, clock='m')},
968
+ clock='m',
969
+ )
970
+
971
+ # Create DDFM model
972
+ ddfm = create_model(
973
+ 'ddfm',
974
+ encoder_layers=[64, 32, 16], # Encoder architecture
975
+ num_factors=2, # Number of factors
976
+ activation='tanh', # Activation function
977
+ epochs=100, # Training epochs
978
+ batch_size=32, # Batch size
979
+ learning_rate=0.001, # Learning rate
980
+ )
981
+
982
+ # Fit model
983
+ result = ddfm.fit(X, config)
984
+
985
+ # Use results (same API as linear DFM)
986
+ factors = result.Z
987
+ loadings = result.C
988
+ X_forecast, Z_forecast = ddfm.predict(horizon=12)
989
+ ```
990
+
991
+ ### Testing DDFM
992
+
993
+ The package includes synthetic DGP tests for validating DDFM:
994
+
995
+ ```python
996
+ from dfm_python.engine.synthetic_dgp import SyntheticDGP
997
+ from dfm_python import create_model
998
+
999
+ # Create synthetic data with known factors
1000
+ dgp = SyntheticDGP(seed=42, n=10, r=1, poly_degree=2) # Nonlinear factors
1001
+ X = dgp.simulate(200)
1002
+
1003
+ # Fit DDFM
1004
+ ddfm = create_model('ddfm', encoder_layers=[64, 32], num_factors=1)
1005
+ result = ddfm.fit(X, config)
1006
+
1007
+ # Evaluate factor recovery
1008
+ r2 = dgp.evaluate(result.Z) # Trace R² metric
1009
+ print(f"Factor recovery R²: {r2:.3f}")
1010
+ ```
1011
+
1012
+ ## Package Structure
1013
+
1014
+ The package is organized into clear layers for better maintainability and extensibility:
1015
+
1016
+ - **`engine/`**: Pure mathematical/algorithmic code shared by all factor models
1017
+ - EM algorithm, Kalman filter, numerical utilities
1018
+ - Time series utilities, diagnostics, synthetic DGP
1019
+ - Model-agnostic low-level engine code
1020
+
1021
+ - **`models/`**: Factor model implementations
1022
+ - `base.py`: BaseFactorModel interface
1023
+ - `dfm.py`: Linear Dynamic Factor Model (EM-based)
1024
+ - `ddfm.py`: Deep Dynamic Factor Model (PyTorch-based, fully integrated)
1025
+
1026
+ **Note**: The original DDFM implementation (TensorFlow/Keras) has been archived to `archive/DDFM_original/` for reference. The current implementation is fully integrated in `src/dfm_python/models/ddfm.py`.
1027
+
1028
+ - **`nowcasting/`**: Nowcasting and news decomposition domain logic
1029
+ - `nowcast.py`: Nowcast class and core logic
1030
+ - News decomposition and backtest functionality
1031
+
1032
+ - **`api.py` / `__init__.py`**: Public high-level API
1033
+ - `DFM` class, `create_model()` factory
1034
+ - Module-level convenience functions
1035
+
1036
+ - **`core/`**: Backward compatibility layer (re-exports from `engine/`)
1037
+ - Maintains old import paths for existing code
1038
+ - New code should use `engine.*` directly
1039
+
878
1040
  ## Testing
879
1041
 
880
1042
  Run the full test suite:
@@ -887,15 +1049,19 @@ Run specific test categories:
887
1049
 
888
1050
  ```bash
889
1051
  # Core DFM estimation
890
- pytest src/test/test_dfm.py -v
1052
+ pytest src/test/test_api.py -v
891
1053
 
892
1054
  # Kalman filter/smoother
893
1055
  pytest src/test/test_kalman.py -v
894
1056
 
895
- # Numerical stability
896
- pytest src/test/test_numeric.py -v
1057
+ # DDFM tests (requires PyTorch)
1058
+ pytest src/test/test_ddfm.py -v
1059
+
1060
+ # Regression tests (ensure backward compatibility)
1061
+ pytest src/test/test_dfm_regression.py -v
897
1062
 
898
- # Idiosyncratic components (new)
1063
+ # Integration tests
1064
+ pytest src/test/test_integration.py -v
899
1065
  pytest src/test/test_dfm.py -k "idio" -v
900
1066
  ```
901
1067
 
@@ -1001,26 +1167,18 @@ DATA_VIEW_SOURCE=file # or database
1001
1167
  **Python**: 3.10+
1002
1168
  **PyPI**: https://pypi.org/project/dfm-python/
1003
1169
 
1004
- ### Recent Improvements (v0.3.0)
1005
-
1006
- - ✅ **Polars Migration**: Core data processing migrated to Polars for improved performance and modern data manipulation
1007
- - ✅ **Polars Data Views + Kalman Cache (v0.3.1)**:
1008
- - `create_data_view()` now uses Polars masking and avoids per-view numpy copies
1009
- - `DFM.load_data()` stores a Polars view for reuse; `Nowcast` reuses cached Kalman states
1010
- - Enable `logging.getLogger('dfm_python.nowcast').setLevel('DEBUG')` to see per-view timing (data view vs. Kalman)
1011
- - ✅ **Full Nowcast Implementation**: Complete nowcasting API with `nowcast.py` module consolidating all nowcasting and news decomposition functionality
1012
- - ✅ **Backtesting Implementation**: Pseudo real-time evaluation framework for model validation
1013
- - `Nowcast.backtest()` method for comprehensive backtesting with point-wise metrics
1014
- - `BacktestResult` dataclass with visualization capabilities
1015
- - Support for higher-frequency backward steps and flexible date handling
1016
- - Automatic error handling and failed step tracking
1017
- - ✅ **Enhanced Nowcast API**:
1018
- - `NowcastResult` and `NewsDecompResult` dataclasses for structured results
1019
- - `return_result` parameter in `__call__` method for full metadata
1020
- - Improved date parsing and error handling
1021
- - ✅ **Module Reorganization**: `news.py` functionality integrated into `nowcast.py` for clearer module structure
1022
- - All nowcasting functions now available via `from dfm_python.nowcast import ...`
1023
- - `news.py` module removed (functionality fully migrated to `nowcast.py`)
1170
+ ### Recent Improvements (v0.3.1)
1171
+
1172
+ - ✅ **Polars-backed Data Views + Kalman Cache**
1173
+ `create_data_view()` now applies release-date masks via Polars, avoiding per-view NumPy copies. `Nowcast.backtest()` reuses cached Kalman filter/smoother states across successive data views, giving large speed-ups for 12-step pseudo real-time tests.
1174
+ - ✅ **Backtest Trajectory Plot**
1175
+ `BacktestResult.plot_trajectory()` draws 이전 vs. 업데이트 nowcast curves plus monthly actuals along a relative week axis (`-12주 … 현재`). The tutorial simply calls this helper—no more hand-built plotting code.
1176
+ - ✅ **Higher-Frequency Backsteps**
1177
+ `Nowcast.backtest(..., backward_steps=12, higher_freq=True)` now emits weekly snapshots even when the model clock is monthly, so you can inspect 12주 worth of “pseudo releases” immediately.
1178
+ - ✅ **Reload & Reuse**
1179
+ `DFM.load_pickle()` restores config, time index, and cached Polars data views so you can call `predict()`/`nowcast()` right after loading without re-reading CSVs.
1180
+ - ✅ **API + Docs Refresh**
1181
+ README/tutorial sections now highlight the faster backtests, new trajectory plots, and guidelines for interpreting flattened Q-matrix values or “strange” news decomposition outputs.
1024
1182
 
1025
1183
  ### Previous Improvements (v0.2.9)
1026
1184
 
@@ -0,0 +1,26 @@
1
+ # Example DDFM (Deep Dynamic Factor Model) Configuration
2
+ # This configuration demonstrates all DDFM-specific parameters
3
+
4
+ # Standard DFM parameters
5
+ ar_lag: 1
6
+ threshold: 1e-5
7
+ max_iter: 5000
8
+ nan_method: 2
9
+ nan_k: 3
10
+ clock: 'm'
11
+
12
+ # DDFM-specific parameters
13
+ ddfm_encoder_layers: [64, 32, 16] # Hidden layer dimensions for encoder
14
+ ddfm_num_factors: 2 # Number of factors (optional, inferred from blocks if None)
15
+ ddfm_activation: 'tanh' # Activation function: 'tanh', 'relu', or 'sigmoid'
16
+ ddfm_use_batch_norm: true # Use batch normalization in encoder
17
+ ddfm_learning_rate: 0.001 # Learning rate for Adam optimizer
18
+ ddfm_epochs: 100 # Number of training epochs
19
+ ddfm_batch_size: 32 # Batch size for training
20
+ ddfm_factor_order: 2 # VAR lag order for factor dynamics (1 or 2)
21
+ ddfm_use_idiosyncratic: true # Model idiosyncratic components with AR(1) dynamics
22
+ ddfm_min_obs_idio: 5 # Minimum observations for idio AR(1) estimation
23
+
24
+ # Note: Series and blocks configuration should be provided separately
25
+ # via series config files or spec CSV, or merged with this config
26
+