dfm-python 0.4.2__py3-none-any.whl → 0.4.3__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 +9 -11
- dfm_python/config/__init__.py +8 -13
- dfm_python/config/adapter.py +12 -210
- dfm_python/config/results.py +18 -10
- dfm_python/config/schema.py +31 -66
- dfm_python/config/utils.py +13 -13
- dfm_python/data/__init__.py +5 -29
- dfm_python/data/dataloader.py +51 -8
- dfm_python/data/utils.py +28 -30
- dfm_python/encoder/vae.py +3 -2
- dfm_python/lightning/__init__.py +27 -14
- dfm_python/lightning/data_module.py +348 -220
- dfm_python/lightning/scaling.py +432 -0
- dfm_python/models/__init__.py +3 -3
- dfm_python/models/base.py +335 -6
- dfm_python/models/ddfm.py +1137 -321
- dfm_python/models/dfm.py +125 -586
- dfm_python/models/estimation.py +13 -14
- dfm_python/nowcast/__init__.py +17 -8
- dfm_python/nowcast/dataview.py +7 -7
- dfm_python/nowcast/helpers.py +6 -6
- dfm_python/nowcast/nowcast.py +274 -26
- dfm_python/nowcast/splitters.py +431 -0
- dfm_python/nowcast/transformers.py +330 -0
- dfm_python/nowcast/utils.py +11 -11
- dfm_python/ssm/kalman.py +13 -27
- dfm_python/ssm/utils.py +3 -1
- dfm_python/trainer/__init__.py +22 -22
- dfm_python/trainer/ddfm.py +7 -7
- dfm_python/trainer/dfm.py +7 -7
- dfm_python/utils/__init__.py +15 -15
- dfm_python/utils/data.py +17 -20
- dfm_python/utils/diagnostics.py +28 -23
- dfm_python/utils/helpers.py +7 -7
- dfm_python/utils/statespace.py +41 -71
- dfm_python/utils/time.py +148 -249
- {dfm_python-0.4.2.dist-info → dfm_python-0.4.3.dist-info}/METADATA +24 -64
- dfm_python-0.4.3.dist-info/RECORD +52 -0
- dfm_python/data/transformation.py +0 -313
- dfm_python/lightning/ddfm_module.py +0 -580
- dfm_python/lightning/dfm_module.py +0 -466
- dfm_python-0.4.2.dist-info/RECORD +0 -52
- {dfm_python-0.4.2.dist-info → dfm_python-0.4.3.dist-info}/WHEEL +0 -0
- {dfm_python-0.4.2.dist-info → dfm_python-0.4.3.dist-info}/licenses/LICENSE +0 -0
dfm_python/__init__.py
CHANGED
|
@@ -19,7 +19,7 @@ has a frequency faster than the clock, a ValueError will be raised.
|
|
|
19
19
|
|
|
20
20
|
Key Features:
|
|
21
21
|
- Hydra-based configuration (YAML files) - primary configuration method
|
|
22
|
-
- Expects preprocessed data from users - package
|
|
22
|
+
- Expects preprocessed data from users - users handle all preprocessing, package extracts statistics from pipeline
|
|
23
23
|
- Flexible block structure for factor modeling
|
|
24
24
|
- Robust handling of missing data (internal spline interpolation)
|
|
25
25
|
- Automatic standardization and data clipping
|
|
@@ -27,11 +27,11 @@ Key Features:
|
|
|
27
27
|
|
|
28
28
|
Example (Standard Lightning Pattern):
|
|
29
29
|
>>> from dfm_python import DFM, DFMDataModule, DFMTrainer
|
|
30
|
-
>>> import
|
|
30
|
+
>>> import pandas as pd
|
|
31
31
|
>>>
|
|
32
32
|
>>> # Step 1: Load and preprocess data
|
|
33
|
-
>>> df =
|
|
34
|
-
>>> df_processed = df
|
|
33
|
+
>>> df = pd.read_csv('data/sample_data.csv')
|
|
34
|
+
>>> df_processed = df[[col for col in df.columns if col != 'date']]
|
|
35
35
|
>>>
|
|
36
36
|
>>> # Step 2: Create DataModule
|
|
37
37
|
>>> dm = DFMDataModule(config_path='config/default.yaml', data=df_processed)
|
|
@@ -67,7 +67,7 @@ Note: DFMConfig and SeriesConfig are internal implementation details.
|
|
|
67
67
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
-
__version__ = "0.4.
|
|
70
|
+
__version__ = "0.4.3"
|
|
71
71
|
|
|
72
72
|
# ============================================================================
|
|
73
73
|
# PUBLIC API DEFINITION
|
|
@@ -79,8 +79,8 @@ __version__ = "0.4.2"
|
|
|
79
79
|
# Public API categories:
|
|
80
80
|
# 1. Configuration: DFMConfig, SeriesConfig, config sources
|
|
81
81
|
# 2. High-level API: DFM, DDFM, module-level convenience functions
|
|
82
|
-
# 3. Core utilities:
|
|
83
|
-
# 4. Models: BaseFactorModel,
|
|
82
|
+
# 3. Core utilities: TimeIndex, diagnostics
|
|
83
|
+
# 4. Models: BaseFactorModel, DDFM (low-level)
|
|
84
84
|
# 5. Nowcasting: Nowcast, result classes, para_const
|
|
85
85
|
# 6. Data & Results: DFMResult
|
|
86
86
|
# ============================================================================
|
|
@@ -94,8 +94,6 @@ from .config import (
|
|
|
94
94
|
# Internal imports (for backward compatibility, but not recommended)
|
|
95
95
|
from .config import DFMConfig, SeriesConfig # Internal use only
|
|
96
96
|
|
|
97
|
-
# Data utilities
|
|
98
|
-
|
|
99
97
|
# Results
|
|
100
98
|
from .config.results import DFMResult, DDFMResult, BaseResult
|
|
101
99
|
|
|
@@ -125,14 +123,14 @@ from .nowcast.helpers import (
|
|
|
125
123
|
|
|
126
124
|
# Model implementations
|
|
127
125
|
from .models.base import BaseFactorModel
|
|
128
|
-
from .models.dfm import
|
|
126
|
+
from .models.dfm import DFM
|
|
129
127
|
|
|
130
128
|
# DDFM high-level API and low-level model (PyTorch is mandatory)
|
|
131
129
|
from .models.ddfm import DDFM, DDFMModel
|
|
132
130
|
|
|
133
131
|
__all__ = [
|
|
134
132
|
# Core classes
|
|
135
|
-
'DFM', '
|
|
133
|
+
'DFM', 'Nowcast',
|
|
136
134
|
# Model base and implementations
|
|
137
135
|
'BaseFactorModel',
|
|
138
136
|
# Nowcast result classes
|
dfm_python/config/__init__.py
CHANGED
|
@@ -18,21 +18,18 @@ from .adapter import (
|
|
|
18
18
|
HydraSource,
|
|
19
19
|
MergedConfigSource,
|
|
20
20
|
make_config_source,
|
|
21
|
-
# Note: from_spec is in models/dfm.py, not in adapter
|
|
22
|
-
# Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
|
|
23
|
-
# are available from adapter module but not exported as public API
|
|
24
21
|
)
|
|
25
22
|
from .utils import (
|
|
26
23
|
FREQUENCY_HIERARCHY,
|
|
27
24
|
PERIODS_PER_YEAR,
|
|
28
25
|
get_periods_per_year,
|
|
29
26
|
get_annual_factor,
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
compute_idio_lengths,
|
|
28
|
+
get_tent_weights,
|
|
32
29
|
generate_tent_weights,
|
|
33
30
|
generate_R_mat,
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
get_agg_structure,
|
|
32
|
+
group_by_freq,
|
|
36
33
|
)
|
|
37
34
|
|
|
38
35
|
__all__ = [
|
|
@@ -53,13 +50,11 @@ __all__ = [
|
|
|
53
50
|
'PERIODS_PER_YEAR',
|
|
54
51
|
'get_periods_per_year',
|
|
55
52
|
'get_annual_factor',
|
|
56
|
-
'
|
|
57
|
-
'
|
|
53
|
+
'compute_idio_lengths',
|
|
54
|
+
'get_tent_weights',
|
|
58
55
|
'generate_tent_weights',
|
|
59
56
|
'generate_R_mat',
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
# Note: Private functions (_load_config_from_dataframe, _write_series_blocks_yaml)
|
|
63
|
-
# are available from adapter module but not exported as public API
|
|
57
|
+
'get_agg_structure',
|
|
58
|
+
'group_by_freq',
|
|
64
59
|
]
|
|
65
60
|
|
dfm_python/config/adapter.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
This module provides adapters for loading DFMConfig from various sources:
|
|
4
4
|
- YAML files (with Hydra/OmegaConf support)
|
|
5
5
|
- Dictionary configurations
|
|
6
|
-
- Spec CSV files (series definitions)
|
|
7
6
|
- Hydra DictConfig objects
|
|
8
7
|
- Merged configurations from multiple sources
|
|
9
8
|
|
|
@@ -25,12 +24,11 @@ except ImportError:
|
|
|
25
24
|
|
|
26
25
|
if TYPE_CHECKING:
|
|
27
26
|
from .schema import DDFMConfig
|
|
28
|
-
import polars as pl
|
|
29
27
|
|
|
30
28
|
_logger = get_logger(__name__)
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
def
|
|
31
|
+
def _load_config_defaults(cfg, root_config_dir, config_type: str) -> Optional[dict]:
|
|
34
32
|
"""Load config from defaults or direct path (helper for series/blocks loading).
|
|
35
33
|
|
|
36
34
|
Parameters
|
|
@@ -195,7 +193,7 @@ class YamlSource:
|
|
|
195
193
|
|
|
196
194
|
# Load series from config/series/default.yaml
|
|
197
195
|
series_list = []
|
|
198
|
-
series_dict =
|
|
196
|
+
series_dict = _load_config_defaults(cfg, root_config_dir, 'series')
|
|
199
197
|
series_loaded = series_dict is not None
|
|
200
198
|
|
|
201
199
|
# Convert series dict to SeriesConfig objects
|
|
@@ -216,7 +214,8 @@ class YamlSource:
|
|
|
216
214
|
frequency=series_data.get('frequency', 'm'),
|
|
217
215
|
transformation=series_data.get('transformation', 'lin'),
|
|
218
216
|
blocks=series_data.get('blocks', []),
|
|
219
|
-
release_date=release_date
|
|
217
|
+
release_date=release_date,
|
|
218
|
+
scaler=series_data.get('scaler', None) # Optional, for per-series scaling
|
|
220
219
|
))
|
|
221
220
|
|
|
222
221
|
# If no series loaded from separate files, try to get from main config
|
|
@@ -248,7 +247,7 @@ class YamlSource:
|
|
|
248
247
|
blocks_properties_from_config = blocks_data
|
|
249
248
|
|
|
250
249
|
# Also try loading from config/blocks/default.yaml
|
|
251
|
-
blocks_dict_raw =
|
|
250
|
+
blocks_dict_raw = _load_config_defaults(cfg, root_config_dir, 'blocks')
|
|
252
251
|
if blocks_dict_raw is not None:
|
|
253
252
|
blocks_properties_from_config.update(blocks_dict_raw)
|
|
254
253
|
|
|
@@ -403,7 +402,7 @@ class MergedConfigSource:
|
|
|
403
402
|
"""Merge multiple configuration sources.
|
|
404
403
|
|
|
405
404
|
This allows combining configurations from different sources,
|
|
406
|
-
e.g., base YAML config + series from
|
|
405
|
+
e.g., base YAML config + series from another YAML or dict.
|
|
407
406
|
|
|
408
407
|
The merge strategy:
|
|
409
408
|
- Base config provides main settings (threshold, max_iter, clock, blocks)
|
|
@@ -493,86 +492,6 @@ class MergedConfigSource:
|
|
|
493
492
|
)
|
|
494
493
|
|
|
495
494
|
|
|
496
|
-
def _write_series_blocks_yaml(
|
|
497
|
-
config: DFMConfig,
|
|
498
|
-
output_dir: Path,
|
|
499
|
-
series_filename: str,
|
|
500
|
-
blocks_filename: str
|
|
501
|
-
) -> Tuple[Path, Path]:
|
|
502
|
-
"""Write series and block YAML files for a given configuration."""
|
|
503
|
-
series_dir = output_dir / 'series'
|
|
504
|
-
series_dir.mkdir(parents=True, exist_ok=True)
|
|
505
|
-
series_path = series_dir / f'{series_filename}.yaml'
|
|
506
|
-
|
|
507
|
-
blocks_dir = output_dir / 'blocks'
|
|
508
|
-
blocks_dir.mkdir(parents=True, exist_ok=True)
|
|
509
|
-
blocks_path = blocks_dir / f'{blocks_filename}.yaml'
|
|
510
|
-
|
|
511
|
-
series_dict = {}
|
|
512
|
-
block_names = config.block_names
|
|
513
|
-
|
|
514
|
-
for series in config.series:
|
|
515
|
-
if isinstance(series.blocks, list) and len(series.blocks) > 0:
|
|
516
|
-
if isinstance(series.blocks[0], int):
|
|
517
|
-
blocks_names = [block_names[i] for i, val in enumerate(series.blocks) if val != 0]
|
|
518
|
-
else:
|
|
519
|
-
blocks_names = list(series.blocks)
|
|
520
|
-
else:
|
|
521
|
-
blocks_names = [DEFAULT_BLOCK_NAME]
|
|
522
|
-
|
|
523
|
-
series_entry = {
|
|
524
|
-
'series_name': series.series_name,
|
|
525
|
-
'frequency': series.frequency,
|
|
526
|
-
'transformation': series.transformation,
|
|
527
|
-
'blocks': blocks_names
|
|
528
|
-
}
|
|
529
|
-
if series.release_date is not None:
|
|
530
|
-
series_entry['release'] = series.release_date
|
|
531
|
-
series_dict[series.series_id] = series_entry
|
|
532
|
-
|
|
533
|
-
blocks_dict = {}
|
|
534
|
-
for block_name, block_config in config.blocks.items():
|
|
535
|
-
block_entry = {
|
|
536
|
-
'factors': block_config.get('factors', 1),
|
|
537
|
-
'ar_lag': block_config.get('ar_lag', 1),
|
|
538
|
-
'clock': block_config.get('clock', 'm')
|
|
539
|
-
}
|
|
540
|
-
if block_config.get('notes'):
|
|
541
|
-
block_entry['notes'] = block_config['notes']
|
|
542
|
-
blocks_dict[block_name] = block_entry
|
|
543
|
-
|
|
544
|
-
def _write_yaml(path: Path, payload: Dict[str, Any]) -> None:
|
|
545
|
-
try:
|
|
546
|
-
import yaml # type: ignore
|
|
547
|
-
yaml_kwargs = {'default_flow_style': False, 'sort_keys': False, 'allow_unicode': True}
|
|
548
|
-
import io
|
|
549
|
-
stream = io.StringIO()
|
|
550
|
-
yaml.dump(payload, stream, **yaml_kwargs)
|
|
551
|
-
yaml_content = stream.getvalue()
|
|
552
|
-
lines = yaml_content.split('\n')
|
|
553
|
-
formatted_lines = []
|
|
554
|
-
for i, line in enumerate(lines):
|
|
555
|
-
if line and not line.startswith(' ') and ':' in line and not line.strip().startswith('#'):
|
|
556
|
-
if i > 0 and formatted_lines and formatted_lines[-1].strip():
|
|
557
|
-
formatted_lines.append('')
|
|
558
|
-
formatted_lines.append(line)
|
|
559
|
-
with open(path, 'w', encoding='utf-8') as f:
|
|
560
|
-
f.write('\n'.join(formatted_lines))
|
|
561
|
-
except ImportError:
|
|
562
|
-
try:
|
|
563
|
-
from omegaconf import OmegaConf # type: ignore
|
|
564
|
-
except ImportError as exc: # pragma: no cover
|
|
565
|
-
raise ImportError(
|
|
566
|
-
"Either PyYAML or omegaconf is required for YAML generation. "
|
|
567
|
-
"Install with: pip install pyyaml or pip install omegaconf"
|
|
568
|
-
) from exc
|
|
569
|
-
cfg = OmegaConf.create(payload)
|
|
570
|
-
OmegaConf.save(cfg, path)
|
|
571
|
-
|
|
572
|
-
_write_yaml(series_path, series_dict)
|
|
573
|
-
_write_yaml(blocks_path, blocks_dict)
|
|
574
|
-
|
|
575
|
-
return series_path, blocks_path
|
|
576
495
|
|
|
577
496
|
|
|
578
497
|
def make_config_source(
|
|
@@ -580,7 +499,6 @@ def make_config_source(
|
|
|
580
499
|
*,
|
|
581
500
|
yaml: Optional[Union[str, Path]] = None,
|
|
582
501
|
mapping: Optional[Union[Dict[str, Any], Any]] = None,
|
|
583
|
-
spec: Optional[Union[str, Path]] = None,
|
|
584
502
|
hydra: Optional[Union[Dict[str, Any], 'DictConfig']] = None,
|
|
585
503
|
) -> ConfigSource:
|
|
586
504
|
"""Create a ConfigSource adapter from various input formats.
|
|
@@ -598,8 +516,6 @@ def make_config_source(
|
|
|
598
516
|
Explicit YAML file path
|
|
599
517
|
mapping : dict, optional
|
|
600
518
|
Explicit dictionary config
|
|
601
|
-
spec : str or Path, optional
|
|
602
|
-
Explicit spec CSV file path
|
|
603
519
|
hydra : DictConfig or dict, optional
|
|
604
520
|
Explicit Hydra config
|
|
605
521
|
|
|
@@ -618,18 +534,17 @@ def make_config_source(
|
|
|
618
534
|
>>>
|
|
619
535
|
>>> # Explicit keyword arguments
|
|
620
536
|
>>> source = make_config_source(yaml='config/default.yaml')
|
|
621
|
-
>>> source = make_config_source(spec='data/spec.csv')
|
|
622
537
|
>>>
|
|
623
|
-
>>> # Merge YAML base +
|
|
538
|
+
>>> # Merge YAML base + dict override
|
|
624
539
|
>>> base = make_config_source(yaml='config/default.yaml')
|
|
625
|
-
>>> override = make_config_source(
|
|
540
|
+
>>> override = make_config_source(mapping={'series': [...]})
|
|
626
541
|
>>> merged = MergedConfigSource(base, override)
|
|
627
542
|
"""
|
|
628
543
|
# Check for explicit keyword arguments (only one allowed)
|
|
629
|
-
explicit_kwargs = [k for k, v in [('yaml', yaml), ('mapping', mapping), ('
|
|
544
|
+
explicit_kwargs = [k for k, v in [('yaml', yaml), ('mapping', mapping), ('hydra', hydra)] if v is not None]
|
|
630
545
|
if len(explicit_kwargs) > 1:
|
|
631
546
|
raise ValueError(
|
|
632
|
-
f"Only one of yaml, mapping,
|
|
547
|
+
f"Only one of yaml, mapping, or hydra can be specified. "
|
|
633
548
|
f"Got: {', '.join(explicit_kwargs)}. "
|
|
634
549
|
f"For merging configs, use MergedConfigSource."
|
|
635
550
|
)
|
|
@@ -655,12 +570,6 @@ def make_config_source(
|
|
|
655
570
|
return YamlSource(yaml)
|
|
656
571
|
if mapping is not None:
|
|
657
572
|
return DictSource(_coerce_to_mapping(mapping))
|
|
658
|
-
if spec is not None:
|
|
659
|
-
raise ValueError(
|
|
660
|
-
"Direct spec CSV loading has been removed. "
|
|
661
|
-
"Use dfm_python.api.from_spec() or from_spec_df() to convert the CSV "
|
|
662
|
-
"to YAML files, then load via YAML/Hydra."
|
|
663
|
-
)
|
|
664
573
|
if hydra is not None:
|
|
665
574
|
return HydraSource(hydra)
|
|
666
575
|
|
|
@@ -668,7 +577,7 @@ def make_config_source(
|
|
|
668
577
|
if source is None:
|
|
669
578
|
raise ValueError(
|
|
670
579
|
"No configuration source provided. "
|
|
671
|
-
"Specify source, yaml, mapping,
|
|
580
|
+
"Specify source, yaml, mapping, or hydra."
|
|
672
581
|
)
|
|
673
582
|
|
|
674
583
|
# If already a ConfigSource, return as-is
|
|
@@ -693,8 +602,7 @@ def make_config_source(
|
|
|
693
602
|
elif suffix == '.csv':
|
|
694
603
|
raise ValueError(
|
|
695
604
|
"Direct CSV configs are no longer supported. "
|
|
696
|
-
"
|
|
697
|
-
"to YAML files, then load the YAML configuration."
|
|
605
|
+
"Please use YAML configuration files instead."
|
|
698
606
|
)
|
|
699
607
|
else:
|
|
700
608
|
# Default to YAML if extension unclear
|
|
@@ -715,112 +623,6 @@ def make_config_source(
|
|
|
715
623
|
)
|
|
716
624
|
|
|
717
625
|
|
|
718
|
-
# ============================================================================
|
|
719
|
-
# Internal helper: Load config from DataFrame
|
|
720
|
-
# ============================================================================
|
|
721
|
-
|
|
722
|
-
def _load_config_from_dataframe(df: pl.DataFrame) -> DFMConfig:
|
|
723
|
-
"""Load configuration from DataFrame (internal helper).
|
|
724
|
-
|
|
725
|
-
This function converts a DataFrame with series specifications into a DFMConfig.
|
|
726
|
-
|
|
727
|
-
Parameters
|
|
728
|
-
----------
|
|
729
|
-
df : pl.DataFrame
|
|
730
|
-
Polars DataFrame with columns: series_id, series_name, frequency, transformation, blocks
|
|
731
|
-
Optional columns: Release (release date)
|
|
732
|
-
Block_* columns: Binary indicators for block membership
|
|
733
|
-
|
|
734
|
-
Returns
|
|
735
|
-
-------
|
|
736
|
-
DFMConfig
|
|
737
|
-
Model configuration object
|
|
738
|
-
"""
|
|
739
|
-
series_list = []
|
|
740
|
-
block_names_set = set()
|
|
741
|
-
|
|
742
|
-
# Convert polars DataFrame to dict of rows for easier access
|
|
743
|
-
df_dict = df.to_dict(as_series=False)
|
|
744
|
-
|
|
745
|
-
# First, try to find Block_* columns (CSV format)
|
|
746
|
-
block_cols = [col for col in df.columns
|
|
747
|
-
if (col.startswith('Block_') or col.startswith('Block-'))
|
|
748
|
-
and col not in ['blocks']]
|
|
749
|
-
|
|
750
|
-
# Iterate over rows
|
|
751
|
-
num_rows = len(df)
|
|
752
|
-
for i in range(num_rows):
|
|
753
|
-
# Parse blocks from Block_* columns or 'blocks' column
|
|
754
|
-
blocks = []
|
|
755
|
-
|
|
756
|
-
if block_cols:
|
|
757
|
-
# CSV format: global block, block_1, etc. columns with 1/0 values
|
|
758
|
-
for block_col in block_cols:
|
|
759
|
-
if block_col in df_dict:
|
|
760
|
-
block_value = df_dict[block_col][i]
|
|
761
|
-
# Handle various types: int, float, numpy int/float, bool, string "1"/"0"
|
|
762
|
-
# Check numpy types first (numpy.int64, numpy.float64, etc.)
|
|
763
|
-
if hasattr(block_value, 'item'):
|
|
764
|
-
# numpy scalar - convert to Python native type
|
|
765
|
-
block_value = block_value.item()
|
|
766
|
-
|
|
767
|
-
if isinstance(block_value, (int, float)) and block_value != 0:
|
|
768
|
-
blocks.append(block_col)
|
|
769
|
-
elif isinstance(block_value, bool) and block_value:
|
|
770
|
-
blocks.append(block_col)
|
|
771
|
-
elif isinstance(block_value, str) and block_value.strip() in ['1', 'True', 'true']:
|
|
772
|
-
blocks.append(block_col)
|
|
773
|
-
else:
|
|
774
|
-
# Fallback: try 'blocks' column (comma-separated string or list)
|
|
775
|
-
if 'blocks' in df_dict:
|
|
776
|
-
blocks_str = df_dict['blocks'][i]
|
|
777
|
-
if isinstance(blocks_str, str):
|
|
778
|
-
blocks = [b.strip() for b in blocks_str.split(',')]
|
|
779
|
-
elif isinstance(blocks_str, list):
|
|
780
|
-
blocks = blocks_str
|
|
781
|
-
else:
|
|
782
|
-
blocks = [DEFAULT_BLOCK_NAME]
|
|
783
|
-
else:
|
|
784
|
-
blocks = [DEFAULT_BLOCK_NAME]
|
|
785
|
-
|
|
786
|
-
# Ensure at least one block
|
|
787
|
-
if not blocks:
|
|
788
|
-
blocks = [DEFAULT_BLOCK_NAME]
|
|
789
|
-
|
|
790
|
-
# Track block names
|
|
791
|
-
for block in blocks:
|
|
792
|
-
block_names_set.add(block)
|
|
793
|
-
|
|
794
|
-
# Parse release_date if available
|
|
795
|
-
release_date = None
|
|
796
|
-
if 'Release' in df_dict:
|
|
797
|
-
release_date = df_dict['Release'][i]
|
|
798
|
-
if release_date is not None:
|
|
799
|
-
try:
|
|
800
|
-
release_date = int(release_date)
|
|
801
|
-
except (ValueError, TypeError):
|
|
802
|
-
release_date = None
|
|
803
|
-
|
|
804
|
-
series_id = df_dict.get('series_id', [f"series_{j}" for j in range(num_rows)])[i]
|
|
805
|
-
series_name = df_dict.get('series_name', df_dict.get('series_id', [f"Series {j}" for j in range(num_rows)]))[i]
|
|
806
|
-
|
|
807
|
-
series_list.append(SeriesConfig(
|
|
808
|
-
series_id=series_id,
|
|
809
|
-
series_name=series_name,
|
|
810
|
-
frequency=df_dict.get('frequency', ['m'] * num_rows)[i],
|
|
811
|
-
transformation=df_dict.get('transformation', ['lin'] * num_rows)[i],
|
|
812
|
-
blocks=blocks,
|
|
813
|
-
release_date=release_date
|
|
814
|
-
))
|
|
815
|
-
|
|
816
|
-
# Create default blocks if none specified
|
|
817
|
-
block_names = sorted(block_names_set) if block_names_set else [DEFAULT_BLOCK_NAME]
|
|
818
|
-
blocks = {}
|
|
819
|
-
for block_name in block_names:
|
|
820
|
-
blocks[block_name] = {'factors': 1, 'ar_lag': 1, 'clock': 'm'}
|
|
821
|
-
|
|
822
|
-
# block_names is derived automatically in DFMConfig.__post_init__ from blocks dict
|
|
823
|
-
return DFMConfig(series=series_list, blocks=blocks)
|
|
824
626
|
|
|
825
627
|
|
|
826
628
|
# ============================================================================
|
dfm_python/config/results.py
CHANGED
|
@@ -4,6 +4,7 @@ This module contains dataclasses for storing DFM estimation results and paramete
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
+
import warnings
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from typing import Optional, List, Dict, Any
|
|
9
10
|
from abc import ABC
|
|
@@ -118,8 +119,8 @@ class BaseResult(ABC):
|
|
|
118
119
|
except Exception:
|
|
119
120
|
return self.num_state()
|
|
120
121
|
|
|
121
|
-
def
|
|
122
|
-
"""Return factors as
|
|
122
|
+
def to_pandas_factors(self, time_index: Optional[object] = None, factor_names: Optional[List[str]] = None):
|
|
123
|
+
"""Return factors as pandas DataFrame.
|
|
123
124
|
|
|
124
125
|
Parameters
|
|
125
126
|
----------
|
|
@@ -129,7 +130,7 @@ class BaseResult(ABC):
|
|
|
129
130
|
Column names. Defaults to F1..Fm.
|
|
130
131
|
"""
|
|
131
132
|
try:
|
|
132
|
-
import
|
|
133
|
+
import pandas as pd
|
|
133
134
|
from ..utils.time import TimeIndex
|
|
134
135
|
|
|
135
136
|
cols = factor_names if factor_names is not None else [f"F{i+1}" for i in range(self.num_state())]
|
|
@@ -152,17 +153,24 @@ class BaseResult(ABC):
|
|
|
152
153
|
if time_list:
|
|
153
154
|
df_dict['time'] = time_list
|
|
154
155
|
|
|
155
|
-
return
|
|
156
|
+
return pd.DataFrame(df_dict)
|
|
156
157
|
except (ImportError, ValueError, TypeError):
|
|
157
158
|
return self.Z
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
|
|
161
|
+
def to_pandas_smoothed(self, time_index: Optional[object] = None, series_ids: Optional[List[str]] = None):
|
|
162
|
+
"""Return smoothed data (original scale) as pandas DataFrame."""
|
|
161
163
|
try:
|
|
162
|
-
import
|
|
164
|
+
import pandas as pd
|
|
163
165
|
from ..utils.time import TimeIndex
|
|
164
166
|
|
|
165
|
-
|
|
167
|
+
# Get column names: use provided series_ids, fallback to stored IDs, or generate defaults
|
|
168
|
+
if series_ids is not None:
|
|
169
|
+
cols = series_ids
|
|
170
|
+
elif self.series_ids is not None:
|
|
171
|
+
cols = self.series_ids
|
|
172
|
+
else:
|
|
173
|
+
cols = [f"S{i+1}" for i in range(self.num_series())]
|
|
166
174
|
|
|
167
175
|
# Create DataFrame with series as columns
|
|
168
176
|
df_dict = {col: self.X_sm[:, i] for i, col in enumerate(cols)}
|
|
@@ -182,10 +190,10 @@ class BaseResult(ABC):
|
|
|
182
190
|
if time_list:
|
|
183
191
|
df_dict['time'] = time_list
|
|
184
192
|
|
|
185
|
-
return
|
|
193
|
+
return pd.DataFrame(df_dict)
|
|
186
194
|
except (ImportError, ValueError, TypeError):
|
|
187
195
|
return self.X_sm
|
|
188
|
-
|
|
196
|
+
|
|
189
197
|
|
|
190
198
|
def save(self, path: str) -> None:
|
|
191
199
|
"""Save result to a pickle file."""
|