dfm-python 0.1.8__tar.gz → 0.2.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.0/PKG-INFO +647 -0
- dfm_python-0.2.0/README.md +603 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/config/blocks/default.yaml +0 -7
- {dfm_python-0.1.8 → dfm_python-0.2.0}/config/default.yaml +1 -1
- {dfm_python-0.1.8 → dfm_python-0.2.0}/pyproject.toml +2 -1
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/__init__.py +69 -31
- dfm_python-0.2.0/src/dfm_python/config.py +1295 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/data_loader.py +187 -225
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/dfm.py +248 -145
- dfm_python-0.2.0/src/dfm_python/dfm_api.py +630 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/kalman.py +4 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/news.py +62 -12
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/aggregation.py +7 -1
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/data_utils.py +27 -4
- dfm_python-0.2.0/src/test/test_api_edge_cases.py +295 -0
- dfm_python-0.2.0/src/test/test_config_sources.py +369 -0
- dfm_python-0.2.0/src/test/test_convenience_api.py +160 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_dfm.py +60 -17
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_kalman.py +4 -4
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/test_news.py +2 -1
- dfm_python-0.2.0/src/test/test_tutorials.py +288 -0
- dfm_python-0.2.0/tutorial/basic_tutorial.py +236 -0
- dfm_python-0.2.0/tutorial/hydra_tutorial.py +252 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/uv.lock +3 -1
- dfm_python-0.1.8/PKG-INFO +0 -343
- dfm_python-0.1.8/README.md +0 -300
- dfm_python-0.1.8/src/dfm_python/config.py +0 -746
- dfm_python-0.1.8/src/dfm_python/dfm_api.py +0 -328
- dfm_python-0.1.8/trash/simple_api_example.py +0 -41
- dfm_python-0.1.8/trash/test_dfm_edge_cases.py +0 -552
- dfm_python-0.1.8/trash/test_dfm_stress.py +0 -377
- dfm_python-0.1.8/trash/test_synthetic.py +0 -130
- dfm_python-0.1.8/tutorial/basic_tutorial.ipynb +0 -577
- dfm_python-0.1.8/tutorial/basic_tutorial.py +0 -342
- {dfm_python-0.1.8 → dfm_python-0.2.0}/.gitignore +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/.python-version +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/CHANGELOG.md +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/LICENSE +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/MANIFEST.in +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/config/series/default.yaml +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/data/sample_data.csv +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/data/sample_spec.csv +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/__init__.py +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/dfm_python/utils/__init__.py +0 -0
- {dfm_python-0.1.8 → dfm_python-0.2.0}/src/test/__init__.py +0 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dfm-python
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/yourusername/dfm-python
|
|
8
|
+
Project-URL: Issues, https://github.com/yourusername/dfm-python/issues
|
|
9
|
+
Author: DFM Python Contributors
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: dfm,dynamic-factor-model,econometrics,forecasting,nowcasting,time-series
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: hydra-core>=1.3.2
|
|
21
|
+
Requires-Dist: matplotlib>=3.10.7
|
|
22
|
+
Requires-Dist: numpy>=1.24.0
|
|
23
|
+
Requires-Dist: pandas>=2.0.0
|
|
24
|
+
Requires-Dist: scipy>=1.10.0
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: httpx>=0.24.0; extra == 'all'
|
|
27
|
+
Requires-Dist: hydra-core>=1.3.0; extra == 'all'
|
|
28
|
+
Requires-Dist: omegaconf>=2.3.0; extra == 'all'
|
|
29
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.11.0; extra == 'all'
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == 'all'
|
|
32
|
+
Requires-Dist: supabase>=2.0.0; extra == 'all'
|
|
33
|
+
Provides-Extra: database
|
|
34
|
+
Requires-Dist: httpx>=0.24.0; extra == 'database'
|
|
35
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'database'
|
|
36
|
+
Requires-Dist: pydantic-settings>=2.11.0; extra == 'database'
|
|
37
|
+
Requires-Dist: supabase>=2.0.0; extra == 'database'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: hydra
|
|
41
|
+
Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
|
|
42
|
+
Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# dfm-python: Dynamic Factor Models for Python
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/dfm-python/)
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
|
|
50
|
+
A Python implementation of **Dynamic Factor Models (DFM)** for nowcasting and forecasting high-dimensional time series. Implements clock-based synchronization and tent kernel aggregation for mixed-frequency data.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual (no daily/weekly)
|
|
55
|
+
- **Clock-based framework**: All factors evolve at a common clock frequency
|
|
56
|
+
- **Block structure**: Flexible factor organization (global, sector-specific)
|
|
57
|
+
- **Robust missing data**: Spline interpolation and Kalman filter handling
|
|
58
|
+
- **News decomposition**: Attribute forecast changes to data releases
|
|
59
|
+
- **Generic design**: YAML configs or direct object creation, extensible via adapters
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install dfm-python
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Requirements**: Python >= 3.12, numpy >= 1.24.0, pandas >= 2.0.0, scipy >= 1.10.0
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
### Class-Based API (Recommended)
|
|
72
|
+
|
|
73
|
+
The package is built around a `DFM` class that provides both core estimation (`fit()`) and convenience methods (`load_config()`, `load_data()`, `train()`, `predict()`, `plot()`).
|
|
74
|
+
|
|
75
|
+
#### Direct Class Usage
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from dfm_python.dfm import DFM
|
|
79
|
+
from dfm_python.config import DFMConfig, SeriesConfig, BlockConfig
|
|
80
|
+
|
|
81
|
+
# Create config
|
|
82
|
+
config = DFMConfig(
|
|
83
|
+
series=[SeriesConfig(frequency='m', transformation='lin', blocks=[1], series_id='s1')],
|
|
84
|
+
blocks={'Block_Global': BlockConfig(factors=1, clock='m')}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Create model and fit
|
|
88
|
+
model = DFM()
|
|
89
|
+
result = model.fit(X, config, max_iter=100)
|
|
90
|
+
factors = result.Z
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Module-Level Convenience API (Recommended for Most Users)
|
|
94
|
+
|
|
95
|
+
The package provides a singleton instance and convenient constructors for easy usage:
|
|
96
|
+
|
|
97
|
+
#### 1. From YAML (Simplest)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import dfm_python as dfm
|
|
101
|
+
|
|
102
|
+
# Load from YAML file
|
|
103
|
+
dfm.from_yaml('config/default.yaml')
|
|
104
|
+
|
|
105
|
+
# Load data, train, predict, plot
|
|
106
|
+
dfm.load_data('data/sample_data.csv', sample_start='2015-01-01', sample_end='2022-12-31')
|
|
107
|
+
dfm.train(max_iter=5000)
|
|
108
|
+
X_forecast, Z_forecast = dfm.predict(horizon=12)
|
|
109
|
+
dfm.plot(kind='factor', factor_index=0, forecast_horizon=12, save_path='outputs/factor_forecast.png')
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### 2. From Spec CSV + Params (Ideal for CSV-based series definitions)
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import dfm_python as dfm
|
|
116
|
+
from dfm_python.config import Params
|
|
117
|
+
|
|
118
|
+
# Create Params object with main settings
|
|
119
|
+
params = Params(
|
|
120
|
+
max_iter=5000,
|
|
121
|
+
threshold=1e-5,
|
|
122
|
+
regularization_scale=1e-5,
|
|
123
|
+
damping_factor=0.8
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Load from spec CSV (series definitions) + Params (main settings)
|
|
127
|
+
dfm.from_spec('data/sample_spec.csv', params=params)
|
|
128
|
+
|
|
129
|
+
# Or use defaults
|
|
130
|
+
dfm.from_spec('data/sample_spec.csv') # Uses Params() defaults
|
|
131
|
+
|
|
132
|
+
# Load data, train, predict
|
|
133
|
+
dfm.load_data('data/sample_data.csv')
|
|
134
|
+
dfm.train()
|
|
135
|
+
X_forecast, Z_forecast = dfm.predict(horizon=12)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### 3. From Dictionary (Programmatic)
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import dfm_python as dfm
|
|
142
|
+
|
|
143
|
+
config_dict = {
|
|
144
|
+
'clock': 'm',
|
|
145
|
+
'max_iter': 5000,
|
|
146
|
+
'blocks': {'Block_Global': {'factors': 1, 'clock': 'm'}},
|
|
147
|
+
'series': [{'series_id': 's1', 'frequency': 'm', 'transformation': 'lin', 'blocks': [0]}]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dfm.from_dict(config_dict)
|
|
151
|
+
dfm.load_data('data/sample_data.csv')
|
|
152
|
+
dfm.train()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Access Results
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Access results (DFMResult)
|
|
159
|
+
result = dfm.get_result()
|
|
160
|
+
factors = result.Z # (T × m) Smoothed factors
|
|
161
|
+
loadings = result.C # (N × m) Factor loadings
|
|
162
|
+
smoothed = result.X_sm # (T × N) Smoothed data
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Configuration Sources
|
|
166
|
+
|
|
167
|
+
The package supports multiple configuration sources. Choose based on your needs:
|
|
168
|
+
|
|
169
|
+
#### 1. Hydra Decorator (Recommended for Hydra users)
|
|
170
|
+
|
|
171
|
+
Use the `@hydra.main` decorator for powerful configuration composition and CLI overrides:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import hydra
|
|
175
|
+
from hydra.utils import get_original_cwd
|
|
176
|
+
from omegaconf import DictConfig
|
|
177
|
+
import dfm_python as dfm
|
|
178
|
+
from pathlib import Path
|
|
179
|
+
|
|
180
|
+
@hydra.main(config_path="../config", config_name="default", version_base="1.3")
|
|
181
|
+
def main(cfg: DictConfig) -> None:
|
|
182
|
+
# Access config values explicitly via cfg.*
|
|
183
|
+
print(f"max_iter: {cfg.max_iter}")
|
|
184
|
+
print(f"threshold: {cfg.threshold}")
|
|
185
|
+
print(f"blocks.Block_Global.factors: {cfg.blocks.Block_Global.factors}")
|
|
186
|
+
|
|
187
|
+
# Load configuration
|
|
188
|
+
dfm.load_config(hydra=cfg)
|
|
189
|
+
|
|
190
|
+
# Resolve paths relative to original working directory
|
|
191
|
+
original_cwd = Path(get_original_cwd())
|
|
192
|
+
data_path = original_cwd / "data" / "sample_data.csv"
|
|
193
|
+
|
|
194
|
+
# Load data, train, predict
|
|
195
|
+
dfm.load_data(str(data_path), sample_start="2021-01-01", sample_end="2022-12-31")
|
|
196
|
+
dfm.train(max_iter=cfg.max_iter) # Use cfg.max_iter explicitly
|
|
197
|
+
X_forecast, Z_forecast = dfm.predict(horizon=cfg.get('forecast_horizon', 12))
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
main()
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**CLI Overrides**:
|
|
204
|
+
```bash
|
|
205
|
+
# Override parameters
|
|
206
|
+
python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4
|
|
207
|
+
|
|
208
|
+
# Override block settings
|
|
209
|
+
python tutorial/hydra_tutorial.py blocks.Block_Global.factors=2
|
|
210
|
+
|
|
211
|
+
# Multiple overrides
|
|
212
|
+
python tutorial/hydra_tutorial.py max_iter=10 damping_factor=0.9 regularization_scale=1e-6
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
See `tutorial/hydra_tutorial.py` for a complete example.
|
|
216
|
+
|
|
217
|
+
#### 2. Spec CSV + Params (Ideal for CSV-based workflows)
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import dfm_python as dfm
|
|
221
|
+
from dfm_python.config import Params
|
|
222
|
+
|
|
223
|
+
# Create Params object with all exposed parameters
|
|
224
|
+
params = Params(
|
|
225
|
+
max_iter=5000,
|
|
226
|
+
threshold=1e-5,
|
|
227
|
+
nan_method=2,
|
|
228
|
+
clock='m',
|
|
229
|
+
regularization_scale=1e-5,
|
|
230
|
+
damping_factor=0.8,
|
|
231
|
+
# ... all other parameters
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Load from spec CSV (series definitions) + Params (main settings)
|
|
235
|
+
dfm.from_spec('data/sample_spec.csv', params=params)
|
|
236
|
+
|
|
237
|
+
# Or use defaults
|
|
238
|
+
dfm.from_spec('data/sample_spec.csv') # Uses Params() defaults
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The `Params` dataclass contains all global estimation parameters:
|
|
242
|
+
- **Estimation**: `ar_lag`, `threshold`, `max_iter`, `nan_method`, `nan_k`, `clock`
|
|
243
|
+
- **AR Clipping**: `clip_ar_coefficients`, `ar_clip_min`, `ar_clip_max`, `warn_on_ar_clip`
|
|
244
|
+
- **Data Clipping**: `clip_data_values`, `data_clip_threshold`, `warn_on_data_clip`
|
|
245
|
+
- **Regularization**: `use_regularization`, `regularization_scale`, `min_eigenvalue`, `max_eigenvalue`, `warn_on_regularization`
|
|
246
|
+
- **Damped Updates**: `use_damped_updates`, `damping_factor`, `warn_on_damped_update`
|
|
247
|
+
|
|
248
|
+
See `tutorial/basic_tutorial.py` for a complete example with all parameters exposed.
|
|
249
|
+
|
|
250
|
+
#### 3. YAML File (Simplest)
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
import dfm_python as dfm
|
|
254
|
+
|
|
255
|
+
# Load from YAML file
|
|
256
|
+
dfm.from_yaml('config/default.yaml')
|
|
257
|
+
|
|
258
|
+
# Or use the generic load_config
|
|
259
|
+
dfm.load_config(yaml='config/default.yaml')
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### 4. Dictionary (Programmatic)
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import dfm_python as dfm
|
|
266
|
+
|
|
267
|
+
config_dict = {
|
|
268
|
+
'clock': 'm',
|
|
269
|
+
'max_iter': 5000,
|
|
270
|
+
'threshold': 1e-5,
|
|
271
|
+
'blocks': {'Block_Global': {'factors': 1, 'clock': 'm'}},
|
|
272
|
+
'series': [{'series_id': 's1', 'frequency': 'm', 'transformation': 'lin', 'blocks': [0]}]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
dfm.from_dict(config_dict)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### 5. Spec DataFrame (In-memory)
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
import pandas as pd
|
|
282
|
+
import dfm_python as dfm
|
|
283
|
+
from dfm_python.config import Params
|
|
284
|
+
|
|
285
|
+
# Load spec from DataFrame (useful when already in memory)
|
|
286
|
+
spec_df = pd.read_csv('data/sample_spec.csv')
|
|
287
|
+
params = Params(max_iter=100)
|
|
288
|
+
|
|
289
|
+
dfm.from_spec_df(spec_df, params=params)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### Functional API (Deprecated)
|
|
293
|
+
|
|
294
|
+
The `dfm()` function is deprecated but still supported for backward compatibility:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from dfm_python import dfm, load_config, load_data
|
|
298
|
+
|
|
299
|
+
config = load_config('config.yaml')
|
|
300
|
+
X, Time, _ = load_data('data.csv', config)
|
|
301
|
+
result = dfm(X, config, max_iter=100) # Deprecated: emits DeprecationWarning
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Note**: For new code, prefer:
|
|
305
|
+
- `DFM().fit()` for direct class usage
|
|
306
|
+
- `dfm.load_config().train()` for module-level convenience API
|
|
307
|
+
|
|
308
|
+
#### Legacy Config Loading (Still Supported)
|
|
309
|
+
|
|
310
|
+
All the original `load_config()` methods still work:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
import dfm_python as dfm
|
|
314
|
+
|
|
315
|
+
# All these still work (backward compatible)
|
|
316
|
+
dfm.load_config(yaml='config/default.yaml')
|
|
317
|
+
dfm.load_config(mapping={...})
|
|
318
|
+
dfm.load_config(spec='data/spec.csv')
|
|
319
|
+
dfm.load_config(hydra=hydra_cfg)
|
|
320
|
+
dfm.load_config(base='config/default.yaml', override='data/spec.csv')
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### ConfigSource Adapters (Advanced)
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from dfm_python.config_sources import make_config_source, YamlSource, DictSource, SpecCSVSource, HydraSource
|
|
327
|
+
|
|
328
|
+
# Factory creates the right adapter based on input
|
|
329
|
+
source = make_config_source('config/default.yaml') # YAML
|
|
330
|
+
source = make_config_source(mapping={'series': {...}}) # Dict
|
|
331
|
+
source = make_config_source(spec='data/spec.csv') # Spec CSV
|
|
332
|
+
source = make_config_source(hydra=hydra_cfg) # Hydra DictConfig
|
|
333
|
+
config = source.load() # -> DFMConfig
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Merging Configurations
|
|
337
|
+
|
|
338
|
+
Combine base config (YAML) with series from spec CSV:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
import dfm_python as dfm
|
|
342
|
+
|
|
343
|
+
# Base config provides main settings, spec CSV provides series
|
|
344
|
+
dfm.load_config(
|
|
345
|
+
base='config/default.yaml',
|
|
346
|
+
override='data/sample_spec.csv'
|
|
347
|
+
)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Low-Level API (Advanced)
|
|
351
|
+
|
|
352
|
+
For more control, use the `DFM` class directly:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from dfm_python.dfm import DFM
|
|
356
|
+
from dfm_python import DFMConfig, SeriesConfig, load_data, BlockConfig
|
|
357
|
+
|
|
358
|
+
# Create configuration programmatically
|
|
359
|
+
config = DFMConfig(
|
|
360
|
+
series=[
|
|
361
|
+
SeriesConfig(
|
|
362
|
+
series_id='gdp_real',
|
|
363
|
+
series_name='Real GDP',
|
|
364
|
+
frequency='q',
|
|
365
|
+
transformation='pca',
|
|
366
|
+
category='National Accounts',
|
|
367
|
+
units='Index',
|
|
368
|
+
blocks=[1] # Loads on first block (global)
|
|
369
|
+
)
|
|
370
|
+
],
|
|
371
|
+
blocks={'Block_Global': BlockConfig(factors=1, clock='m')},
|
|
372
|
+
clock='m',
|
|
373
|
+
threshold=1e-5,
|
|
374
|
+
max_iter=5000
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Load data and estimate using DFM class
|
|
378
|
+
X, Time, _ = load_data('data.csv', config)
|
|
379
|
+
model = DFM()
|
|
380
|
+
result = model.fit(X, config) # Recommended: use DFM().fit()
|
|
381
|
+
# Or use deprecated functional API:
|
|
382
|
+
# result = dfm(X, config) # Deprecated: emits DeprecationWarning
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Configuration
|
|
386
|
+
|
|
387
|
+
### YAML Format
|
|
388
|
+
|
|
389
|
+
```yaml
|
|
390
|
+
dfm:
|
|
391
|
+
clock: "m" # Clock frequency (base for all factors)
|
|
392
|
+
threshold: 1e-5 # EM convergence threshold
|
|
393
|
+
max_iter: 5000 # Maximum EM iterations
|
|
394
|
+
ar_lag: 1 # AR lag for factors
|
|
395
|
+
|
|
396
|
+
series:
|
|
397
|
+
gdp_real:
|
|
398
|
+
series_name: "Real GDP"
|
|
399
|
+
frequency: "q"
|
|
400
|
+
transformation: "pca"
|
|
401
|
+
category: "National Accounts"
|
|
402
|
+
units: "Index"
|
|
403
|
+
blocks: [Global]
|
|
404
|
+
|
|
405
|
+
consumption:
|
|
406
|
+
series_name: "Consumption"
|
|
407
|
+
frequency: "m"
|
|
408
|
+
transformation: "pch"
|
|
409
|
+
category: "Consumption"
|
|
410
|
+
units: "Index"
|
|
411
|
+
blocks: [Global]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Key Parameters
|
|
415
|
+
|
|
416
|
+
- **clock**: Base frequency for latent factors (typically "m" for monthly)
|
|
417
|
+
- **threshold**: EM convergence criterion (default: 1e-5)
|
|
418
|
+
- **max_iter**: Maximum EM iterations (default: 5000)
|
|
419
|
+
- **ar_lag**: Autoregressive lag for factors (default: 1)
|
|
420
|
+
|
|
421
|
+
### Numerical Stability
|
|
422
|
+
|
|
423
|
+
All numerical stability techniques are configurable and transparent:
|
|
424
|
+
|
|
425
|
+
```yaml
|
|
426
|
+
dfm:
|
|
427
|
+
clip_ar_coefficients: true # Ensure stationarity
|
|
428
|
+
clip_data_values: true # Clip extreme outliers
|
|
429
|
+
use_regularization: true # Prevent ill-conditioned matrices
|
|
430
|
+
regularization_scale: 1e-5 # Scale relative to matrix trace (default 1e-5)
|
|
431
|
+
use_damped_updates: true # Prevent likelihood decreases
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Warnings are logged when techniques are applied. All parameters can be disabled for research purposes.
|
|
435
|
+
|
|
436
|
+
Note:
|
|
437
|
+
- Default `regularization_scale` is 1e-5 across code and YAML.
|
|
438
|
+
- You can override via Hydra CLI (see above).
|
|
439
|
+
|
|
440
|
+
## Data Format
|
|
441
|
+
|
|
442
|
+
### File-Based (CSV)
|
|
443
|
+
|
|
444
|
+
Data file with series as columns and dates as rows:
|
|
445
|
+
|
|
446
|
+
```csv
|
|
447
|
+
Date,gdp_real,consumption,investment
|
|
448
|
+
2000-01-01,,98.5,95.0
|
|
449
|
+
2000-02-01,,98.7,95.2
|
|
450
|
+
2000-03-01,100.5,99.0,95.5
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Requirements**:
|
|
454
|
+
- First column: `Date` (YYYY-MM-DD format)
|
|
455
|
+
- Column names: Must match `series_id` in configuration
|
|
456
|
+
- Missing values: Empty cells or NaN
|
|
457
|
+
- Mixed frequencies: Quarterly series only at quarter-end months
|
|
458
|
+
|
|
459
|
+
**Frequency Support**:
|
|
460
|
+
- **Supported**: Monthly (m), Quarterly (q), Semi-annual (sa), Annual (a)
|
|
461
|
+
- **Not supported**: Daily (d), Weekly (w) - raises ValueError
|
|
462
|
+
- All series must have frequency ≤ clock frequency
|
|
463
|
+
|
|
464
|
+
### Database-Backed
|
|
465
|
+
|
|
466
|
+
For production applications, implement adapters that return:
|
|
467
|
+
- `X`: Transformed data matrix (T × N)
|
|
468
|
+
- `Time`: Time index (pandas DatetimeIndex)
|
|
469
|
+
- `Z`: Original untransformed data (T × N)
|
|
470
|
+
|
|
471
|
+
## Usage Examples
|
|
472
|
+
|
|
473
|
+
### Basic Estimation
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
from dfm_python import load_config, load_data, dfm
|
|
477
|
+
|
|
478
|
+
config = load_config('config.yaml')
|
|
479
|
+
X, Time, _ = load_data('data.csv', config)
|
|
480
|
+
result = dfm(X, config)
|
|
481
|
+
|
|
482
|
+
# Extract common factor
|
|
483
|
+
common_factor = result.Z[:, 0]
|
|
484
|
+
|
|
485
|
+
# Model fit
|
|
486
|
+
rmse = result.rmse
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### News Decomposition
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
from dfm_python import news_dfm
|
|
493
|
+
|
|
494
|
+
# Estimate on old and new data
|
|
495
|
+
result_old = dfm(X_old, config)
|
|
496
|
+
result_new = dfm(X_new, config)
|
|
497
|
+
|
|
498
|
+
# Decompose news
|
|
499
|
+
y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
|
|
500
|
+
X_old, result_old, t_fcst=100, v_news=0
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
# Forecast update
|
|
504
|
+
forecast_update = y_new - y_old
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## API Reference
|
|
508
|
+
|
|
509
|
+
### `load_config(configfile) -> DFMConfig`
|
|
510
|
+
|
|
511
|
+
Load configuration from YAML file or return existing DFMConfig object.
|
|
512
|
+
|
|
513
|
+
**Parameters**:
|
|
514
|
+
- `configfile`: Path to YAML file (.yaml, .yml) or `DFMConfig` object
|
|
515
|
+
|
|
516
|
+
**Returns**: `DFMConfig` object
|
|
517
|
+
|
|
518
|
+
### `load_data(datafile, config, sample_start=None) -> Tuple[np.ndarray, pd.DatetimeIndex, np.ndarray]`
|
|
519
|
+
|
|
520
|
+
Load and transform time series data.
|
|
521
|
+
|
|
522
|
+
**Parameters**:
|
|
523
|
+
- `datafile`: Path to data file (CSV supported)
|
|
524
|
+
- `config`: `DFMConfig` object
|
|
525
|
+
- `sample_start`: Optional start date (YYYY-MM-DD) to filter data
|
|
526
|
+
|
|
527
|
+
**Returns**: `(X, Time, Z)` where:
|
|
528
|
+
- `X`: Transformed data matrix (T × N)
|
|
529
|
+
- `Time`: Time index (DatetimeIndex)
|
|
530
|
+
- `Z`: Original untransformed data (T × N)
|
|
531
|
+
|
|
532
|
+
### `DFM.fit(X, config, threshold=None, max_iter=None, ...) -> DFMResult`
|
|
533
|
+
|
|
534
|
+
**Recommended**: Estimate Dynamic Factor Model using EM algorithm via the `DFM` class.
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
from dfm_python.dfm import DFM
|
|
538
|
+
model = DFM()
|
|
539
|
+
result = model.fit(X, config, max_iter=100)
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### `dfm(X, config, threshold=None, max_iter=None) -> DFMResult` (Deprecated)
|
|
543
|
+
|
|
544
|
+
**Deprecated**: Use `DFM().fit()` instead. This function emits a `DeprecationWarning`.
|
|
545
|
+
|
|
546
|
+
Estimate Dynamic Factor Model using EM algorithm.
|
|
547
|
+
|
|
548
|
+
**Parameters**:
|
|
549
|
+
- `X`: Data matrix (T × N) with possible missing values
|
|
550
|
+
- `config`: `DFMConfig` object
|
|
551
|
+
- `threshold`: Convergence threshold (default: from config)
|
|
552
|
+
- `max_iter`: Maximum iterations (default: from config)
|
|
553
|
+
|
|
554
|
+
**Returns**: `DFMResult` object
|
|
555
|
+
|
|
556
|
+
### `DFMResult` Object
|
|
557
|
+
|
|
558
|
+
Contains all estimation outputs:
|
|
559
|
+
|
|
560
|
+
- **Factors**: `Z` (T × m), `C` (N × m)
|
|
561
|
+
- **Parameters**: `A` (m × m), `Q` (m × m), `R` (N × N)
|
|
562
|
+
- **Smoothed Data**: `X_sm` (T × N), `x_sm` (T × N)
|
|
563
|
+
- **Initial Conditions**: `Z_0` (m,), `V_0` (m × m)
|
|
564
|
+
- **Standardization**: `Mx` (N,), `Wx` (N,)
|
|
565
|
+
- **Convergence**: `converged`, `num_iter`, `loglik`
|
|
566
|
+
- **Model Fit**: `rmse`, `rmse_per_series`
|
|
567
|
+
|
|
568
|
+
## Architecture
|
|
569
|
+
|
|
570
|
+
**Core Modules**:
|
|
571
|
+
- `config.py`: Configuration management
|
|
572
|
+
- `data_loader.py`: Data loading and transformation
|
|
573
|
+
- `dfm.py`: Core estimation (EM algorithm)
|
|
574
|
+
- `kalman.py`: Kalman filter and smoother
|
|
575
|
+
- `news.py`: News decomposition
|
|
576
|
+
|
|
577
|
+
**Design Principles**:
|
|
578
|
+
- Generic core (no application-specific code)
|
|
579
|
+
- Flexible configuration (YAML or direct object creation)
|
|
580
|
+
- Extensible via application-specific adapters
|
|
581
|
+
- Robust error handling and numerical stability
|
|
582
|
+
- Hydra-friendly: tutorial uses `@hydra.main` with `get_original_cwd()`; paths remain stable
|
|
583
|
+
- High-level API: `dfm.predict()`, `dfm.plot()` for concise forecasting and visualization
|
|
584
|
+
|
|
585
|
+
### Tutorials
|
|
586
|
+
|
|
587
|
+
Two comprehensive tutorials demonstrate different approaches:
|
|
588
|
+
|
|
589
|
+
1. **`tutorial/basic_tutorial.py`** - Spec CSV + Params approach
|
|
590
|
+
- No Hydra dependency
|
|
591
|
+
- Uses `Params` dataclass for main settings
|
|
592
|
+
- Spec CSV defines all series and block memberships
|
|
593
|
+
- All parameters exposed as CLI arguments
|
|
594
|
+
- Run: `python tutorial/basic_tutorial.py --spec data/sample_spec.csv --data data/sample_data.csv`
|
|
595
|
+
|
|
596
|
+
2. **`tutorial/hydra_tutorial.py`** - Hydra decorator approach
|
|
597
|
+
- Uses `@hydra.main` decorator
|
|
598
|
+
- Access config via `cfg.*` (e.g., `cfg.max_iter`, `cfg.blocks.Block_Global.factors`)
|
|
599
|
+
- CLI overrides for experimentation
|
|
600
|
+
- Run: `python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4`
|
|
601
|
+
|
|
602
|
+
Both tutorials demonstrate:
|
|
603
|
+
- Configuration loading
|
|
604
|
+
- Data loading with date windowing
|
|
605
|
+
- Training with explicit parameters
|
|
606
|
+
- Forecasting and visualization
|
|
607
|
+
- Saving results
|
|
608
|
+
|
|
609
|
+
See the tutorial files for complete examples with all features.
|
|
610
|
+
|
|
611
|
+
## Troubleshooting
|
|
612
|
+
|
|
613
|
+
**Convergence Issues**:
|
|
614
|
+
- Increase `max_iter` (try 10000)
|
|
615
|
+
- Relax `threshold` (try 1e-3)
|
|
616
|
+
- Check data quality and missing data patterns
|
|
617
|
+
|
|
618
|
+
**Dimension Mismatch**:
|
|
619
|
+
- Ensure `series_id` in config matches data column names
|
|
620
|
+
- Verify block structure consistency
|
|
621
|
+
- Check frequency codes are valid
|
|
622
|
+
|
|
623
|
+
**Numerical Instability**:
|
|
624
|
+
- Monitor warnings for stability techniques
|
|
625
|
+
- Adjust thresholds based on data characteristics
|
|
626
|
+
- Investigate data quality if techniques frequently needed
|
|
627
|
+
|
|
628
|
+
## License
|
|
629
|
+
|
|
630
|
+
MIT License
|
|
631
|
+
|
|
632
|
+
## Citation
|
|
633
|
+
|
|
634
|
+
```bibtex
|
|
635
|
+
@software{dfm-python,
|
|
636
|
+
title = {dfm-python: Dynamic Factor Models for Nowcasting and Forecasting},
|
|
637
|
+
author = {DFM Python Contributors},
|
|
638
|
+
year = {2025},
|
|
639
|
+
url = {https://pypi.org/project/dfm-python/}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
**Package Status**: Stable
|
|
646
|
+
**PyPI**: https://pypi.org/project/dfm-python/
|
|
647
|
+
**Python**: 3.12+
|