dfm-python 0.4.9__py3-none-any.whl → 0.5.0__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/__init__.py +1 -3
- dfm_python/config/results.py +53 -1
- dfm_python/data/__init__.py +2 -0
- dfm_python/data/utils.py +1 -2
- dfm_python/lightning/data_module.py +49 -7
- dfm_python/models/base.py +267 -397
- dfm_python/models/ddfm.py +118 -249
- dfm_python/models/dfm.py +59 -193
- dfm_python/models/mcmc.py +32 -59
- dfm_python/models/utils.py +145 -255
- dfm_python/ssm/em.py +104 -135
- dfm_python/ssm/kalman.py +28 -45
- dfm_python/ssm/utils.py +2 -2
- dfm_python/utils/helpers.py +1 -1
- dfm_python/utils/statespace.py +4 -5
- dfm_python-0.5.0.dist-info/METADATA +393 -0
- {dfm_python-0.4.9.dist-info → dfm_python-0.5.0.dist-info}/RECORD +19 -19
- dfm_python-0.4.9.dist-info/METADATA +0 -1198
- {dfm_python-0.4.9.dist-info → dfm_python-0.5.0.dist-info}/WHEEL +0 -0
- {dfm_python-0.4.9.dist-info → dfm_python-0.5.0.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py
CHANGED
|
@@ -65,7 +65,7 @@ Note: DFMConfig and SeriesConfig are internal implementation details.
|
|
|
65
65
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
66
66
|
"""
|
|
67
67
|
|
|
68
|
-
__version__ = "0.
|
|
68
|
+
__version__ = "0.5.0"
|
|
69
69
|
|
|
70
70
|
# ============================================================================
|
|
71
71
|
# PUBLIC API DEFINITION
|
|
@@ -107,8 +107,6 @@ from .lightning import (
|
|
|
107
107
|
EMAlgorithm, # Module class
|
|
108
108
|
)
|
|
109
109
|
|
|
110
|
-
# Nowcasting removed - use src.nowcast instead
|
|
111
|
-
|
|
112
110
|
# Model implementations
|
|
113
111
|
from .models.base import BaseFactorModel
|
|
114
112
|
from .models.dfm import DFM
|
dfm_python/config/results.py
CHANGED
|
@@ -6,8 +6,9 @@ This module contains dataclasses for storing DFM estimation results and paramete
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import warnings
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Optional, List, Dict, Any
|
|
9
|
+
from typing import Optional, List, Dict, Any, Tuple
|
|
10
10
|
from abc import ABC
|
|
11
|
+
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
from .schema import DFMConfig
|
|
13
14
|
|
|
@@ -278,6 +279,57 @@ class DDFMResult(BaseResult):
|
|
|
278
279
|
use_idiosyncratic: Optional[bool] = None # Whether idio components were used
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
@dataclass
|
|
283
|
+
class NowcastResult:
|
|
284
|
+
"""Result from a single nowcast calculation.
|
|
285
|
+
|
|
286
|
+
This dataclass contains all information from a nowcast operation,
|
|
287
|
+
including the nowcast value, metadata about the data view, and
|
|
288
|
+
optional diagnostic information.
|
|
289
|
+
|
|
290
|
+
Attributes
|
|
291
|
+
----------
|
|
292
|
+
target_series : str
|
|
293
|
+
Target series ID that was nowcasted.
|
|
294
|
+
target_period : datetime
|
|
295
|
+
Target period for the nowcast (the period being estimated).
|
|
296
|
+
view_date : datetime
|
|
297
|
+
View date (when data is available). This determines which
|
|
298
|
+
data points are masked/unmasked in the nowcast calculation.
|
|
299
|
+
nowcast_value : float
|
|
300
|
+
The calculated nowcast value for the target series.
|
|
301
|
+
confidence_interval : Tuple[float, float], optional
|
|
302
|
+
Confidence interval (lower, upper) for the nowcast, if available.
|
|
303
|
+
factors_at_view : np.ndarray, optional
|
|
304
|
+
Factor values at the view_date (m,). These are the updated
|
|
305
|
+
factor states after applying the data view masking.
|
|
306
|
+
dfm_result : BaseResult, optional
|
|
307
|
+
Full DFM/DDFM result for this view. Can be used for further
|
|
308
|
+
analysis or diagnostics.
|
|
309
|
+
data_availability : Dict[str, int], optional
|
|
310
|
+
Dictionary with keys 'n_available' and 'n_missing' indicating
|
|
311
|
+
how many data points were available vs missing in the data view.
|
|
312
|
+
|
|
313
|
+
Examples
|
|
314
|
+
--------
|
|
315
|
+
>>> from dfm_python import DFM
|
|
316
|
+
>>> model = DFM()
|
|
317
|
+
>>> trainer.fit(model, data_module)
|
|
318
|
+
>>> # Get nowcast with full result
|
|
319
|
+
>>> result = model.nowcast('gdp', view_date='2024-01-15', return_result=True)
|
|
320
|
+
>>> print(f"Nowcast: {result.nowcast_value}")
|
|
321
|
+
>>> print(f"Available data: {result.data_availability['n_available']}")
|
|
322
|
+
"""
|
|
323
|
+
target_series: str
|
|
324
|
+
target_period: datetime
|
|
325
|
+
view_date: datetime
|
|
326
|
+
nowcast_value: float
|
|
327
|
+
confidence_interval: Optional[Tuple[float, float]] = None # (lower, upper)
|
|
328
|
+
factors_at_view: Optional[np.ndarray] = None # Factor values at view_date
|
|
329
|
+
dfm_result: Optional[BaseResult] = None # Full DFM/DDFM result for this view
|
|
330
|
+
data_availability: Optional[Dict[str, int]] = None # n_available, n_missing
|
|
331
|
+
|
|
332
|
+
|
|
281
333
|
@dataclass
|
|
282
334
|
class FitParams:
|
|
283
335
|
"""Parameter overrides for DFM model fitting.
|
dfm_python/data/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ This module provides:
|
|
|
4
4
|
- Dataset classes: DFMDataset (linear DFM), DDFMDataset (deep DDFM)
|
|
5
5
|
- DataLoader factories: create_dfm_dataloader, create_ddfm_dataloader
|
|
6
6
|
- Data reading: read_data, load_data
|
|
7
|
+
- Nowcast data classes moved to src.nowcasting in main project
|
|
7
8
|
|
|
8
9
|
Note: Transformation functions are application-specific and should be provided
|
|
9
10
|
by users in their preprocessing pipelines. The package does not include
|
|
@@ -13,6 +14,7 @@ transformation utilities to remain generic.
|
|
|
13
14
|
from .dataset import DFMDataset, DDFMDataset
|
|
14
15
|
from .dataloader import create_dfm_dataloader, create_ddfm_dataloader
|
|
15
16
|
from .utils import read_data, load_data
|
|
17
|
+
# Nowcast data classes moved to src.nowcasting in main project
|
|
16
18
|
|
|
17
19
|
__all__ = [
|
|
18
20
|
# Datasets
|
dfm_python/data/utils.py
CHANGED
|
@@ -17,7 +17,6 @@ from ..config import DFMConfig
|
|
|
17
17
|
from ..config.utils import FREQUENCY_HIERARCHY
|
|
18
18
|
from ..utils.time import TimeIndex, parse_timestamp
|
|
19
19
|
from ..utils.helpers import (
|
|
20
|
-
safe_get_attr,
|
|
21
20
|
get_frequencies,
|
|
22
21
|
get_series_ids,
|
|
23
22
|
)
|
|
@@ -267,7 +266,7 @@ def load_data(
|
|
|
267
266
|
_logger.info(f"Loaded data: {X.shape[0]} time periods, {X.shape[1]} series (raw, not transformed)")
|
|
268
267
|
|
|
269
268
|
# Validate data quality
|
|
270
|
-
clock =
|
|
269
|
+
clock = getattr(config, 'clock', 'm')
|
|
271
270
|
clock_hierarchy = FREQUENCY_HIERARCHY.get(clock, 3)
|
|
272
271
|
|
|
273
272
|
frequencies = get_frequencies(config)
|
|
@@ -182,34 +182,76 @@ def _get_scale(scaler: Any, data: np.ndarray) -> Optional[np.ndarray]:
|
|
|
182
182
|
def _normalize_wx(wx: np.ndarray) -> np.ndarray:
|
|
183
183
|
"""Normalize Wx to avoid division by zero.
|
|
184
184
|
|
|
185
|
+
This function replaces zero or NaN values in Wx with 1.0 to prevent
|
|
186
|
+
division by zero during standardization/unstandardization.
|
|
187
|
+
|
|
185
188
|
Parameters
|
|
186
189
|
----------
|
|
187
190
|
wx : np.ndarray
|
|
188
|
-
Scale values (N,)
|
|
191
|
+
Scale values (N,), may contain zeros or NaN
|
|
189
192
|
|
|
190
193
|
Returns
|
|
191
194
|
-------
|
|
192
195
|
np.ndarray
|
|
193
|
-
Normalized scale values with zeros replaced by 1.0
|
|
196
|
+
Normalized scale values with zeros and NaN replaced by 1.0
|
|
194
197
|
"""
|
|
195
|
-
|
|
198
|
+
# Replace both zero and NaN with 1.0
|
|
199
|
+
return np.where((wx == 0) | np.isnan(wx), 1.0, wx)
|
|
196
200
|
|
|
197
201
|
|
|
198
202
|
def _compute_mx_wx(data: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
199
203
|
"""Compute Mx and Wx from data as fallback.
|
|
200
204
|
|
|
205
|
+
This function computes mean (Mx) and standard deviation (Wx) from data,
|
|
206
|
+
handling missing values (NaN) by using nan-aware functions. If NaN values
|
|
207
|
+
are detected, a warning is issued recommending imputation.
|
|
208
|
+
|
|
201
209
|
Parameters
|
|
202
210
|
----------
|
|
203
211
|
data : np.ndarray
|
|
204
|
-
Processed data array (T x N)
|
|
212
|
+
Processed data array (T x N), may contain NaN values
|
|
205
213
|
|
|
206
214
|
Returns
|
|
207
215
|
-------
|
|
208
216
|
Tuple[np.ndarray, np.ndarray]
|
|
209
|
-
(Mx, Wx) tuple where Mx is mean and Wx is normalized std
|
|
217
|
+
(Mx, Wx) tuple where Mx is mean and Wx is normalized std.
|
|
218
|
+
NaN values are handled using nanmean and nanstd.
|
|
219
|
+
|
|
220
|
+
Notes
|
|
221
|
+
-----
|
|
222
|
+
If data contains NaN values, this function will:
|
|
223
|
+
1. Use np.nanmean() and np.nanstd() to compute statistics ignoring NaN
|
|
224
|
+
2. Issue a warning recommending data imputation for better results
|
|
225
|
+
3. Continue processing with available data
|
|
210
226
|
"""
|
|
211
|
-
|
|
212
|
-
|
|
227
|
+
# Check for NaN values
|
|
228
|
+
has_nan = np.any(np.isnan(data))
|
|
229
|
+
if has_nan:
|
|
230
|
+
nan_count = np.sum(np.isnan(data))
|
|
231
|
+
nan_pct = 100.0 * nan_count / data.size
|
|
232
|
+
_logger.warning(
|
|
233
|
+
f"Data contains {nan_count} NaN values ({nan_pct:.1f}%). "
|
|
234
|
+
f"Using nanmean/nanstd to compute standardization parameters. "
|
|
235
|
+
f"For better results, consider imputing missing values before creating DataModule. "
|
|
236
|
+
f"Suggested approaches: forward-fill, backward-fill, or interpolation."
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Use nan-aware functions to handle missing values
|
|
240
|
+
mx = np.nanmean(data, axis=0)
|
|
241
|
+
wx = np.nanstd(data, axis=0)
|
|
242
|
+
|
|
243
|
+
# Check if any series have all NaN values (would result in NaN std)
|
|
244
|
+
nan_std_mask = np.isnan(wx)
|
|
245
|
+
if np.any(nan_std_mask):
|
|
246
|
+
n_nan_std = np.sum(nan_std_mask)
|
|
247
|
+
_logger.warning(
|
|
248
|
+
f"{n_nan_std} series have all NaN values, resulting in NaN standard deviation. "
|
|
249
|
+
f"These will be normalized to 1.0 to avoid division by zero. "
|
|
250
|
+
f"Consider imputing or removing these series."
|
|
251
|
+
)
|
|
252
|
+
# Replace NaN std with 1.0 (will be normalized by _normalize_wx anyway)
|
|
253
|
+
wx = np.where(nan_std_mask, 1.0, wx)
|
|
254
|
+
|
|
213
255
|
wx = _normalize_wx(wx)
|
|
214
256
|
return mx, wx
|
|
215
257
|
|