dfm-python 0.1.1__tar.gz → 0.1.3__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.1.3/PKG-INFO +1010 -0
- dfm_python-0.1.3/README.md +968 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/config/example_config.yaml +60 -30
- dfm_python-0.1.3/data/sample_data.csv +193 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/pyproject.toml +1 -1
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/__init__.py +1 -1
- dfm_python-0.1.3/src/dfm_python/__init__.py +60 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/config.py +70 -23
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/data_loader.py +59 -10
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/dfm.py +237 -303
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/kalman.py +68 -16
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/news.py +25 -3
- dfm_python-0.1.3/src/dfm_python/utils/__init__.py +27 -0
- dfm_python-0.1.3/src/dfm_python/utils/aggregation.py +258 -0
- {dfm_python-0.1.1/src → dfm_python-0.1.3/src/dfm_python}/utils/data_utils.py +11 -1
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_dfm.py +6 -6
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_kalman.py +13 -4
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_news.py +1 -1
- dfm_python-0.1.3/test_comprehensive.py +510 -0
- dfm_python-0.1.1/PKG-INFO +0 -395
- dfm_python-0.1.1/README.md +0 -353
- dfm_python-0.1.1/data/sample_data.csv +0 -133
- dfm_python-0.1.1/src/dfm_python/__init__.py +0 -28
- dfm_python-0.1.1/src/utils/__init__.py +0 -12
- {dfm_python-0.1.1 → dfm_python-0.1.3}/.gitignore +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/.python-version +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/LICENSE +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/MANIFEST.in +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/spec/example_spec.csv +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/__init__.py +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_synthetic.py +0 -0
- {dfm_python-0.1.1 → dfm_python-0.1.3}/uv.lock +0 -0
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dfm-python
|
|
3
|
+
Version: 0.1.3
|
|
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: numpy>=1.24.0
|
|
21
|
+
Requires-Dist: pandas>=2.0.0
|
|
22
|
+
Requires-Dist: scipy>=1.10.0
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: httpx>=0.24.0; extra == 'all'
|
|
25
|
+
Requires-Dist: hydra-core>=1.3.0; extra == 'all'
|
|
26
|
+
Requires-Dist: omegaconf>=2.3.0; extra == 'all'
|
|
27
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.11.0; extra == 'all'
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == 'all'
|
|
30
|
+
Requires-Dist: supabase>=2.0.0; extra == 'all'
|
|
31
|
+
Provides-Extra: database
|
|
32
|
+
Requires-Dist: httpx>=0.24.0; extra == 'database'
|
|
33
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == 'database'
|
|
34
|
+
Requires-Dist: pydantic-settings>=2.11.0; extra == 'database'
|
|
35
|
+
Requires-Dist: supabase>=2.0.0; extra == 'database'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
38
|
+
Provides-Extra: hydra
|
|
39
|
+
Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
|
|
40
|
+
Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# dfm-python: Dynamic Factor Models for Python
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/dfm-python/)
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
|
|
48
|
+
A comprehensive, production-ready Python implementation of **Dynamic Factor Models (DFM)** for nowcasting and forecasting economic and financial time series. This package implements the FRBNY (Federal Reserve Bank of New York) approach to mixed-frequency DFMs with clock-based synchronization and tent kernel aggregation.
|
|
49
|
+
|
|
50
|
+
## Table of Contents
|
|
51
|
+
|
|
52
|
+
- [Overview](#overview)
|
|
53
|
+
- [Key Features](#key-features)
|
|
54
|
+
- [Installation](#installation)
|
|
55
|
+
- [Quick Start](#quick-start)
|
|
56
|
+
- [Package Overview: What We Provide](#package-overview-what-we-provide)
|
|
57
|
+
- [Detailed Inputs and Outputs](#detailed-inputs-and-outputs)
|
|
58
|
+
- [Configuration Guide](#configuration-guide)
|
|
59
|
+
- [Data Format Requirements](#data-format-requirements)
|
|
60
|
+
- [Usage Examples](#usage-examples)
|
|
61
|
+
- [API Reference](#api-reference)
|
|
62
|
+
- [Advanced Features](#advanced-features)
|
|
63
|
+
- [Clock-Based Mixed-Frequency Framework](#clock-based-mixed-frequency-framework)
|
|
64
|
+
- [Troubleshooting](#troubleshooting)
|
|
65
|
+
- [Testing](#testing)
|
|
66
|
+
- [Architecture](#architecture)
|
|
67
|
+
- [Contributing](#contributing)
|
|
68
|
+
- [License](#license)
|
|
69
|
+
|
|
70
|
+
## Overview
|
|
71
|
+
|
|
72
|
+
Dynamic Factor Models (DFM) are powerful econometric tools for analyzing high-dimensional time series data by extracting common factors that drive multiple observed series. This package provides a complete implementation suitable for:
|
|
73
|
+
|
|
74
|
+
- **Nowcasting**: Forecasting the present using partial information
|
|
75
|
+
- **Forecasting**: Multi-step ahead predictions
|
|
76
|
+
- **Factor Analysis**: Extracting common trends from multiple series
|
|
77
|
+
- **News Decomposition**: Understanding how new data releases affect forecasts
|
|
78
|
+
|
|
79
|
+
### What is a Dynamic Factor Model?
|
|
80
|
+
|
|
81
|
+
A DFM models observed time series as:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
y_t = C × Z_t + e_t (Observation equation)
|
|
85
|
+
Z_t = A × Z_{t-1} + v_t (State equation)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Where:
|
|
89
|
+
- `y_t`: Observed time series at time t (n × 1 vector)
|
|
90
|
+
- `Z_t`: Unobserved common factors (m × 1 vector)
|
|
91
|
+
- `C`: Factor loadings matrix (n × m) - how series relate to factors
|
|
92
|
+
- `A`: Transition matrix (m × m) - how factors evolve over time
|
|
93
|
+
- `e_t`, `v_t`: Error terms (observation and state innovations)
|
|
94
|
+
|
|
95
|
+
The model extracts common patterns across multiple series while accounting for their individual dynamics and handling missing data robustly.
|
|
96
|
+
|
|
97
|
+
## Key Features
|
|
98
|
+
|
|
99
|
+
### 🎯 Core Capabilities
|
|
100
|
+
|
|
101
|
+
- **Mixed-Frequency Data**: Seamlessly handle daily, weekly, monthly, quarterly, semi-annual, and annual series simultaneously
|
|
102
|
+
- **Clock-Based Synchronization**: All latent factors evolve at a common "clock" frequency (typically monthly)
|
|
103
|
+
- **Tent Kernel Aggregation**: Deterministic tent kernels map lower-frequency observations to higher-frequency latent states
|
|
104
|
+
- **Robust Missing Data**: Automatic interpolation and handling of missing observations (tested with up to 50% missing data)
|
|
105
|
+
- **Block Structure**: Organize series into logical blocks (e.g., Global, Consumption, Investment factors)
|
|
106
|
+
- **Flexible Configuration**: Support for both YAML and CSV configuration formats
|
|
107
|
+
- **News Decomposition**: Decompose forecast updates into contributions from individual data releases
|
|
108
|
+
- **Production Ready**: Comprehensive error handling, logging, validation, and numerical stability
|
|
109
|
+
|
|
110
|
+
### 📊 Supported Frequencies
|
|
111
|
+
|
|
112
|
+
- **Daily** (`d`): High-frequency data (e.g., stock prices, exchange rates)
|
|
113
|
+
- **Weekly** (`w`): Weekly indicators (e.g., unemployment claims)
|
|
114
|
+
- **Monthly** (`m`): Standard economic indicators (e.g., unemployment, industrial production)
|
|
115
|
+
- **Quarterly** (`q`): Low-frequency aggregates (e.g., GDP, national accounts)
|
|
116
|
+
- **Semi-annual** (`sa`): Semi-annual aggregates
|
|
117
|
+
- **Annual** (`a`): Annual aggregates
|
|
118
|
+
|
|
119
|
+
### 🔧 Data Transformations
|
|
120
|
+
|
|
121
|
+
The package supports various transformations to ensure stationarity:
|
|
122
|
+
|
|
123
|
+
- `lin`: Levels (no transformation)
|
|
124
|
+
- `chg`: First difference (change)
|
|
125
|
+
- `ch1`: Year-over-year change
|
|
126
|
+
- `pch`: Percent change
|
|
127
|
+
- `pc1`: Year-over-year percent change
|
|
128
|
+
- `pca`: Percent change at annual rate
|
|
129
|
+
- `cch`: Continuously compounded rate of change
|
|
130
|
+
- `cca`: Continuously compounded annual rate
|
|
131
|
+
- `log`: Natural logarithm
|
|
132
|
+
|
|
133
|
+
## Installation
|
|
134
|
+
|
|
135
|
+
### Basic Installation
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pip install dfm-python
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Development Installation
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
git clone https://github.com/your-repo/dfm-python.git
|
|
145
|
+
cd dfm-python
|
|
146
|
+
pip install -e .
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Requirements
|
|
150
|
+
|
|
151
|
+
- Python >= 3.12
|
|
152
|
+
- numpy >= 1.24.0
|
|
153
|
+
- pandas >= 2.0.0
|
|
154
|
+
- scipy >= 1.10.0
|
|
155
|
+
|
|
156
|
+
### Optional Dependencies
|
|
157
|
+
|
|
158
|
+
- **hydra-core** >= 1.3.0 (for Hydra configuration support)
|
|
159
|
+
- **omegaconf** >= 2.3.0 (for YAML configuration)
|
|
160
|
+
- **pytest** >= 7.0.0 (for testing)
|
|
161
|
+
|
|
162
|
+
## Quick Start
|
|
163
|
+
|
|
164
|
+
### Minimal Example
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from dfm_python import load_config, load_data, dfm
|
|
168
|
+
import pandas as pd
|
|
169
|
+
|
|
170
|
+
# 1. Load configuration
|
|
171
|
+
config = load_config('config.yaml')
|
|
172
|
+
|
|
173
|
+
# 2. Load and prepare data
|
|
174
|
+
X, Time, Z = load_data('data.csv', config, sample_start=pd.Timestamp('2000-01-01'))
|
|
175
|
+
|
|
176
|
+
# 3. Estimate the model
|
|
177
|
+
result = dfm(X, config, threshold=1e-4)
|
|
178
|
+
|
|
179
|
+
# 4. Access results
|
|
180
|
+
factors = result.Z # Extracted factors (T × m)
|
|
181
|
+
loadings = result.C # Factor loadings (N × m)
|
|
182
|
+
smoothed = result.X_sm # Smoothed data (T × N)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Complete Working Example
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from dfm_python import load_config, load_data, dfm
|
|
189
|
+
import pandas as pd
|
|
190
|
+
import numpy as np
|
|
191
|
+
|
|
192
|
+
# Load configuration (YAML or CSV)
|
|
193
|
+
config = load_config('config/example_config.yaml')
|
|
194
|
+
|
|
195
|
+
# Load data
|
|
196
|
+
X, Time, Z = load_data(
|
|
197
|
+
'data/sample_data.csv',
|
|
198
|
+
config,
|
|
199
|
+
sample_start=pd.Timestamp('2000-01-01')
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
print(f"Data loaded: {X.shape[0]} time periods, {X.shape[1]} series")
|
|
203
|
+
print(f"Time range: {Time[0]} to {Time[-1]}")
|
|
204
|
+
|
|
205
|
+
# Estimate DFM model
|
|
206
|
+
print("\nEstimating DFM model...")
|
|
207
|
+
result = dfm(X, config, threshold=1e-4, max_iter=1000)
|
|
208
|
+
|
|
209
|
+
# Explore results
|
|
210
|
+
print(f"\n✓ Model estimated successfully!")
|
|
211
|
+
print(f" Factors extracted: {result.Z.shape[1]}")
|
|
212
|
+
print(f" Factor loadings: {result.C.shape}")
|
|
213
|
+
print(f" Transition matrix: {result.A.shape}")
|
|
214
|
+
|
|
215
|
+
# Access common factor (first factor)
|
|
216
|
+
common_factor = result.Z[:, 0]
|
|
217
|
+
print(f"\n Common factor (first 5 values): {common_factor[:5]}")
|
|
218
|
+
|
|
219
|
+
# Reconstruct a series from factors
|
|
220
|
+
series_0_reconstructed = result.Z @ result.C[0, :].T
|
|
221
|
+
print(f"\n Reconstructed series 0 (first 5 values): {series_0_reconstructed[:5]}")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Package Overview: What We Provide
|
|
225
|
+
|
|
226
|
+
### Core Modules
|
|
227
|
+
|
|
228
|
+
1. **`dfm_python.config`**: Configuration management
|
|
229
|
+
- `DFMConfig`: Main configuration class
|
|
230
|
+
- `SeriesConfig`: Individual series configuration
|
|
231
|
+
- Configuration loading from YAML/CSV
|
|
232
|
+
- Validation and type checking
|
|
233
|
+
|
|
234
|
+
2. **`dfm_python.data_loader`**: Data handling
|
|
235
|
+
- `load_config()`: Load configuration from files
|
|
236
|
+
- `load_data()`: Load and transform data from CSV
|
|
237
|
+
- `transform_data()`: Apply transformations to time series
|
|
238
|
+
- Automatic date parsing and alignment
|
|
239
|
+
|
|
240
|
+
3. **`dfm_python.dfm`**: Core estimation
|
|
241
|
+
- `dfm()`: Main estimation function
|
|
242
|
+
- `init_conditions()`: Initial parameter estimation
|
|
243
|
+
- `em_step()`: EM algorithm iteration
|
|
244
|
+
- `DFMResult`: Result dataclass
|
|
245
|
+
|
|
246
|
+
4. **`dfm_python.kalman`**: Filtering and smoothing
|
|
247
|
+
- `run_kf()`: Complete Kalman filter and smoother
|
|
248
|
+
- `skf()`: Standard Kalman filter (forward pass)
|
|
249
|
+
- `fis()`: Fixed-interval smoother (backward pass)
|
|
250
|
+
- `miss_data()`: Missing data handling
|
|
251
|
+
|
|
252
|
+
5. **`dfm_python.news`**: News decomposition
|
|
253
|
+
- `news_dfm()`: News decomposition analysis
|
|
254
|
+
- `update_nowcast()`: Update forecasts with new data
|
|
255
|
+
- `para_const()`: Parameter-constrained filtering
|
|
256
|
+
|
|
257
|
+
6. **`dfm_python.utils`**: Utilities
|
|
258
|
+
- `rem_nans_spline()`: Missing value treatment
|
|
259
|
+
- `summarize()`: Data summary statistics
|
|
260
|
+
- `get_aggregation_structure()`: Tent kernel aggregation
|
|
261
|
+
- `generate_tent_weights()`: Tent weight generation
|
|
262
|
+
|
|
263
|
+
### Key Classes and Functions
|
|
264
|
+
|
|
265
|
+
**Main Entry Points:**
|
|
266
|
+
- `load_config(file)`: Load configuration
|
|
267
|
+
- `load_data(file, config)`: Load and transform data
|
|
268
|
+
- `dfm(X, config)`: Estimate DFM model
|
|
269
|
+
|
|
270
|
+
**Result Object:**
|
|
271
|
+
- `DFMResult`: Contains all estimation outputs (factors, loadings, parameters, smoothed data)
|
|
272
|
+
|
|
273
|
+
**Configuration:**
|
|
274
|
+
- `DFMConfig`: Model configuration (series, blocks, parameters)
|
|
275
|
+
- `SeriesConfig`: Individual series specification
|
|
276
|
+
|
|
277
|
+
## Detailed Inputs and Outputs
|
|
278
|
+
|
|
279
|
+
### Input: Configuration File
|
|
280
|
+
|
|
281
|
+
The configuration defines the model structure and estimation parameters.
|
|
282
|
+
|
|
283
|
+
#### YAML Format (Recommended)
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
# config/example_config.yaml
|
|
287
|
+
model:
|
|
288
|
+
series:
|
|
289
|
+
gdp_real:
|
|
290
|
+
series_name: "Real GDP (Quarterly)"
|
|
291
|
+
frequency: "q" # Frequency code
|
|
292
|
+
transformation: "pca" # Transformation code
|
|
293
|
+
category: "GDP"
|
|
294
|
+
units: "Index (2000=100)"
|
|
295
|
+
blocks: [Global, Consumption, Investment] # Block loadings
|
|
296
|
+
|
|
297
|
+
consumption:
|
|
298
|
+
series_name: "Consumption (Monthly)"
|
|
299
|
+
frequency: "m"
|
|
300
|
+
transformation: "pch"
|
|
301
|
+
category: "Consumption"
|
|
302
|
+
units: "Index (2000=100)"
|
|
303
|
+
blocks: [Global, Consumption]
|
|
304
|
+
|
|
305
|
+
blocks:
|
|
306
|
+
Global:
|
|
307
|
+
factors: 2 # Number of factors in this block
|
|
308
|
+
Consumption:
|
|
309
|
+
factors: 1
|
|
310
|
+
Investment:
|
|
311
|
+
factors: 1
|
|
312
|
+
|
|
313
|
+
dfm:
|
|
314
|
+
ar_lag: 1 # AR lag for factors
|
|
315
|
+
threshold: 1e-5 # Convergence threshold
|
|
316
|
+
max_iter: 5000 # Maximum EM iterations
|
|
317
|
+
nan_method: 2 # Missing data method
|
|
318
|
+
nan_k: 3 # Spline parameter
|
|
319
|
+
clock: "m" # Global clock frequency
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### CSV Format (Alternative)
|
|
323
|
+
|
|
324
|
+
```csv
|
|
325
|
+
series_id,series_name,frequency,transformation,category,units,Block_Global,Block_Consumption,Block_Investment
|
|
326
|
+
gdp_real,Real GDP (Quarterly),q,pca,GDP,Index (2000=100),1,1,1
|
|
327
|
+
consumption,Consumption (Monthly),m,pch,Consumption,Index (2000=100),1,1,0
|
|
328
|
+
investment,Investment (Monthly),m,pch,Investment,Index (2000=100),1,0,1
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Configuration Parameters:**
|
|
332
|
+
|
|
333
|
+
| Parameter | Type | Description | Default |
|
|
334
|
+
|-----------|------|-------------|---------|
|
|
335
|
+
| `series` | List[SeriesConfig] | Series definitions | Required |
|
|
336
|
+
| `block_names` | List[str] | Block names (e.g., ["Global", "Consumption"]) | Required |
|
|
337
|
+
| `factors_per_block` | List[int] | Number of factors per block | [1, 1, ...] |
|
|
338
|
+
| `ar_lag` | int | AR lag for factors | 1 |
|
|
339
|
+
| `threshold` | float | EM convergence threshold | 1e-5 |
|
|
340
|
+
| `max_iter` | int | Maximum EM iterations | 5000 |
|
|
341
|
+
| `nan_method` | int | Missing data method (1-5) | 2 |
|
|
342
|
+
| `nan_k` | int | Spline interpolation parameter | 3 |
|
|
343
|
+
| `clock` | str | Global clock frequency | "m" |
|
|
344
|
+
|
|
345
|
+
### Input: Data File
|
|
346
|
+
|
|
347
|
+
The data file is a CSV with the following structure:
|
|
348
|
+
|
|
349
|
+
```csv
|
|
350
|
+
Date,gdp_real,consumption,investment,stock_index,exchange_rate
|
|
351
|
+
2000-01-01,,98.5,95.0,995.87,0.997
|
|
352
|
+
2000-02-01,,98.7,95.2,996.75,1.010
|
|
353
|
+
2000-03-01,100.5,99.0,95.5,979.65,0.992
|
|
354
|
+
2000-04-01,,99.3,95.8,991.26,0.991
|
|
355
|
+
...
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Data Requirements:**
|
|
359
|
+
|
|
360
|
+
1. **Date Column**: First column must be named `Date` (case-sensitive)
|
|
361
|
+
2. **Column Names**: Must exactly match `series_id` values in configuration
|
|
362
|
+
3. **Date Format**: YYYY-MM-DD format (e.g., `2000-01-01`)
|
|
363
|
+
4. **Missing Values**: Use empty cells or `NaN` for missing observations
|
|
364
|
+
5. **Mixed Frequencies**:
|
|
365
|
+
- Quarterly: Only include values at quarter-end months (March, June, September, December)
|
|
366
|
+
- Monthly: Include values for all months
|
|
367
|
+
- Daily: Include values for all days (or aggregate to monthly)
|
|
368
|
+
|
|
369
|
+
### Output: DFMResult Object
|
|
370
|
+
|
|
371
|
+
The `dfm()` function returns a `DFMResult` dataclass containing all estimation outputs:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
@dataclass
|
|
375
|
+
class DFMResult:
|
|
376
|
+
# Factor estimates and loadings
|
|
377
|
+
Z: np.ndarray # Smoothed factors (T × m)
|
|
378
|
+
C: np.ndarray # Factor loadings (N × m)
|
|
379
|
+
|
|
380
|
+
# Model parameters
|
|
381
|
+
A: np.ndarray # Transition matrix (m × m)
|
|
382
|
+
Q: np.ndarray # Factor covariance (m × m)
|
|
383
|
+
R: np.ndarray # Observation covariance (N × N)
|
|
384
|
+
|
|
385
|
+
# Smoothed data
|
|
386
|
+
x_sm: np.ndarray # Standardized smoothed data (T × N)
|
|
387
|
+
X_sm: np.ndarray # Unstandardized smoothed data (T × N)
|
|
388
|
+
|
|
389
|
+
# Initial conditions
|
|
390
|
+
Z_0: np.ndarray # Initial state (m,)
|
|
391
|
+
V_0: np.ndarray # Initial covariance (m × m)
|
|
392
|
+
|
|
393
|
+
# Standardization parameters
|
|
394
|
+
Mx: np.ndarray # Series means (N,)
|
|
395
|
+
Wx: np.ndarray # Series standard deviations (N,)
|
|
396
|
+
|
|
397
|
+
# Model structure
|
|
398
|
+
r: np.ndarray # Factors per block (n_blocks,)
|
|
399
|
+
p: int # AR lag
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Output Dimensions:**
|
|
403
|
+
|
|
404
|
+
| Output | Shape | Description |
|
|
405
|
+
|--------|-------|-------------|
|
|
406
|
+
| `Z` | (T, m) | Smoothed factor estimates, where T = time periods, m = total factors |
|
|
407
|
+
| `C` | (N, m) | Factor loadings, where N = number of series |
|
|
408
|
+
| `A` | (m, m) | Transition matrix for factor dynamics |
|
|
409
|
+
| `Q` | (m, m) | Covariance of factor innovations |
|
|
410
|
+
| `R` | (N, N) | Covariance of observation residuals (diagonal) |
|
|
411
|
+
| `X_sm` | (T, N) | Smoothed data (original scale) |
|
|
412
|
+
| `x_sm` | (T, N) | Smoothed data (standardized) |
|
|
413
|
+
|
|
414
|
+
**How to Use Outputs:**
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
result = dfm(X, config)
|
|
418
|
+
|
|
419
|
+
# Extract common factor (first factor)
|
|
420
|
+
common_factor = result.Z[:, 0]
|
|
421
|
+
|
|
422
|
+
# Get factor loadings for a specific series
|
|
423
|
+
series_idx = 0
|
|
424
|
+
loadings = result.C[series_idx, :] # Shape: (m,)
|
|
425
|
+
|
|
426
|
+
# Reconstruct a series from factors
|
|
427
|
+
series_reconstructed = result.Z @ result.C[series_idx, :].T # Shape: (T,)
|
|
428
|
+
|
|
429
|
+
# Get smoothed version of original data
|
|
430
|
+
smoothed_data = result.X_sm # Shape: (T, N)
|
|
431
|
+
|
|
432
|
+
# Access model parameters
|
|
433
|
+
transition_matrix = result.A
|
|
434
|
+
factor_covariance = result.Q
|
|
435
|
+
observation_covariance = result.R
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Configuration Guide
|
|
439
|
+
|
|
440
|
+
### Series Configuration
|
|
441
|
+
|
|
442
|
+
Each series requires:
|
|
443
|
+
|
|
444
|
+
- **`series_id`**: Unique identifier (must match CSV column name)
|
|
445
|
+
- **`series_name`**: Human-readable name
|
|
446
|
+
- **`frequency`**: One of `d`, `w`, `m`, `q`, `sa`, `a`
|
|
447
|
+
- **`transformation`**: Transformation code (see [Data Transformations](#-data-transformations))
|
|
448
|
+
- **`blocks`**: List of blocks this series loads on (must include first block = 1)
|
|
449
|
+
- **`category`**: Optional category for organization
|
|
450
|
+
- **`units`**: Optional units description
|
|
451
|
+
|
|
452
|
+
### Block Structure
|
|
453
|
+
|
|
454
|
+
Blocks organize factors into logical groups:
|
|
455
|
+
|
|
456
|
+
```yaml
|
|
457
|
+
blocks:
|
|
458
|
+
Global:
|
|
459
|
+
factors: 2 # Common factors affecting all series
|
|
460
|
+
Consumption:
|
|
461
|
+
factors: 1 # Consumption-specific factor
|
|
462
|
+
Investment:
|
|
463
|
+
factors: 1 # Investment-specific factor
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Rules:**
|
|
467
|
+
- All series must load on the first block (Global)
|
|
468
|
+
- Series can load on multiple blocks
|
|
469
|
+
- Each block can have multiple factors
|
|
470
|
+
|
|
471
|
+
### Clock Parameter
|
|
472
|
+
|
|
473
|
+
The `clock` parameter defines the base frequency for all latent factors:
|
|
474
|
+
|
|
475
|
+
```yaml
|
|
476
|
+
dfm:
|
|
477
|
+
clock: "m" # Monthly clock (default)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**How Clock Works:**
|
|
481
|
+
- All latent factors (global and block-level) evolve at the clock frequency
|
|
482
|
+
- Series with frequencies **slower** than clock (e.g., quarterly with monthly clock) use tent kernels
|
|
483
|
+
- Series with frequencies **faster** than clock (e.g., daily with monthly clock) use missing data approach
|
|
484
|
+
- This synchronization simplifies the Kalman filter and ensures consistent factor evolution
|
|
485
|
+
|
|
486
|
+
## Data Format Requirements
|
|
487
|
+
|
|
488
|
+
### CSV Structure
|
|
489
|
+
|
|
490
|
+
```csv
|
|
491
|
+
Date,series_id_1,series_id_2,series_id_3,...
|
|
492
|
+
2000-01-01,value_1,value_2,value_3,...
|
|
493
|
+
2000-02-01,value_1,value_2,value_3,...
|
|
494
|
+
...
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Mixed-Frequency Handling
|
|
498
|
+
|
|
499
|
+
**Quarterly Series:**
|
|
500
|
+
- Include values only at quarter-end months (March, June, September, December)
|
|
501
|
+
- Leave other months empty/NaN
|
|
502
|
+
|
|
503
|
+
**Example:**
|
|
504
|
+
```csv
|
|
505
|
+
Date,gdp_real
|
|
506
|
+
2000-01-01, # Empty - not quarter-end
|
|
507
|
+
2000-02-01, # Empty - not quarter-end
|
|
508
|
+
2000-03-01,100.5 # Q1 value
|
|
509
|
+
2000-04-01, # Empty - not quarter-end
|
|
510
|
+
2000-05-01, # Empty - not quarter-end
|
|
511
|
+
2000-06-01,101.2 # Q2 value
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Monthly Series:**
|
|
515
|
+
- Include values for all months
|
|
516
|
+
|
|
517
|
+
**Daily Series:**
|
|
518
|
+
- Include values for all days (or aggregate to monthly for the model)
|
|
519
|
+
|
|
520
|
+
### Missing Data
|
|
521
|
+
|
|
522
|
+
Missing values are handled automatically:
|
|
523
|
+
- Use empty cells or `NaN` in CSV
|
|
524
|
+
- Package interpolates using spline interpolation
|
|
525
|
+
- Tested robustly with up to 50% missing data
|
|
526
|
+
|
|
527
|
+
## Usage Examples
|
|
528
|
+
|
|
529
|
+
### Example 1: Basic DFM Estimation
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
from dfm_python import load_config, load_data, dfm
|
|
533
|
+
import pandas as pd
|
|
534
|
+
|
|
535
|
+
# Load configuration and data
|
|
536
|
+
config = load_config('config/example_config.yaml')
|
|
537
|
+
X, Time, Z = load_data('data/sample_data.csv', config)
|
|
538
|
+
|
|
539
|
+
# Estimate model
|
|
540
|
+
result = dfm(X, config, threshold=1e-4, max_iter=1000)
|
|
541
|
+
|
|
542
|
+
# Extract common factor
|
|
543
|
+
common_factor = result.Z[:, 0]
|
|
544
|
+
print(f"Common factor extracted: {len(common_factor)} time periods")
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Example 2: Working with Results
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
# Access all result components
|
|
551
|
+
factors = result.Z # (T × m) Factor estimates
|
|
552
|
+
loadings = result.C # (N × m) Factor loadings
|
|
553
|
+
transition = result.A # (m × m) Transition matrix
|
|
554
|
+
covariance = result.Q # (m × m) Factor covariance
|
|
555
|
+
smoothed_data = result.X_sm # (T × N) Smoothed data
|
|
556
|
+
|
|
557
|
+
# Compute factor contribution to a specific series
|
|
558
|
+
series_idx = 0
|
|
559
|
+
series_factor_contribution = result.Z @ result.C[series_idx, :].T
|
|
560
|
+
|
|
561
|
+
# Get factor loadings for interpretation
|
|
562
|
+
print("Factor loadings for first series:")
|
|
563
|
+
print(result.C[0, :])
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Example 3: News Decomposition
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
from dfm_python import news_dfm
|
|
570
|
+
|
|
571
|
+
# Estimate model on old data
|
|
572
|
+
result_old = dfm(X_old, config)
|
|
573
|
+
|
|
574
|
+
# Estimate model on new data
|
|
575
|
+
result_new = dfm(X_new, config)
|
|
576
|
+
|
|
577
|
+
# Decompose news (how new data affects forecast)
|
|
578
|
+
y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
|
|
579
|
+
X_old, # Old vintage data
|
|
580
|
+
X_new, # New vintage data
|
|
581
|
+
result_old, # DFM estimation results
|
|
582
|
+
t_fcst=100, # Forecast time index
|
|
583
|
+
v_news=0 # Target variable index (or list for multiple targets)
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
print(f"Forecast update: {y_new - y_old}")
|
|
587
|
+
print(f"News contributions: {singlenews}")
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Example 4: Handling Missing Data
|
|
591
|
+
|
|
592
|
+
```python
|
|
593
|
+
# The package automatically handles missing data
|
|
594
|
+
# Missing values are interpolated using spline interpolation
|
|
595
|
+
|
|
596
|
+
# Check missing data patterns
|
|
597
|
+
import numpy as np
|
|
598
|
+
missing_pct = np.isnan(X).sum(axis=0) / X.shape[0] * 100
|
|
599
|
+
for i, series_id in enumerate(config.SeriesID):
|
|
600
|
+
print(f"{series_id}: {missing_pct[i]:.1f}% missing")
|
|
601
|
+
|
|
602
|
+
# Estimate model (missing data handled automatically)
|
|
603
|
+
result = dfm(X, config)
|
|
604
|
+
|
|
605
|
+
# Smoothed data has no missing values
|
|
606
|
+
assert np.isnan(result.X_sm).sum() == 0, "All missing values should be filled"
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### Example 5: Custom Configuration
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
from dfm_python.config import DFMConfig, SeriesConfig
|
|
613
|
+
|
|
614
|
+
# Create configuration programmatically
|
|
615
|
+
series_list = [
|
|
616
|
+
SeriesConfig(
|
|
617
|
+
series_id="gdp_real",
|
|
618
|
+
series_name="Real GDP",
|
|
619
|
+
frequency="q",
|
|
620
|
+
transformation="pca",
|
|
621
|
+
category="GDP",
|
|
622
|
+
units="Index",
|
|
623
|
+
blocks=[1, 1, 0] # Loads on Global and Consumption blocks
|
|
624
|
+
),
|
|
625
|
+
SeriesConfig(
|
|
626
|
+
series_id="consumption",
|
|
627
|
+
series_name="Consumption",
|
|
628
|
+
frequency="m",
|
|
629
|
+
transformation="pch",
|
|
630
|
+
category="Consumption",
|
|
631
|
+
units="Index",
|
|
632
|
+
blocks=[1, 1, 0]
|
|
633
|
+
),
|
|
634
|
+
]
|
|
635
|
+
|
|
636
|
+
config = DFMConfig(
|
|
637
|
+
series=series_list,
|
|
638
|
+
block_names=["Global", "Consumption", "Investment"],
|
|
639
|
+
factors_per_block=[2, 1, 1],
|
|
640
|
+
clock="m"
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# Use configuration
|
|
644
|
+
result = dfm(X, config)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
## API Reference
|
|
648
|
+
|
|
649
|
+
### Main Functions
|
|
650
|
+
|
|
651
|
+
#### `load_config(file: Union[str, Path]) -> DFMConfig`
|
|
652
|
+
|
|
653
|
+
Load configuration from YAML or CSV file.
|
|
654
|
+
|
|
655
|
+
**Parameters:**
|
|
656
|
+
- `file`: Path to configuration file (`.yaml` or `.csv`)
|
|
657
|
+
|
|
658
|
+
**Returns:**
|
|
659
|
+
- `DFMConfig`: Configuration object
|
|
660
|
+
|
|
661
|
+
**Example:**
|
|
662
|
+
```python
|
|
663
|
+
config = load_config('config.yaml')
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
#### `load_data(file: Union[str, Path], config: DFMConfig, sample_start: Optional[pd.Timestamp] = None) -> Tuple[np.ndarray, pd.DatetimeIndex, np.ndarray]`
|
|
667
|
+
|
|
668
|
+
Load and transform data from CSV file.
|
|
669
|
+
|
|
670
|
+
**Parameters:**
|
|
671
|
+
- `file`: Path to CSV data file
|
|
672
|
+
- `config`: DFM configuration object
|
|
673
|
+
- `sample_start`: Optional start date (filters data before this date)
|
|
674
|
+
|
|
675
|
+
**Returns:**
|
|
676
|
+
- `X`: Transformed data matrix (T × N), ready for estimation
|
|
677
|
+
- `Time`: Time index (pandas DatetimeIndex)
|
|
678
|
+
- `Z`: Original untransformed data (T × N)
|
|
679
|
+
|
|
680
|
+
**Example:**
|
|
681
|
+
```python
|
|
682
|
+
X, Time, Z = load_data('data.csv', config, sample_start=pd.Timestamp('2000-01-01'))
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
#### `dfm(X: np.ndarray, config: DFMConfig, threshold: Optional[float] = None, max_iter: Optional[int] = None) -> DFMResult`
|
|
686
|
+
|
|
687
|
+
Estimate Dynamic Factor Model using EM algorithm.
|
|
688
|
+
|
|
689
|
+
**Parameters:**
|
|
690
|
+
- `X`: Data matrix (T × N) with possible missing values
|
|
691
|
+
- `config`: DFM configuration object
|
|
692
|
+
- `threshold`: Convergence threshold (default: 1e-5). Smaller = more precise but slower
|
|
693
|
+
- `max_iter`: Maximum EM iterations (default: 5000)
|
|
694
|
+
|
|
695
|
+
**Returns:**
|
|
696
|
+
- `DFMResult`: Result object containing all estimation outputs
|
|
697
|
+
|
|
698
|
+
**Example:**
|
|
699
|
+
```python
|
|
700
|
+
result = dfm(X, config, threshold=1e-4, max_iter=1000)
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### Configuration Classes
|
|
704
|
+
|
|
705
|
+
#### `DFMConfig`
|
|
706
|
+
|
|
707
|
+
Main configuration dataclass defining the model structure.
|
|
708
|
+
|
|
709
|
+
**Attributes:**
|
|
710
|
+
- `series`: List of `SeriesConfig` objects
|
|
711
|
+
- `block_names`: List of block names (e.g., ["Global", "Consumption"])
|
|
712
|
+
- `factors_per_block`: Number of factors per block (optional, defaults to 1 per block)
|
|
713
|
+
- `ar_lag`: AR lag for factors (typically 1)
|
|
714
|
+
- `threshold`: EM convergence threshold (default: 1e-5)
|
|
715
|
+
- `max_iter`: Maximum EM iterations (default: 5000)
|
|
716
|
+
- `nan_method`: Missing data handling method (1-5, default: 2)
|
|
717
|
+
- `nan_k`: Spline interpolation parameter (default: 3)
|
|
718
|
+
- `clock`: Global clock frequency (default: "m")
|
|
719
|
+
|
|
720
|
+
#### `SeriesConfig`
|
|
721
|
+
|
|
722
|
+
Individual series configuration.
|
|
723
|
+
|
|
724
|
+
**Attributes:**
|
|
725
|
+
- `series_id`: Unique identifier (must match CSV column name)
|
|
726
|
+
- `series_name`: Human-readable name
|
|
727
|
+
- `frequency`: Frequency code (`d`, `w`, `m`, `q`, `sa`, `a`)
|
|
728
|
+
- `transformation`: Transformation code (`pch`, `pca`, etc.)
|
|
729
|
+
- `category`: Series category (for organization)
|
|
730
|
+
- `units`: Units of measurement
|
|
731
|
+
- `blocks`: List of blocks this series loads on (must include first block = 1)
|
|
732
|
+
|
|
733
|
+
### Result Object
|
|
734
|
+
|
|
735
|
+
#### `DFMResult`
|
|
736
|
+
|
|
737
|
+
Result dataclass containing all estimation outputs.
|
|
738
|
+
|
|
739
|
+
**Key Attributes:**
|
|
740
|
+
- `Z`: Smoothed factor estimates (T × m)
|
|
741
|
+
- `C`: Factor loadings (N × m)
|
|
742
|
+
- `A`: Transition matrix (m × m)
|
|
743
|
+
- `Q`: Factor covariance (m × m)
|
|
744
|
+
- `R`: Observation covariance (N × N)
|
|
745
|
+
- `X_sm`: Smoothed data (T × N)
|
|
746
|
+
- `Mx`, `Wx`: Standardization parameters (means and std devs)
|
|
747
|
+
|
|
748
|
+
**Example:**
|
|
749
|
+
```python
|
|
750
|
+
result = dfm(X, config)
|
|
751
|
+
factors = result.Z # Extract factors
|
|
752
|
+
loadings = result.C # Extract loadings
|
|
753
|
+
smoothed = result.X_sm # Extract smoothed data
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
## Advanced Features
|
|
757
|
+
|
|
758
|
+
### News Decomposition
|
|
759
|
+
|
|
760
|
+
Decompose forecast updates into contributions from individual data releases:
|
|
761
|
+
|
|
762
|
+
```python
|
|
763
|
+
from dfm_python import news_dfm
|
|
764
|
+
|
|
765
|
+
# Calculate news decomposition
|
|
766
|
+
y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
|
|
767
|
+
X_old, # Old vintage data
|
|
768
|
+
X_new, # New vintage data
|
|
769
|
+
result, # DFM estimation results
|
|
770
|
+
t_fcst=100, # Forecast time index
|
|
771
|
+
v_news=0 # Target variable index (or list for multiple targets)
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# singlenews: Individual news contributions from each new data point
|
|
775
|
+
# y_new - y_old: Total forecast update
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Multiple Target Variables
|
|
779
|
+
|
|
780
|
+
Support for decomposing news for multiple target variables simultaneously:
|
|
781
|
+
|
|
782
|
+
```python
|
|
783
|
+
# Multiple targets
|
|
784
|
+
targets = [0, 1, 2] # Indices of target variables
|
|
785
|
+
y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
|
|
786
|
+
X_old, X_new, result, t_fcst=100, v_news=targets
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# y_old, y_new: Now arrays of shape (n_targets,)
|
|
790
|
+
# singlenews: Array of shape (N, n_targets)
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Custom Missing Data Handling
|
|
794
|
+
|
|
795
|
+
Control how missing data is handled:
|
|
796
|
+
|
|
797
|
+
```python
|
|
798
|
+
from dfm_python import DFMConfig, SeriesConfig
|
|
799
|
+
|
|
800
|
+
config = DFMConfig(
|
|
801
|
+
series=[...],
|
|
802
|
+
nan_method=2, # Method: 1=median fill, 2=remove rows + spline, 3=remove all-NaN rows, etc.
|
|
803
|
+
nan_k=3 # Spline interpolation parameter
|
|
804
|
+
)
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
**Missing Data Methods:**
|
|
808
|
+
- `1`: Replace all missing values with median, then apply moving average
|
|
809
|
+
- `2`: Remove rows with >80% NaN, then fill remaining NaNs with spline interpolation (recommended)
|
|
810
|
+
- `3`: Remove only rows that are completely NaN (all series missing)
|
|
811
|
+
- `4`: Remove all-NaN rows, then fill remaining NaNs with spline interpolation
|
|
812
|
+
- `5`: Fill missing values with spline interpolation (no row removal)
|
|
813
|
+
|
|
814
|
+
## Clock-Based Mixed-Frequency Framework
|
|
815
|
+
|
|
816
|
+
### Overview
|
|
817
|
+
|
|
818
|
+
This package implements a **clock-based** mixed-frequency framework following the FRBNY approach. The key innovation is synchronizing all latent factors to a common "clock" frequency, typically monthly.
|
|
819
|
+
|
|
820
|
+
### How It Works
|
|
821
|
+
|
|
822
|
+
1. **Global Clock**: All latent factors (global and block-level) evolve at the clock frequency
|
|
823
|
+
2. **Tent Kernels**: Lower-frequency observations (e.g., quarterly) are mapped to higher-frequency latent states (e.g., monthly) using deterministic tent kernels
|
|
824
|
+
3. **Missing Data Approach**: Higher-frequency observations (e.g., daily) are handled via missing data when clock is slower
|
|
825
|
+
|
|
826
|
+
### Tent Kernels
|
|
827
|
+
|
|
828
|
+
Tent kernels are deterministic weight patterns that aggregate lower-frequency observations to higher-frequency latent states. For example, quarterly GDP is connected to 5 monthly latent states with weights `[1, 2, 3, 2, 1]` (peaking at the middle month).
|
|
829
|
+
|
|
830
|
+
**Supported Frequency Pairs:**
|
|
831
|
+
- Quarterly → Monthly: `[1, 2, 3, 2, 1]` (5 periods)
|
|
832
|
+
- Semi-annual → Monthly: `[1, 2, 3, 4, 3, 2, 1]` (7 periods)
|
|
833
|
+
- Annual → Monthly: `[1, 2, 3, 4, 5, 4, 3, 2, 1]` (9 periods)
|
|
834
|
+
- And more (see `TENT_WEIGHTS_LOOKUP` in `utils.aggregation`)
|
|
835
|
+
|
|
836
|
+
**Maximum Tent Size**: 12 periods (larger gaps use missing data approach)
|
|
837
|
+
|
|
838
|
+
### Configuration
|
|
839
|
+
|
|
840
|
+
Set the clock frequency in your configuration:
|
|
841
|
+
|
|
842
|
+
```yaml
|
|
843
|
+
dfm:
|
|
844
|
+
clock: "m" # Monthly clock (default)
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Clock Options:**
|
|
848
|
+
- `"d"`: Daily
|
|
849
|
+
- `"w"`: Weekly
|
|
850
|
+
- `"m"`: Monthly (recommended)
|
|
851
|
+
- `"q"`: Quarterly
|
|
852
|
+
|
|
853
|
+
### Benefits
|
|
854
|
+
|
|
855
|
+
- **Simplified Kalman Filter**: Single unified filter at clock frequency
|
|
856
|
+
- **Consistent Factor Evolution**: All factors evolve synchronously
|
|
857
|
+
- **Robust Aggregation**: Deterministic tent kernels ensure proper aggregation
|
|
858
|
+
- **Flexible**: Works with any frequency combination
|
|
859
|
+
|
|
860
|
+
## Troubleshooting
|
|
861
|
+
|
|
862
|
+
### Common Issues
|
|
863
|
+
|
|
864
|
+
#### Import Error: `ModuleNotFoundError: No module named 'utils'`
|
|
865
|
+
|
|
866
|
+
**Solution**: This was fixed in version 0.1.2. Update your package:
|
|
867
|
+
```bash
|
|
868
|
+
pip install --upgrade dfm-python
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
#### Quarterly Series Show 100% Missing After Transformation
|
|
872
|
+
|
|
873
|
+
**Cause**: Quarterly data must have values only at quarter-end months (March, June, September, December).
|
|
874
|
+
|
|
875
|
+
**Solution**: Ensure your CSV has quarterly values only at quarter-end months, with NaN/empty for other months.
|
|
876
|
+
|
|
877
|
+
#### Convergence Warnings
|
|
878
|
+
|
|
879
|
+
**Cause**: EM algorithm may not converge if:
|
|
880
|
+
- Data has too much missing data (>50% per series)
|
|
881
|
+
- Initial conditions are poor
|
|
882
|
+
- Threshold is too strict
|
|
883
|
+
|
|
884
|
+
**Solution**:
|
|
885
|
+
- Check data quality
|
|
886
|
+
- Increase `max_iter`
|
|
887
|
+
- Relax `threshold` (try 1e-3 instead of 1e-5)
|
|
888
|
+
- Review missing data patterns
|
|
889
|
+
|
|
890
|
+
#### Dimension Mismatch Errors
|
|
891
|
+
|
|
892
|
+
**Cause**: Configuration doesn't match data structure.
|
|
893
|
+
|
|
894
|
+
**Solution**:
|
|
895
|
+
- Verify `series_id` in config matches CSV column names exactly
|
|
896
|
+
- Check that block structure is consistent
|
|
897
|
+
- Ensure all series have valid frequency codes
|
|
898
|
+
|
|
899
|
+
#### Insufficient Data Errors
|
|
900
|
+
|
|
901
|
+
**Cause**: Not enough finite observations for covariance calculation.
|
|
902
|
+
|
|
903
|
+
**Solution**:
|
|
904
|
+
- Check data quality and missing data patterns
|
|
905
|
+
- Adjust `nan_method` (try method 2 or 4)
|
|
906
|
+
- Ensure sufficient time periods (T >= 10 recommended)
|
|
907
|
+
|
|
908
|
+
### Getting Help
|
|
909
|
+
|
|
910
|
+
- Check the [sample data and configuration files](#data-format-requirements) for reference
|
|
911
|
+
- Review the [API Reference](#api-reference) for function details
|
|
912
|
+
- Ensure you're using the latest version: `pip install --upgrade dfm-python`
|
|
913
|
+
|
|
914
|
+
## Testing
|
|
915
|
+
|
|
916
|
+
The package includes comprehensive test suite:
|
|
917
|
+
|
|
918
|
+
```bash
|
|
919
|
+
# Run all tests
|
|
920
|
+
python -m pytest src/test/
|
|
921
|
+
|
|
922
|
+
# Run specific test file
|
|
923
|
+
python -m pytest src/test/test_dfm.py
|
|
924
|
+
|
|
925
|
+
# Run with verbose output
|
|
926
|
+
python -m pytest src/test/ -v
|
|
927
|
+
|
|
928
|
+
# Test with sample data
|
|
929
|
+
python -c "
|
|
930
|
+
from dfm_python import load_config, load_data, dfm
|
|
931
|
+
config = load_config('config/example_config.yaml')
|
|
932
|
+
X, Time, Z = load_data('data/sample_data.csv', config)
|
|
933
|
+
result = dfm(X, config, max_iter=50) # Quick test
|
|
934
|
+
print('✓ Test passed!')
|
|
935
|
+
"
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
## Architecture
|
|
939
|
+
|
|
940
|
+
The package is designed to be **generic and application-agnostic**:
|
|
941
|
+
|
|
942
|
+
- **Core Module** (`dfm_python`): Pure Python implementation, no external dependencies beyond scientific stack
|
|
943
|
+
- **Modular Design**: Separate modules for estimation, filtering, news decomposition
|
|
944
|
+
- **Extensible**: Easy to add application-specific adapters (database, APIs, etc.)
|
|
945
|
+
|
|
946
|
+
### Module Structure
|
|
947
|
+
|
|
948
|
+
```
|
|
949
|
+
dfm_python/
|
|
950
|
+
├── __init__.py # Package initialization and exports
|
|
951
|
+
├── config.py # Configuration classes (DFMConfig, SeriesConfig)
|
|
952
|
+
├── data_loader.py # Data loading and transformation
|
|
953
|
+
├── dfm.py # Core estimation (dfm, init_conditions, em_step)
|
|
954
|
+
├── kalman.py # Kalman filter and smoother
|
|
955
|
+
├── news.py # News decomposition
|
|
956
|
+
└── utils/
|
|
957
|
+
├── __init__.py # Utility exports
|
|
958
|
+
├── data_utils.py # Data preprocessing utilities
|
|
959
|
+
└── aggregation.py # Tent kernel aggregation
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
## Requirements
|
|
963
|
+
|
|
964
|
+
- **Python**: >= 3.12
|
|
965
|
+
- **numpy**: >= 1.24.0
|
|
966
|
+
- **pandas**: >= 2.0.0
|
|
967
|
+
- **scipy**: >= 1.10.0
|
|
968
|
+
|
|
969
|
+
### Optional Dependencies
|
|
970
|
+
|
|
971
|
+
- **hydra-core**: >= 1.3.0 (for Hydra configuration)
|
|
972
|
+
- **omegaconf**: >= 2.3.0 (for YAML configuration)
|
|
973
|
+
- **pytest**: >= 7.0.0 (for testing)
|
|
974
|
+
|
|
975
|
+
## Contributing
|
|
976
|
+
|
|
977
|
+
Contributions are welcome! The package follows these principles:
|
|
978
|
+
|
|
979
|
+
- **Core module remains generic**: No application-specific code in core
|
|
980
|
+
- **Comprehensive testing**: All features should have tests
|
|
981
|
+
- **Documentation**: Code should be well-documented
|
|
982
|
+
- **Backward compatibility**: Changes should maintain API compatibility when possible
|
|
983
|
+
|
|
984
|
+
## License
|
|
985
|
+
|
|
986
|
+
MIT License - see LICENSE file for details.
|
|
987
|
+
|
|
988
|
+
## Citation
|
|
989
|
+
|
|
990
|
+
If you use this package in your research, please cite:
|
|
991
|
+
|
|
992
|
+
```bibtex
|
|
993
|
+
@software{dfm-python,
|
|
994
|
+
title = {dfm-python: Dynamic Factor Models for Nowcasting and Forecasting},
|
|
995
|
+
author = {DFM Python Contributors},
|
|
996
|
+
year = {2024},
|
|
997
|
+
url = {https://pypi.org/project/dfm-python/},
|
|
998
|
+
version = {0.1.2}
|
|
999
|
+
}
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
## Acknowledgments
|
|
1003
|
+
|
|
1004
|
+
This package implements Dynamic Factor Models following established econometric methodology, with a focus on practical nowcasting and forecasting applications. The clock-based mixed-frequency framework follows the FRBNY (Federal Reserve Bank of New York) approach.
|
|
1005
|
+
|
|
1006
|
+
---
|
|
1007
|
+
|
|
1008
|
+
**Package Status**: Stable (v0.1.2)
|
|
1009
|
+
**PyPI**: https://pypi.org/project/dfm-python/
|
|
1010
|
+
**Python**: 3.12+
|