dfm-python 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

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/kalman.py CHANGED
@@ -155,10 +155,28 @@ def skf(Y: np.ndarray, A: np.ndarray, C: np.ndarray, Q: np.ndarray,
155
155
  Zu = Z_0.copy() # Z_0|0 (In loop, Zu gives Z_t | t)
156
156
  Vu = V_0.copy() # V_0|0 (In loop, Vu gives V_t | t)
157
157
 
158
+ # Validate dimensions match
159
+ if Zu.shape[0] != m:
160
+ raise ValueError(
161
+ f"Dimension mismatch: Z_0 has shape {Zu.shape[0]}, but C has {m} columns. "
162
+ f"This usually indicates a mismatch between init_conditions and em_step. "
163
+ f"Z_0 should have dimension {m} to match C.shape[1]."
164
+ )
165
+ if Vu.shape[0] != m or Vu.shape[1] != m:
166
+ raise ValueError(
167
+ f"Dimension mismatch: V_0 has shape {Vu.shape}, but expected ({m}, {m}). "
168
+ f"This usually indicates a mismatch between init_conditions and em_step."
169
+ )
170
+
158
171
  # Store initial values
159
172
  ZmU[:, 0] = Zu
160
173
  VmU[:, :, 0] = Vu
161
174
 
175
+ # Initialize variables for final iteration (used after loop)
176
+ Y_t = np.array([]) # Initialize Y_t to empty array
177
+ C_t = None
178
+ VCF = None
179
+
162
180
  # Kalman filter procedure
163
181
  for t in range(nobs):
164
182
  # Calculate prior distribution
@@ -130,12 +130,10 @@ def summarize(X: np.ndarray, Time, config, vintage: Optional[str] = None) -> Non
130
130
  else:
131
131
  series_names = []
132
132
 
133
- if hasattr(config, 'get_series_ids'):
134
- series_ids = config.get_series_ids()
135
- elif hasattr(config, 'SeriesID'):
133
+ from ..core.helpers import safe_get_method
134
+ series_ids = safe_get_method(config, 'get_series_ids', [])
135
+ if not series_ids and hasattr(config, 'SeriesID'):
136
136
  series_ids = config.SeriesID
137
- else:
138
- series_ids = []
139
137
 
140
138
  if hasattr(config, 'get_frequencies'):
141
139
  frequencies = config.get_frequencies()
@@ -0,0 +1,312 @@
1
+ Metadata-Version: 2.4
2
+ Name: dfm-python
3
+ Version: 0.2.2
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.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: hydra-core>=1.3.2
23
+ Requires-Dist: matplotlib>=3.10.7
24
+ Requires-Dist: numpy>=1.24.0
25
+ Requires-Dist: pandas>=2.0.0
26
+ Requires-Dist: scipy>=1.10.0
27
+ Provides-Extra: all
28
+ Requires-Dist: httpx>=0.24.0; extra == 'all'
29
+ Requires-Dist: hydra-core>=1.3.0; extra == 'all'
30
+ Requires-Dist: omegaconf>=2.3.0; extra == 'all'
31
+ Requires-Dist: psycopg2-binary>=2.9.0; extra == 'all'
32
+ Requires-Dist: pydantic-settings>=2.11.0; extra == 'all'
33
+ Requires-Dist: pytest>=7.0.0; extra == 'all'
34
+ Requires-Dist: supabase>=2.0.0; extra == 'all'
35
+ Provides-Extra: database
36
+ Requires-Dist: httpx>=0.24.0; extra == 'database'
37
+ Requires-Dist: psycopg2-binary>=2.9.0; extra == 'database'
38
+ Requires-Dist: pydantic-settings>=2.11.0; extra == 'database'
39
+ Requires-Dist: supabase>=2.0.0; extra == 'database'
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
42
+ Provides-Extra: hydra
43
+ Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
44
+ Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # dfm-python: Dynamic Factor Models for Python
48
+
49
+ [![PyPI version](https://img.shields.io/pypi/v/dfm-python.svg)](https://pypi.org/project/dfm-python/)
50
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
51
+
52
+ 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.
53
+
54
+ ## Features
55
+
56
+ - **Mixed-frequency data**: Monthly, quarterly, semi-annual, annual
57
+ - **Clock-based framework**: All factors evolve at a common clock frequency
58
+ - **Block structure**: Flexible factor organization (global, sector-specific)
59
+ - **Robust missing data**: Spline interpolation and Kalman filter handling
60
+ - **News decomposition**: Attribute forecast changes to data releases
61
+ - **Generic design**: YAML configs or direct object creation, extensible via adapters
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ pip install dfm-python
67
+ ```
68
+
69
+ **Requirements**: Python >= 3.10, numpy >= 1.24.0, pandas >= 2.0.0, scipy >= 1.10.0
70
+
71
+ ## Quick Start
72
+
73
+ ### Module-Level API (Recommended)
74
+
75
+ ```python
76
+ import dfm_python as dfm
77
+ from dfm_python.config import Params
78
+
79
+ # 1. Load config from spec CSV + Params
80
+ params = Params(max_iter=5000, threshold=1e-5)
81
+ dfm.from_spec('data/sample_spec.csv', params=params)
82
+
83
+ # 2. Load data
84
+ dfm.load_data('data/sample_data.csv', sample_start='2021-01-01', sample_end='2022-12-31')
85
+
86
+ # 3. Train
87
+ dfm.train(max_iter=5000)
88
+
89
+ # 4. Forecast
90
+ X_forecast, Z_forecast = dfm.predict(horizon=12)
91
+
92
+ # 5. Plot
93
+ dfm.plot(kind='factor', factor_index=0, forecast_horizon=12, save_path='outputs/factor.png')
94
+
95
+ # 6. Access results
96
+ result = dfm.get_result()
97
+ factors = result.Z # (T × m) Smoothed factors
98
+ loadings = result.C # (N × m) Factor loadings
99
+ smoothed = result.X_sm # (T × N) Smoothed data
100
+ ```
101
+
102
+ ### Alternative: YAML Configuration
103
+
104
+ ```python
105
+ import dfm_python as dfm
106
+
107
+ # Load from YAML
108
+ dfm.from_yaml('config/default.yaml')
109
+ dfm.load_data('data/sample_data.csv')
110
+ dfm.train()
111
+ X_forecast, Z_forecast = dfm.predict(horizon=12)
112
+ ```
113
+
114
+ ### Alternative: Direct Class Usage
115
+
116
+ ```python
117
+ from dfm_python import DFM, DFMConfig, SeriesConfig, BlockConfig
118
+
119
+ # Create config
120
+ config = DFMConfig(
121
+ series=[SeriesConfig(frequency='m', transformation='lin', blocks=[1], series_id='s1')],
122
+ blocks={'Block_Global': BlockConfig(factors=1, clock='m')}
123
+ )
124
+
125
+ # Fit model
126
+ model = DFM()
127
+ result = model.fit(X, config, max_iter=100)
128
+ factors = result.Z
129
+ ```
130
+
131
+ ## Configuration
132
+
133
+ ### Spec CSV + Params (Recommended for CSV workflows)
134
+
135
+ ```python
136
+ from dfm_python.config import Params
137
+
138
+ params = Params(
139
+ max_iter=5000,
140
+ threshold=1e-5,
141
+ regularization_scale=1e-5,
142
+ damping_factor=0.8
143
+ )
144
+ dfm.from_spec('data/sample_spec.csv', params=params)
145
+ ```
146
+
147
+ ### Hydra Decorator (Recommended for Hydra users)
148
+
149
+ ```python
150
+ import hydra
151
+ from hydra.utils import get_original_cwd
152
+ from omegaconf import DictConfig
153
+ import dfm_python as dfm
154
+
155
+ @hydra.main(config_path="../config", config_name="default", version_base="1.3")
156
+ def main(cfg: DictConfig) -> None:
157
+ dfm.load_config(hydra=cfg)
158
+ dfm.load_data(str(get_original_cwd() / "data" / "sample_data.csv"))
159
+ dfm.train(max_iter=cfg.max_iter)
160
+ X_forecast, Z_forecast = dfm.predict(horizon=cfg.get('forecast_horizon', 12))
161
+ ```
162
+
163
+ **CLI Overrides**: `python script.py max_iter=10 threshold=1e-4 blocks.Block_Global.factors=2`
164
+
165
+ ### Dictionary
166
+
167
+ ```python
168
+ config_dict = {
169
+ 'clock': 'm',
170
+ 'max_iter': 5000,
171
+ 'blocks': {'Block_Global': {'factors': 1, 'clock': 'm'}},
172
+ 'series': [{'series_id': 's1', 'frequency': 'm', 'transformation': 'lin', 'blocks': [0]}]
173
+ }
174
+ dfm.from_dict(config_dict)
175
+ ```
176
+
177
+ ## Key Parameters
178
+
179
+ - **clock**: Base frequency for latent factors (typically "m" for monthly)
180
+ - **threshold**: EM convergence criterion (default: 1e-5)
181
+ - **max_iter**: Maximum EM iterations (default: 5000)
182
+ - **ar_lag**: Autoregressive lag for factors (default: 1)
183
+ - **regularization_scale**: Scale factor for ridge regularization (default: 1e-5)
184
+ - **damping_factor**: Damping factor for parameter updates (default: 0.8)
185
+
186
+ All numerical stability parameters are configurable and transparent. See `Params` dataclass for full list.
187
+
188
+ ## Data Format
189
+
190
+ CSV file with series as columns and dates as rows:
191
+
192
+ ```csv
193
+ Date,gdp_real,consumption,investment
194
+ 2000-01-01,,98.5,95.0
195
+ 2000-02-01,,98.7,95.2
196
+ 2000-03-01,100.5,99.0,95.5
197
+ ```
198
+
199
+ **Requirements**:
200
+ - First column: `Date` (YYYY-MM-DD format)
201
+ - Column names: Must match `series_id` in configuration
202
+ - Missing values: Empty cells or NaN
203
+ - Mixed frequencies: Quarterly series only at quarter-end months
204
+
205
+ **Frequency Support**: Monthly (m), Quarterly (q), Semi-annual (sa), Annual (a)
206
+
207
+ ## News Decomposition
208
+
209
+ ```python
210
+ from dfm_python import news_dfm
211
+
212
+ # Decompose forecast updates
213
+ y_old, y_new, singlenews, actual, forecast, weight, t_miss, v_miss, innov = news_dfm(
214
+ X_old, result_old, t_fcst=100, v_news=0
215
+ )
216
+
217
+ # Forecast update
218
+ forecast_update = y_new - y_old
219
+ ```
220
+
221
+ ## API Reference
222
+
223
+ ### Module-Level Functions
224
+
225
+ - `dfm.from_spec(spec_path, params=None)` - Load from spec CSV + Params
226
+ - `dfm.from_yaml(yaml_path)` - Load from YAML file
227
+ - `dfm.from_dict(config_dict)` - Load from dictionary
228
+ - `dfm.load_data(data_path, sample_start=None, sample_end=None)` - Load data
229
+ - `dfm.train(max_iter=None, threshold=None, **kwargs)` - Train model
230
+ - `dfm.predict(horizon=12)` - Forecast series and factors
231
+ - `dfm.plot(kind='factor', factor_index=0, forecast_horizon=None, save_path=None)` - Plot results
232
+ - `dfm.get_result()` - Get `DFMResult` object
233
+
234
+ ### DFMResult Object
235
+
236
+ Contains all estimation outputs:
237
+ - **Factors**: `Z` (T × m), `C` (N × m)
238
+ - **Parameters**: `A` (m × m), `Q` (m × m), `R` (N × N)
239
+ - **Smoothed Data**: `X_sm` (T × N), `x_sm` (T × N)
240
+ - **Convergence**: `converged`, `num_iter`, `loglik`
241
+ - **Model Fit**: `rmse`, `rmse_per_series`
242
+
243
+ ## Architecture
244
+
245
+ **Core Modules**:
246
+ - `config.py`: Configuration management (DFMConfig, SeriesConfig, BlockConfig, Params)
247
+ - `dfm.py`: Core estimation (DFM class with fit() method)
248
+ - `api.py`: High-level convenience API
249
+ - `kalman.py`: Kalman filter and smoother
250
+ - `news.py`: News decomposition
251
+
252
+ **Core Submodules**:
253
+ - `core/em.py`: EM algorithm core
254
+ - `core/numeric.py`: Numerical utilities
255
+ - `core/diagnostics.py`: Diagnostic functions
256
+ - `core/results.py`: Result metrics
257
+ - `core/grouping.py`: Frequency grouping utilities
258
+
259
+ ## Testing
260
+
261
+ ```bash
262
+ pytest src/test/ -v
263
+ ```
264
+
265
+ Tests are organized into 5 files:
266
+ - `test_dfm.py` - Core DFM estimation
267
+ - `test_kalman.py` - Kalman filter/smoother
268
+ - `test_news.py` - News decomposition
269
+ - `test_config.py` - Configuration and API
270
+ - `test_api.py` - Edge cases and tutorials
271
+
272
+ ## Tutorials
273
+
274
+ 1. **`tutorial/basic_tutorial.py`** - Spec CSV + Params approach
275
+ ```bash
276
+ python tutorial/basic_tutorial.py --spec data/sample_spec.csv --data data/sample_data.csv
277
+ ```
278
+
279
+ 2. **`tutorial/hydra_tutorial.py`** - Hydra decorator approach
280
+ ```bash
281
+ python tutorial/hydra_tutorial.py max_iter=10 threshold=1e-4
282
+ ```
283
+
284
+ ## Troubleshooting
285
+
286
+ **Convergence Issues**: Increase `max_iter`, relax `threshold`, check data quality
287
+
288
+ **Dimension Mismatch**: Ensure `series_id` matches data column names, verify block structure
289
+
290
+ **Numerical Instability**: Monitor warnings, adjust thresholds, investigate data quality
291
+
292
+ ## License
293
+
294
+ MIT License
295
+
296
+ ## Citation
297
+
298
+ ```bibtex
299
+ @software{dfm-python,
300
+ title = {dfm-python: Dynamic Factor Models for Nowcasting and Forecasting},
301
+ author = {DFM Python Contributors},
302
+ year = {2025},
303
+ url = {https://pypi.org/project/dfm-python/}
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ **Package Status**: Stable (v0.2.1)
310
+ **PyPI**: https://pypi.org/project/dfm-python/
311
+ **Python**: 3.12+
312
+ **Latest Changes**: See [CHANGELOG.md](CHANGELOG.md)
@@ -0,0 +1,21 @@
1
+ dfm_python/__init__.py,sha256=ym-3x_ucZaosWzko_4RtuhaT1TmFaFDSpJFWCk644YA,6870
2
+ dfm_python/api.py,sha256=u7Q9m1szVbnX2i1IDd98x4WI45h1AiEiVwBcIr2dbUY,16162
3
+ dfm_python/config.py,sha256=O3B6kPiXUTgPu2-eTzQE20M7-wzBIhxNzgAavIcuhYc,58416
4
+ dfm_python/data_loader.py,sha256=mQ8reju1A6psRF5F5r8B-2gBrHF0wf5en6vYKZ29ouI,30111
5
+ dfm_python/dfm.py,sha256=msb7VJjpkC3KKl3EuopG2YkVnf9wasT5o-ZMCOCFbyk,33963
6
+ dfm_python/kalman.py,sha256=PmXpi0HrhWJliZmx4Mwqg08dzZCPzo7gguLR5EhMCMU,17819
7
+ dfm_python/news.py,sha256=K2nZd2QA9oV2dN_2nT1iWd4-T6riNICTaUYaWCvTg1M,32599
8
+ dfm_python/core/__init__.py,sha256=RI8Q5-VB83wILo2C1_je3JMtkxANXnTy1D52YERXAHI,1684
9
+ dfm_python/core/diagnostics.py,sha256=aNEgIrbtpms-T3xHUE9TB41KxjDh7QVkeI-MjoHyG5A,14586
10
+ dfm_python/core/em.py,sha256=-KF3IQZwAekdV2ytrHNrN1W_L4WxX9V9H4-WFoBFSWw,49963
11
+ dfm_python/core/grouping.py,sha256=X-QHfb7-ZbQBO3GlrK6DszUWOc5xJ1-v3HlUGaqyO6w,1871
12
+ dfm_python/core/helpers.py,sha256=VGYnBYV-nECt_66q2UuNjfiehfvB1yPLg3Ru1Rdf7So,1505
13
+ dfm_python/core/numeric.py,sha256=wEtpKgmPu2fFDXAjF3AbZZfAQ5l2tJ88o5CnPfRGg8I,13318
14
+ dfm_python/core/results.py,sha256=qBTojSHgTADBVS7IcU8Ysz4CZKQjh95hLRGELV3KmTw,2378
15
+ dfm_python/utils/__init__.py,sha256=0OEbVuIp-cv49V7xlDn_38fHEVKyd_faNOY5gUViqa0,578
16
+ dfm_python/utils/aggregation.py,sha256=2Xg8xCiTkfUWTsBtYW84k1GFlBE3TBgFrjhqPn1lpUY,10170
17
+ dfm_python/utils/data_utils.py,sha256=w9RUKxk0FpcNaigBSPFNCzamIAmylUx8-KVzq3M8teY,8261
18
+ dfm_python-0.2.2.dist-info/METADATA,sha256=q4fE6mA7fPlEAM79rn-5z52fRUQIY3Y8owpG9vCEpk0,9678
19
+ dfm_python-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ dfm_python-0.2.2.dist-info/licenses/LICENSE,sha256=6e87br6LCDHjy2hdIR1-sdBQy351qdmSEbOg6jQ7ZXY,1063
21
+ dfm_python-0.2.2.dist-info/RECORD,,