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