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.
Files changed (32) hide show
  1. dfm_python-0.1.3/PKG-INFO +1010 -0
  2. dfm_python-0.1.3/README.md +968 -0
  3. {dfm_python-0.1.1 → dfm_python-0.1.3}/config/example_config.yaml +60 -30
  4. dfm_python-0.1.3/data/sample_data.csv +193 -0
  5. {dfm_python-0.1.1 → dfm_python-0.1.3}/pyproject.toml +1 -1
  6. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/__init__.py +1 -1
  7. dfm_python-0.1.3/src/dfm_python/__init__.py +60 -0
  8. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/config.py +70 -23
  9. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/data_loader.py +59 -10
  10. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/dfm.py +237 -303
  11. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/kalman.py +68 -16
  12. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/dfm_python/news.py +25 -3
  13. dfm_python-0.1.3/src/dfm_python/utils/__init__.py +27 -0
  14. dfm_python-0.1.3/src/dfm_python/utils/aggregation.py +258 -0
  15. {dfm_python-0.1.1/src → dfm_python-0.1.3/src/dfm_python}/utils/data_utils.py +11 -1
  16. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_dfm.py +6 -6
  17. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_kalman.py +13 -4
  18. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_news.py +1 -1
  19. dfm_python-0.1.3/test_comprehensive.py +510 -0
  20. dfm_python-0.1.1/PKG-INFO +0 -395
  21. dfm_python-0.1.1/README.md +0 -353
  22. dfm_python-0.1.1/data/sample_data.csv +0 -133
  23. dfm_python-0.1.1/src/dfm_python/__init__.py +0 -28
  24. dfm_python-0.1.1/src/utils/__init__.py +0 -12
  25. {dfm_python-0.1.1 → dfm_python-0.1.3}/.gitignore +0 -0
  26. {dfm_python-0.1.1 → dfm_python-0.1.3}/.python-version +0 -0
  27. {dfm_python-0.1.1 → dfm_python-0.1.3}/LICENSE +0 -0
  28. {dfm_python-0.1.1 → dfm_python-0.1.3}/MANIFEST.in +0 -0
  29. {dfm_python-0.1.1 → dfm_python-0.1.3}/spec/example_spec.csv +0 -0
  30. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/__init__.py +0 -0
  31. {dfm_python-0.1.1 → dfm_python-0.1.3}/src/test/test_synthetic.py +0 -0
  32. {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
+ [![PyPI version](https://img.shields.io/pypi/v/dfm-python.svg)](https://pypi.org/project/dfm-python/)
46
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](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+