dfm-python 0.6.11__tar.gz → 0.6.13__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.6.11 → dfm_python-0.6.13}/.gitignore +2 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/PKG-INFO +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/ddfm_exchange.yaml +5 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/ddfm_finance.yaml +5 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/ddfm_macro.yaml +5 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/dfm_exchange.yaml +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/dfm_finance.yaml +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/config/dfm_macro.yaml +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/pyproject.toml +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/__init__.py +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/schema/params.py +119 -35
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/schema/results.py +3 -3
- dfm_python-0.6.13/src/dfm_python/dataset/ddfm_dataset.py +228 -0
- dfm_python-0.6.13/src/dfm_python/functional/cauchy.py +236 -0
- dfm_python-0.6.13/src/dfm_python/functional/krylov.py +204 -0
- dfm_python-0.6.13/src/dfm_python/layer/mlp.py +272 -0
- dfm_python-0.6.13/src/dfm_python/layer/pca.py +304 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/logger/__init__.py +5 -2
- dfm_python-0.6.13/src/dfm_python/logger/ddfm_logger.py +23 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/logger/inference_logger.py +1 -8
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/logger/train_logger.py +1 -8
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/base.py +51 -1
- dfm_python-0.6.13/src/dfm_python/models/ddfm/__init__.py +12 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/ddfm/ddfm.py +313 -496
- dfm_python-0.6.13/src/dfm_python/models/ddfm/decoder.py +263 -0
- dfm_python-0.6.11/src/dfm_python/encoder/simple_autoencoder.py → dfm_python-0.6.13/src/dfm_python/models/ddfm/encoder.py +204 -43
- dfm_python-0.6.13/src/dfm_python/models/ddfm/sampling.py +442 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/dfm/initialization.py +1 -1
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/dfm/mixed_freq.py +24 -3
- dfm_python-0.6.13/src/dfm_python/models/ivdfm/decoder.py +0 -0
- dfm_python-0.6.13/src/dfm_python/models/ivdfm/encoder.py +0 -0
- dfm_python-0.6.13/src/dfm_python/models/ivdfm/ivdfm.py +708 -0
- dfm_python-0.6.13/src/dfm_python/numeric/builder.py +407 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/__init__.py +2 -2
- dfm_python-0.6.13/src/dfm_python/utils/loss.py +330 -0
- dfm_python-0.6.13/src/test/dataset/test_ddfm_dataset.py +171 -0
- dfm_python-0.6.13/src/test/dataset/test_dfm_dataset.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/models/test_ddfm.py +14 -15
- dfm_python-0.6.13/src/test/models/test_ddfm_sampling.py +261 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/models/test_dfm.py +24 -7
- dfm_python-0.6.13/src/test/numeric/test_builder.py +156 -0
- dfm_python-0.6.13/tutorial/tutorial_exchange_ddfm.py +167 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/tutorial/tutorial_exchange_dfm.py +1 -1
- dfm_python-0.6.13/tutorial/tutorial_finance_ddfm.py +186 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/tutorial/tutorial_finance_dfm.py +1 -1
- dfm_python-0.6.13/tutorial/tutorial_macro_ddfm.py +184 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/tutorial/tutorial_macro_dfm.py +1 -1
- dfm_python-0.6.11/.git +0 -1
- dfm_python-0.6.11/failure_reports/finance_failure_20260127_183841.json +0 -22
- dfm_python-0.6.11/failure_reports/macro_failure_20260127_183842.json +0 -15
- dfm_python-0.6.11/models/dfm_exchange.pkl +0 -0
- dfm_python-0.6.11/models/dfm_finance.pkl +0 -0
- dfm_python-0.6.11/models/dfm_macro.pkl +0 -0
- dfm_python-0.6.11/src/dfm_python/dataset/ddfm_dataset.py +0 -497
- dfm_python-0.6.11/src/dfm_python/decoder/__init__.py +0 -16
- dfm_python-0.6.11/src/dfm_python/decoder/base.py +0 -78
- dfm_python-0.6.11/src/dfm_python/decoder/linear.py +0 -72
- dfm_python-0.6.11/src/dfm_python/decoder/mlp.py +0 -128
- dfm_python-0.6.11/src/dfm_python/encoder/__init__.py +0 -38
- dfm_python-0.6.11/src/dfm_python/encoder/base.py +0 -111
- dfm_python-0.6.11/src/dfm_python/encoder/pca.py +0 -271
- dfm_python-0.6.11/src/dfm_python/numeric/builder.py +0 -200
- dfm_python-0.6.11/src/dfm_python/utils/loss.py +0 -100
- dfm_python-0.6.11/src/test/decoder/__init__.py +0 -2
- dfm_python-0.6.11/src/test/decoder/test_linear.py +0 -40
- dfm_python-0.6.11/src/test/decoder/test_mlp.py +0 -29
- dfm_python-0.6.11/src/test/encoder/__init__.py +0 -2
- dfm_python-0.6.11/src/test/encoder/test_base.py +0 -43
- dfm_python-0.6.11/src/test/encoder/test_pca.py +0 -100
- dfm_python-0.6.11/src/test/encoder/test_simple_autoencoder.py +0 -184
- dfm_python-0.6.11/src/test/functional/test_invariance.py +0 -348
- dfm_python-0.6.11/src/test/functional/test_macro_overflow.py +0 -240
- dfm_python-0.6.11/src/test/numeric/test_builder.py +0 -40
- dfm_python-0.6.11/tutorial/tutorial_exchange_ddfm.py +0 -329
- dfm_python-0.6.11/tutorial/tutorial_finance_ddfm.py +0 -283
- dfm_python-0.6.11/tutorial/tutorial_macro_ddfm.py +0 -312
- {dfm_python-0.6.11 → dfm_python-0.6.13}/.cursor/agents/test-runner.md +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/.python-version +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/=0.11.0 +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/MANIFEST.in +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/data/exchange_rate.csv +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/data/finance.csv +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/data/macro.csv +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/adapter.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/constants.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/schema/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/schema/block.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/schema/model.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/config/types.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/dataset/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/dataset/dfm_dataset.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/dataset/time.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/functional/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/functional/em.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/logger/dfm_logger.py +0 -0
- /dfm_python-0.6.11/src/test/dataset/test_ddfm_dataset.py → /dfm_python-0.6.13/src/dfm_python/logger/ivdfm_logger.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/logger/logger.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/dfm/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/dfm/dfm.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/models/dfm/tent.py +0 -0
- /dfm_python-0.6.11/src/test/dataset/test_dfm_dataset.py → /dfm_python-0.6.13/src/dfm_python/models/ivdfm/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/numeric/estimator.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/numeric/stability.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/numeric/statistic.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/numeric/validator.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/ssm/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/ssm/kalman.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/checkpoint.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/errors.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/helper.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/metric.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/misc.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/dfm_python/utils/validation.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/schema/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/schema/test_block.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/schema/test_model.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/schema/test_results.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/test_adapter.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/test_constants.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/test_tent_weights_config.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/config/test_types.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/conftest.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/functional/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/functional/test_em.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/functional/test_em_correctness.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/functional/test_em_properties.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/logger/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/logger/test_inference_logger.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/logger/test_logger.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/logger/test_train_logger.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/models/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/test_cov_rowvar_regression.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/test_estimator.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/test_initialization.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/test_stability.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/numeric/test_validator.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/ssm/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/ssm/test_kalman.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/ssm/test_kalman_correctness.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/utils/__init__.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/utils/test_errors.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/utils/test_helper.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/utils/test_metric.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/src/test/utils/test_misc.py +0 -0
- {dfm_python-0.6.11 → dfm_python-0.6.13}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.13
|
|
4
4
|
Summary: Dynamic Factor Model (DFM) estimation and forecasting in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
@@ -20,6 +20,11 @@ tolerance: 0.0005 # Convergence tolerance (matching original tolerance=0.0005)
|
|
|
20
20
|
disp: 10 # Display frequency for training progress (matching original disp=10)
|
|
21
21
|
seed: 3 # Random seed (matching original seed=3)
|
|
22
22
|
|
|
23
|
+
# Interpolation Parameters
|
|
24
|
+
interpolation_method: linear # Interpolation method for missing values
|
|
25
|
+
interpolation_limit: 10 # Maximum consecutive NaNs to interpolate
|
|
26
|
+
interpolation_limit_direction: both # Direction to fill ('forward', 'backward', or 'both')
|
|
27
|
+
|
|
23
28
|
# Data Handling (used by Dataset, not Model)
|
|
24
29
|
clock: d # Daily clock frequency (all series are daily)
|
|
25
30
|
scaler: null # No scaler - data should be pre-scaled before passing to Dataset
|
|
@@ -18,6 +18,11 @@ tolerance: 0.0005 # Convergence tolerance
|
|
|
18
18
|
disp: 10 # Display frequency for training progress
|
|
19
19
|
seed: null # Random seed (None uses default)
|
|
20
20
|
|
|
21
|
+
# Interpolation Parameters
|
|
22
|
+
interpolation_method: linear # Interpolation method for missing values
|
|
23
|
+
interpolation_limit: 10 # Maximum consecutive NaNs to interpolate
|
|
24
|
+
interpolation_limit_direction: both # Direction to fill ('forward', 'backward', or 'both')
|
|
25
|
+
|
|
21
26
|
# Data Handling (used by Dataset, not Model)
|
|
22
27
|
clock: m # Monthly clock frequency
|
|
23
28
|
scaler: standard # Scaler for target series: 'standard', 'robust', 'minmax', 'maxabs', 'quantile', or null
|
|
@@ -20,6 +20,11 @@ tolerance: 0.0005 # Convergence tolerance
|
|
|
20
20
|
disp: 10 # Display frequency for training progress
|
|
21
21
|
seed: null # Random seed (None uses default)
|
|
22
22
|
|
|
23
|
+
# Interpolation Parameters
|
|
24
|
+
interpolation_method: linear # Interpolation method for missing values
|
|
25
|
+
interpolation_limit: 10 # Maximum consecutive NaNs to interpolate
|
|
26
|
+
interpolation_limit_direction: both # Direction to fill ('forward', 'backward', or 'both')
|
|
27
|
+
|
|
23
28
|
# Data Handling (used by Dataset, not Model)
|
|
24
29
|
clock: w # Weekly clock frequency (base frequency for latent factors)
|
|
25
30
|
scaler: standard # Scaler for target series: 'standard', 'robust', 'minmax', 'maxabs', 'quantile', or null
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# NOTE: Exchange tutorial is long daily data (T~7.6k). Full EM can take hours.
|
|
7
7
|
# Keep tutorial runs short by capping iterations and loosening convergence.
|
|
8
8
|
threshold: 1.0e-04 # EM convergence threshold (tutorial-friendly)
|
|
9
|
-
max_iter:
|
|
9
|
+
max_iter: 10 # Maximum EM iterations (tutorial-friendly)
|
|
10
10
|
|
|
11
11
|
# Data Handling
|
|
12
12
|
clock: d # Daily clock frequency (base frequency for latent factors)
|
|
@@ -63,7 +63,7 @@ Note: DFMConfig uses frequency dict to specify series (column names -> frequenci
|
|
|
63
63
|
For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
__version__ = "0.
|
|
66
|
+
__version__ = "0.6.13"
|
|
67
67
|
|
|
68
68
|
# ============================================================================
|
|
69
69
|
# PUBLIC API DEFINITION
|
|
@@ -163,11 +163,11 @@ class DFMModelState:
|
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
@dataclass
|
|
166
|
-
class
|
|
167
|
-
"""
|
|
166
|
+
class DDFMModelState:
|
|
167
|
+
"""DDFM model state including training state and state-space parameters.
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
loss, factors,
|
|
169
|
+
Consolidates all DDFM model state for checkpointing: training state (convergence,
|
|
170
|
+
loss, factors, residuals) and fitted state-space parameters.
|
|
171
171
|
|
|
172
172
|
Attributes
|
|
173
173
|
----------
|
|
@@ -184,15 +184,84 @@ class DDFMTrainingState:
|
|
|
184
184
|
last_neurons : np.ndarray, optional
|
|
185
185
|
Last layer neurons for MLP decoder (n_mc_samples x T x num_neurons) or (T x num_neurons).
|
|
186
186
|
For linear decoder, this equals factors.
|
|
187
|
+
F : np.ndarray, optional
|
|
188
|
+
Transition matrix (m x m) - VAR(1) dynamics for factors (A in paper)
|
|
189
|
+
Q : np.ndarray, optional
|
|
190
|
+
Process noise covariance (m x m) - innovation covariance (W in original code)
|
|
191
|
+
mu_0 : np.ndarray, optional
|
|
192
|
+
Initial state mean (m,) - initial factor values
|
|
193
|
+
Sigma_0 : np.ndarray, optional
|
|
194
|
+
Initial state covariance (m x m) - initial uncertainty
|
|
195
|
+
H : np.ndarray, optional
|
|
196
|
+
Observation matrix (N x m) - decoder weights (measurement equation, theta in paper)
|
|
197
|
+
R : np.ndarray, optional
|
|
198
|
+
Observation noise covariance (N x N) - typically diagonal, small values
|
|
187
199
|
"""
|
|
200
|
+
# Training state
|
|
188
201
|
num_iter: int = 0
|
|
189
202
|
loss_now: Optional[float] = None
|
|
190
203
|
converged: bool = False
|
|
191
204
|
eps: Optional[np.ndarray] = None
|
|
192
205
|
factors: Optional[np.ndarray] = None
|
|
193
206
|
last_neurons: Optional[np.ndarray] = None
|
|
207
|
+
# State-space parameters (fitted during build_state_space)
|
|
208
|
+
F: Optional[np.ndarray] = None # Transition matrix (m x m) - A in paper
|
|
209
|
+
Q: Optional[np.ndarray] = None # Process noise covariance (m x m) - W in original code
|
|
210
|
+
mu_0: Optional[np.ndarray] = None # Initial state mean (m,)
|
|
211
|
+
Sigma_0: Optional[np.ndarray] = None # Initial state covariance (m x m)
|
|
212
|
+
H: Optional[np.ndarray] = None # Observation matrix (N x m) - theta in paper
|
|
213
|
+
R: Optional[np.ndarray] = None # Observation noise covariance (N x N)
|
|
194
214
|
|
|
195
|
-
|
|
215
|
+
@classmethod
|
|
216
|
+
def from_model(cls, model: Any) -> 'DDFMModelState':
|
|
217
|
+
"""Create DDFMModelState from DDFM model instance.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
model : DDFM
|
|
222
|
+
DDFM model instance
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
DDFMModelState
|
|
227
|
+
Model state dataclass
|
|
228
|
+
"""
|
|
229
|
+
# Get training state from model attributes
|
|
230
|
+
factors = getattr(model, 'factors', None)
|
|
231
|
+
eps = getattr(model, 'eps', None)
|
|
232
|
+
last_neurons = getattr(model, 'last_neurons', None)
|
|
233
|
+
num_iter = getattr(model, '_num_iter', 0)
|
|
234
|
+
loss_now = getattr(model, 'loss_now', None)
|
|
235
|
+
converged = getattr(model, '_converged', False)
|
|
236
|
+
|
|
237
|
+
# Get state-space parameters from training_state
|
|
238
|
+
F = Q = mu_0 = Sigma_0 = H = R = None
|
|
239
|
+
if hasattr(model, 'training_state') and model.training_state is not None:
|
|
240
|
+
ts = model.training_state
|
|
241
|
+
if hasattr(ts, 'F'):
|
|
242
|
+
F = ts.F
|
|
243
|
+
Q = ts.Q
|
|
244
|
+
mu_0 = ts.mu_0
|
|
245
|
+
Sigma_0 = ts.Sigma_0
|
|
246
|
+
H = ts.H
|
|
247
|
+
R = ts.R
|
|
248
|
+
|
|
249
|
+
return cls(
|
|
250
|
+
num_iter=num_iter,
|
|
251
|
+
loss_now=loss_now,
|
|
252
|
+
converged=converged,
|
|
253
|
+
eps=eps,
|
|
254
|
+
factors=factors,
|
|
255
|
+
last_neurons=last_neurons,
|
|
256
|
+
F=F,
|
|
257
|
+
Q=Q,
|
|
258
|
+
mu_0=mu_0,
|
|
259
|
+
Sigma_0=Sigma_0,
|
|
260
|
+
H=H,
|
|
261
|
+
R=R
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def sync_from_model(self, model: Any) -> 'DDFMModelState':
|
|
196
265
|
"""Update this dataclass from model instance attributes.
|
|
197
266
|
|
|
198
267
|
Parameters
|
|
@@ -202,7 +271,7 @@ class DDFMTrainingState:
|
|
|
202
271
|
|
|
203
272
|
Returns
|
|
204
273
|
-------
|
|
205
|
-
|
|
274
|
+
DDFMModelState
|
|
206
275
|
Self (for chaining)
|
|
207
276
|
"""
|
|
208
277
|
if hasattr(model, '_has_factors') and model._has_factors:
|
|
@@ -212,37 +281,52 @@ class DDFMTrainingState:
|
|
|
212
281
|
self.num_iter = getattr(model, '_num_iter', 0)
|
|
213
282
|
self.loss_now = getattr(model, 'loss_now', None)
|
|
214
283
|
self.converged = getattr(model, '_converged', False)
|
|
284
|
+
|
|
285
|
+
# Sync state-space parameters if available
|
|
286
|
+
if hasattr(model, 'training_state') and model.training_state is not None:
|
|
287
|
+
ts = model.training_state
|
|
288
|
+
if hasattr(ts, 'F'):
|
|
289
|
+
self.F = ts.F
|
|
290
|
+
self.Q = ts.Q
|
|
291
|
+
self.mu_0 = ts.mu_0
|
|
292
|
+
self.Sigma_0 = ts.Sigma_0
|
|
293
|
+
self.H = ts.H
|
|
294
|
+
self.R = ts.R
|
|
295
|
+
|
|
215
296
|
return self
|
|
216
|
-
|
|
217
|
-
@dataclass
|
|
218
|
-
class DDFMStateSpaceParams:
|
|
219
|
-
"""State-space model parameters for DDFM (fitted during model training).
|
|
220
|
-
|
|
221
|
-
These parameters are computed during DDFM.build_state_space() and represent
|
|
222
|
-
the fitted state-space model structure. Similar to DFMStateSpaceParams, this contains
|
|
223
|
-
the fitted state-space parameters but for DDFM. Naming follows original paper conventions.
|
|
224
297
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
298
|
+
def apply_to_model(self, model: Any) -> None:
|
|
299
|
+
"""Apply this state to DDFM model instance.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
model : DDFM
|
|
304
|
+
DDFM model instance to update
|
|
305
|
+
"""
|
|
306
|
+
# Apply training state
|
|
307
|
+
if self.factors is not None:
|
|
308
|
+
model.factors = self.factors
|
|
309
|
+
if self.eps is not None:
|
|
310
|
+
model.eps = self.eps
|
|
311
|
+
if self.last_neurons is not None:
|
|
312
|
+
model.last_neurons = self.last_neurons
|
|
313
|
+
model._num_iter = self.num_iter
|
|
314
|
+
model.loss_now = self.loss_now
|
|
315
|
+
model._converged = self.converged
|
|
316
|
+
|
|
317
|
+
# Apply state-space parameters if available
|
|
318
|
+
if self.F is not None and self.Q is not None and self.mu_0 is not None:
|
|
319
|
+
if hasattr(model, 'training_state'):
|
|
320
|
+
model.training_state.F = self.F
|
|
321
|
+
model.training_state.Q = self.Q
|
|
322
|
+
model.training_state.mu_0 = self.mu_0
|
|
323
|
+
model.training_state.Sigma_0 = self.Sigma_0
|
|
324
|
+
model.training_state.H = self.H
|
|
325
|
+
model.training_state.R = self.R
|
|
326
|
+
|
|
327
|
+
# Backward compatibility aliases
|
|
328
|
+
DDFMTrainingState = DDFMModelState
|
|
329
|
+
DDFMStateSpaceParams = DDFMModelState
|
|
246
330
|
|
|
247
331
|
# Backward compatibility alias
|
|
248
332
|
DFMStateSpaceParams = DFMModelState
|
|
@@ -13,7 +13,7 @@ from typing import Optional, List, Dict, Any, Tuple
|
|
|
13
13
|
from datetime import datetime
|
|
14
14
|
|
|
15
15
|
from .model import DFMConfig
|
|
16
|
-
from .params import
|
|
16
|
+
from .params import DDFMModelState
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
@@ -311,7 +311,7 @@ class DDFMResult(BaseResult):
|
|
|
311
311
|
Architecture of the encoder network used.
|
|
312
312
|
use_idiosyncratic : bool, optional
|
|
313
313
|
Whether idiosyncratic components were modeled.
|
|
314
|
-
fit_params :
|
|
314
|
+
fit_params : DDFMModelState, optional
|
|
315
315
|
State-space model parameters created during fit (F, Q, mu_0, sigma_0, H, R).
|
|
316
316
|
These are computed during build_state_space() and represent the fitted
|
|
317
317
|
state-space model structure.
|
|
@@ -365,7 +365,7 @@ class DDFMResult(BaseResult):
|
|
|
365
365
|
use_idiosyncratic: Optional[bool] = None # Whether idio components were used
|
|
366
366
|
|
|
367
367
|
# State-space parameters (fitted during training)
|
|
368
|
-
fit_params: Optional[
|
|
368
|
+
fit_params: Optional[DDFMModelState] = None
|
|
369
369
|
|
|
370
370
|
# Training hyperparameters
|
|
371
371
|
training_max_iter: Optional[int] = None
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""PyTorch Dataset for Deep Dynamic Factor Model (DDFM)."""
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from torch.utils.data import Dataset
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
try:
|
|
8
|
+
import polars as pl
|
|
9
|
+
_has_polars = True
|
|
10
|
+
PolarsDataFrame = pl.DataFrame
|
|
11
|
+
except ImportError:
|
|
12
|
+
pl = None
|
|
13
|
+
_has_polars = False
|
|
14
|
+
PolarsDataFrame = type(None) # Dummy type for type hints when polars not available
|
|
15
|
+
from typing import Tuple, List, Optional, Union
|
|
16
|
+
from sklearn.preprocessing import StandardScaler, RobustScaler, MinMaxScaler
|
|
17
|
+
|
|
18
|
+
from ..config.constants import DEFAULT_TORCH_DTYPE
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DDFMDataset(Dataset):
|
|
22
|
+
"""Dataset for DDFM training.
|
|
23
|
+
|
|
24
|
+
Scales target series if scaler provided.
|
|
25
|
+
|
|
26
|
+
All columns are treated as targets by default. When covariates are specified,
|
|
27
|
+
they are excluded from targets (targets = all_columns - covariates).
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
data : pd.DataFrame | PolarsDataFrame
|
|
32
|
+
Input data. Target series will be scaled if scaler provided.
|
|
33
|
+
time_idx : str
|
|
34
|
+
Time index column name.
|
|
35
|
+
covariates : List[str], optional
|
|
36
|
+
Series to exclude from targets (used for factor extraction but not forecasted).
|
|
37
|
+
If None, all series are targets (default).
|
|
38
|
+
scaler : StandardScaler | RobustScaler | MinMaxScaler, optional
|
|
39
|
+
Scaler instance to scale target series. If None, no scaling.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
data: Union[pd.DataFrame, PolarsDataFrame],
|
|
45
|
+
time_idx: str,
|
|
46
|
+
covariates: Optional[List[str]] = None,
|
|
47
|
+
scaler: Optional[Union[StandardScaler, RobustScaler, MinMaxScaler]] = None,
|
|
48
|
+
):
|
|
49
|
+
if _has_polars and isinstance(data, pl.DataFrame):
|
|
50
|
+
data = data.to_pandas()
|
|
51
|
+
|
|
52
|
+
data = data.copy()
|
|
53
|
+
data.sort_index(inplace=True)
|
|
54
|
+
|
|
55
|
+
self.time_idx = time_idx
|
|
56
|
+
self.time_index = pd.Index(data[time_idx]) if time_idx in data.columns else data.index
|
|
57
|
+
self.data_original = data.copy()
|
|
58
|
+
|
|
59
|
+
# Get variables (all columns excluding time_idx)
|
|
60
|
+
variables = [col for col in data.columns if col != time_idx] if time_idx in data.columns else list(data.columns)
|
|
61
|
+
|
|
62
|
+
# Filter covariates and compute targets
|
|
63
|
+
covariates = [c for c in (covariates or []) if c in variables]
|
|
64
|
+
self.covariates = covariates
|
|
65
|
+
self.target_series = [col for col in variables if col not in covariates]
|
|
66
|
+
|
|
67
|
+
# Split into covariates (X) and targets (y)
|
|
68
|
+
data_for_split = data.drop(columns=[time_idx]) if time_idx in data.columns else data
|
|
69
|
+
y = data_for_split[self.target_series]
|
|
70
|
+
X = data_for_split.drop(columns=self.target_series) if self.target_series else pd.DataFrame()
|
|
71
|
+
|
|
72
|
+
# Scale covariates separately (temporary scaler, not stored)
|
|
73
|
+
if scaler is not None and not X.empty:
|
|
74
|
+
X = pd.DataFrame(type(scaler)().fit_transform(X.values), index=X.index, columns=X.columns)
|
|
75
|
+
|
|
76
|
+
# Scale targets and store scaler for prediction
|
|
77
|
+
if scaler is not None:
|
|
78
|
+
y = pd.DataFrame(scaler.fit_transform(y.values), index=y.index, columns=y.columns)
|
|
79
|
+
|
|
80
|
+
self.scaler = scaler
|
|
81
|
+
|
|
82
|
+
# Store processed data
|
|
83
|
+
self.data = pd.concat([X, y], axis=1) if not X.empty else y
|
|
84
|
+
self.X = X.values if not X.empty else np.empty((len(y), 0))
|
|
85
|
+
self.y = y.values
|
|
86
|
+
self.missing_y = y.isna().values
|
|
87
|
+
self.observed_y = ~self.missing_y
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def target_shape(self) -> Tuple[int, int]:
|
|
91
|
+
"""Target shape (T, num_target_series)."""
|
|
92
|
+
return self.y.shape
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def data_shape(self) -> Tuple[int, int]:
|
|
96
|
+
"""Data shape (T, num_features + num_target_series)."""
|
|
97
|
+
return self.X.shape[0], self.X.shape[1] + self.y.shape[1]
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def all_columns_are_targets(self) -> bool:
|
|
101
|
+
"""Whether all columns are target series (no covariates)."""
|
|
102
|
+
return len(self.covariates) == 0
|
|
103
|
+
|
|
104
|
+
def split_features_and_targets(self, data: pd.DataFrame) -> Tuple[Optional[pd.DataFrame], pd.DataFrame]:
|
|
105
|
+
"""Split DataFrame into features (X) and targets (y)."""
|
|
106
|
+
if self.all_columns_are_targets:
|
|
107
|
+
return None, data
|
|
108
|
+
X = data.drop(columns=self.target_series)
|
|
109
|
+
y = data[self.target_series]
|
|
110
|
+
return X, y
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def target_indices(self) -> np.ndarray:
|
|
114
|
+
"""Target series column indices in processed data (self.data, excluding time_idx)."""
|
|
115
|
+
# target_series are column names in the processed data (self.data)
|
|
116
|
+
# self.data contains X + y (or just y), but not time_idx
|
|
117
|
+
return np.array([self.data.columns.get_loc(col) for col in self.target_series])
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def from_dataset(cls, new_data: Union[pd.DataFrame, PolarsDataFrame], dataset: 'DDFMDataset') -> 'DDFMDataset':
|
|
121
|
+
"""Create new dataset with new data, preserving configuration."""
|
|
122
|
+
return cls(
|
|
123
|
+
data=new_data,
|
|
124
|
+
time_idx=dataset.time_idx,
|
|
125
|
+
covariates=dataset.covariates,
|
|
126
|
+
scaler=dataset.scaler
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def create_autoencoder_dataset(
|
|
130
|
+
self,
|
|
131
|
+
X: Optional[torch.Tensor],
|
|
132
|
+
y_tmp: torch.Tensor,
|
|
133
|
+
y_actual: torch.Tensor,
|
|
134
|
+
eps_draw: torch.Tensor
|
|
135
|
+
) -> 'AutoencoderDataset':
|
|
136
|
+
"""Create a single AutoencoderDataset with corrupted targets."""
|
|
137
|
+
return AutoencoderDataset(X=X, y_corrupted=y_tmp - eps_draw, y_clean=y_actual)
|
|
138
|
+
|
|
139
|
+
def create_pretrain_dataset(
|
|
140
|
+
self,
|
|
141
|
+
data: pd.DataFrame,
|
|
142
|
+
device: Optional[torch.device] = None
|
|
143
|
+
) -> 'AutoencoderDataset':
|
|
144
|
+
"""Create AutoencoderDataset for pre-training (no corruption, clean data)."""
|
|
145
|
+
if device is None:
|
|
146
|
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
147
|
+
|
|
148
|
+
X_df, y_df = self.split_features_and_targets(data)
|
|
149
|
+
X = None if X_df is None else torch.from_numpy(X_df.values).to(dtype=DEFAULT_TORCH_DTYPE, device=device)
|
|
150
|
+
y = torch.from_numpy(y_df.values).to(dtype=DEFAULT_TORCH_DTYPE, device=device)
|
|
151
|
+
|
|
152
|
+
return AutoencoderDataset(X=X, y_corrupted=y, y_clean=y)
|
|
153
|
+
|
|
154
|
+
def create_autoencoder_datasets_list(
|
|
155
|
+
self,
|
|
156
|
+
n_mc_samples: int,
|
|
157
|
+
mu_eps: np.ndarray,
|
|
158
|
+
std_eps: np.ndarray,
|
|
159
|
+
X: Union[np.ndarray, pd.DataFrame],
|
|
160
|
+
y_tmp: Union[np.ndarray, pd.DataFrame],
|
|
161
|
+
y_actual: np.ndarray,
|
|
162
|
+
rng: np.random.RandomState,
|
|
163
|
+
device: Optional[torch.device] = None
|
|
164
|
+
) -> List['AutoencoderDataset']:
|
|
165
|
+
"""Create AutoencoderDataset instances with pre-sampled MC noise."""
|
|
166
|
+
if device is None:
|
|
167
|
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
168
|
+
|
|
169
|
+
X_array = X.values if isinstance(X, pd.DataFrame) else X
|
|
170
|
+
y_tmp_array = y_tmp.values if isinstance(y_tmp, pd.DataFrame) else y_tmp
|
|
171
|
+
T = y_tmp_array.shape[0]
|
|
172
|
+
|
|
173
|
+
# Pre-sample all MC noise at once
|
|
174
|
+
eps_draws = rng.multivariate_normal(mu_eps, np.diag(std_eps), (n_mc_samples, T))
|
|
175
|
+
|
|
176
|
+
# Convert to tensors once
|
|
177
|
+
X_tensor = torch.from_numpy(X_array).to(dtype=DEFAULT_TORCH_DTYPE, device=device) if X_array.size > 0 else None
|
|
178
|
+
y_tmp_tensor = torch.from_numpy(y_tmp_array).to(dtype=DEFAULT_TORCH_DTYPE, device=device)
|
|
179
|
+
y_actual_tensor = torch.from_numpy(y_actual).to(dtype=DEFAULT_TORCH_DTYPE, device=device)
|
|
180
|
+
eps_draws_tensor = torch.from_numpy(eps_draws).to(dtype=DEFAULT_TORCH_DTYPE, device=device)
|
|
181
|
+
|
|
182
|
+
# Create datasets
|
|
183
|
+
return [
|
|
184
|
+
self.create_autoencoder_dataset(X_tensor, y_tmp_tensor, y_actual_tensor, eps_draws_tensor[i])
|
|
185
|
+
for i in range(n_mc_samples)
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class AutoencoderDataset:
|
|
190
|
+
"""Container for autoencoder training data with corrupted inputs and clean targets.
|
|
191
|
+
|
|
192
|
+
Stores pre-loaded tensors for efficient direct slicing. All tensors are expected
|
|
193
|
+
to be on the correct device. Use direct tensor slicing (e.g., `dataset.full_input[i:j]`)
|
|
194
|
+
rather than indexing.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
X : torch.Tensor, optional
|
|
199
|
+
Features (T, N_features) - lags, dummies, etc. Not corrupted.
|
|
200
|
+
y_corrupted : torch.Tensor
|
|
201
|
+
Corrupted targets (T, num_target_series).
|
|
202
|
+
y_clean : torch.Tensor
|
|
203
|
+
Clean targets (T, num_target_series) for reconstruction.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
X: Optional[torch.Tensor],
|
|
209
|
+
y_corrupted: torch.Tensor,
|
|
210
|
+
y_clean: torch.Tensor
|
|
211
|
+
):
|
|
212
|
+
self.X = X
|
|
213
|
+
self.y_corrupted = y_corrupted
|
|
214
|
+
self.y_clean = y_clean
|
|
215
|
+
# Pre-compute full_input once (optimization: avoid torch.cat on every access)
|
|
216
|
+
if self.X is not None:
|
|
217
|
+
self._full_input = torch.cat([self.X, self.y_corrupted], dim=1)
|
|
218
|
+
else:
|
|
219
|
+
self._full_input = self.y_corrupted
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def full_input(self) -> torch.Tensor:
|
|
223
|
+
"""Full autoencoder input: clean X features + corrupted y targets."""
|
|
224
|
+
return self._full_input
|
|
225
|
+
|
|
226
|
+
def __len__(self) -> int:
|
|
227
|
+
"""Return number of time steps."""
|
|
228
|
+
return self.y_corrupted.shape[0]
|