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.
- {dfm_python-0.2.8 → dfm_python-0.3.0}/.gitignore +1 -1
- {dfm_python-0.2.8 → dfm_python-0.3.0}/PKG-INFO +426 -58
- {dfm_python-0.2.8 → dfm_python-0.3.0}/README.md +420 -47
- dfm_python-0.3.0/config/blocks/default.yaml +29 -0
- dfm_python-0.3.0/config/blocks/sample_spec.yaml +29 -0
- dfm_python-0.3.0/config/blocks/sample_spec2.yaml +29 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/config/default.yaml +3 -2
- dfm_python-0.3.0/config/experiment/exp1.yaml +14 -0
- dfm_python-0.3.0/config/experiment/exp2.yaml +14 -0
- dfm_python-0.3.0/config/experiment/exp3.yaml +14 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +29 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_194140_025b98_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_194423_013beb_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_194532_190712_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_195439_12eea1_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_200209_8b6463_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_200319_c62fea_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_200838_320d08_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_200934_65c460_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/series/spec_20251118_201046_8e7482_series.yaml +749 -0
- dfm_python-0.3.0/config/generated/spec_20251118_194140_025b98.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_194248_82a2a3.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_194423_013beb.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_194532_190712.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_195001_ef69d1.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_195110_eb12bc.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_195439_12eea1.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_195547_0ecebd.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_200209_8b6463.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_200319_c62fea.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_200722_7d6a26.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_200838_320d08.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_200934_65c460.yaml +29 -0
- dfm_python-0.3.0/config/generated/spec_20251118_201046_8e7482.yaml +29 -0
- dfm_python-0.3.0/config/series/default.yaml +749 -0
- dfm_python-0.3.0/config/series/sample_spec.yaml +749 -0
- dfm_python-0.3.0/config/series/sample_spec2.yaml +699 -0
- dfm_python-0.3.0/full_tutorial_example.ipynb +2223 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/pyproject.toml +12 -11
- dfm_python-0.3.0/src/adapters/__init__.py +52 -0
- dfm_python-0.3.0/src/adapters/data_view.py +230 -0
- dfm_python-0.3.0/src/adapters/database.py +161 -0
- dfm_python-0.3.0/src/adapters/model_result.py +200 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/__init__.py +13 -34
- dfm_python-0.3.0/src/dfm_python/api.py +1071 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/config.py +17 -83
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/config_sources.py +426 -98
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/diagnostics.py +39 -30
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/em.py +72 -52
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/helpers.py +345 -6
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/numeric.py +2 -2
- dfm_python-0.3.0/src/dfm_python/core/time.py +775 -0
- {dfm_python-0.2.8/src/dfm_python → dfm_python-0.3.0/src/dfm_python/core}/utils.py +70 -8
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/data.py +257 -103
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/dfm.py +71 -26
- dfm_python-0.3.0/src/dfm_python/nowcast.py +1519 -0
- dfm_python-0.3.0/src/test/test_api.py +466 -0
- dfm_python-0.3.0/src/test/test_core.py +339 -0
- dfm_python-0.3.0/src/test/test_integration.py +183 -0
- dfm_python-0.3.0/src/test/test_nowcasting.py +1186 -0
- dfm_python-0.3.0/tutorial/basic_tutorial.py +1069 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/uv.lock +520 -981
- dfm_python-0.2.8/config/blocks/default.yaml +0 -63
- dfm_python-0.2.8/config/series/default.yaml +0 -699
- dfm_python-0.2.8/full_tutorial_example.ipynb +0 -2014
- dfm_python-0.2.8/src/dfm_python/api.py +0 -420
- dfm_python-0.2.8/src/dfm_python/news.py +0 -782
- dfm_python-0.2.8/src/test/__init__.py +0 -1255
- dfm_python-0.2.8/src/test/test_api.py +0 -704
- dfm_python-0.2.8/src/test/test_dfm.py +0 -1397
- dfm_python-0.2.8/src/test/test_factor.py +0 -118
- dfm_python-0.2.8/src/test/test_numeric.py +0 -478
- dfm_python-0.2.8/src/test/test_stability.py +0 -222
- dfm_python-0.2.8/tutorial/basic_tutorial.py +0 -599
- dfm_python-0.2.8/tutorial/hydra_tutorial.py +0 -252
- {dfm_python-0.2.8 → dfm_python-0.3.0}/.python-version +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/LICENSE +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/MANIFEST.in +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/data/sample_data.csv +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/data/sample_spec.csv +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/core/__init__.py +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/dfm_python/kalman.py +0 -0
- {dfm_python-0.2.8 → dfm_python-0.3.0}/src/test/test_kalman.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.
|
|
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:
|
|
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:
|
|
35
|
-
Provides-Extra:
|
|
36
|
-
Requires-Dist:
|
|
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 (
|
|
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 (
|
|
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
|
-
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
#
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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=
|
|
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
|
-
- **`
|
|
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. **
|
|
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
|
-
|
|
649
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
-
|
|
694
|
-
-
|
|
695
|
-
-
|
|
696
|
-
-
|
|
697
|
-
-
|
|
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
|
-
|
|
1018
|
+
For database integration, set these environment variables:
|
|
700
1019
|
|
|
701
1020
|
```bash
|
|
702
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|