dfm-python 0.5.3__py3-none-any.whl → 0.5.5__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 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.5.2"
68
+ __version__ = "0.5.5"
69
69
 
70
70
  # ============================================================================
71
71
  # PUBLIC API DEFINITION
@@ -85,8 +85,8 @@ __version__ = "0.5.2"
85
85
  # Configuration (from config/ subpackage)
86
86
  from .config import (
87
87
  DEFAULT_BLOCK_NAME,
88
- ConfigSource, YamlSource, DictSource, HydraSource,
89
- MergedConfigSource, make_config_source,
88
+ ConfigSource, YamlSource, HydraSource,
89
+ make_config_source,
90
90
  )
91
91
  # Internal imports (for backward compatibility, but not recommended)
92
92
  from .config import DFMConfig, SeriesConfig # Internal use only
@@ -102,7 +102,9 @@ from .utils.time import calculate_rmse
102
102
  # Users can import these directly from dfm_python
103
103
  from .lightning import (
104
104
  DFMDataModule,
105
+ DDFMDataModule, # DDFM-specific DataModule
105
106
  DFMDataset, # Dataset class (usually not needed directly)
107
+ DDFMDataset, # DDFM Dataset class (usually not needed directly)
106
108
  KalmanFilter, # Module class
107
109
  EMAlgorithm, # Module class
108
110
  )
@@ -122,8 +124,8 @@ __all__ = [
122
124
  # Constants
123
125
  'DEFAULT_BLOCK_NAME',
124
126
  # Config sources
125
- 'ConfigSource', 'YamlSource', 'DictSource', 'HydraSource',
126
- 'MergedConfigSource', 'make_config_source',
127
+ 'ConfigSource', 'YamlSource', 'HydraSource',
128
+ 'make_config_source',
127
129
  # Low-level API (functional interface - advanced usage)
128
130
  'BaseResult', 'DFMResult', 'DDFMResult', 'calculate_rmse', 'diagnose_series', 'print_series_diagnosis',
129
131
  ]
@@ -137,7 +139,9 @@ __all__.extend([
137
139
  # Lightning modules (mandatory dependency)
138
140
  __all__.extend([
139
141
  'DFMDataModule',
142
+ 'DDFMDataModule', # DDFM-specific DataModule
140
143
  'DFMDataset', # Dataset class (usually not needed directly)
144
+ 'DDFMDataset', # DDFM Dataset class (usually not needed directly)
141
145
  'KalmanFilter', # Module class
142
146
  'EMAlgorithm', # Module class
143
147
  ])
@@ -1,20 +1,20 @@
1
1
  """Configuration schema for DFM.
2
2
 
3
3
  This module provides the core configuration dataclasses:
4
- - BaseModelConfig: Base class with shared model structure
5
- - DFMConfig(BaseModelConfig): Linear DFM with EM algorithm parameters
6
- - DDFMConfig(BaseModelConfig): Deep DFM with neural network training parameters
4
+ - BaseModelConfig: Base class with shared model structure (series, clock, data handling)
5
+ - DFMConfig(BaseModelConfig): Linear DFM with EM algorithm parameters and block structure
6
+ - DDFMConfig(BaseModelConfig): Deep DFM with neural network training parameters (no blocks)
7
7
  - SeriesConfig: Component configurations
8
8
 
9
9
  Note: Parameter classes (Params, FitParams) are in config/params.py
10
10
  Note: Validation functions are in config/utils.py
11
11
 
12
12
  The configuration hierarchy:
13
- - BaseModelConfig: Model structure (series, blocks, factors, clock, data handling)
14
- - DFMConfig: Adds EM algorithm parameters (max_iter, threshold, regularization)
15
- - DDFMConfig: Adds neural network parameters (epochs, learning_rate, encoder_layers)
13
+ - BaseModelConfig: Model structure (series, clock, data handling) - NO blocks
14
+ - DFMConfig: Adds blocks structure and EM algorithm parameters (max_iter, threshold, regularization)
15
+ - DDFMConfig: Adds neural network parameters (epochs, learning_rate, encoder_layers) - NO blocks
16
16
 
17
- Blocks are defined as Dict[str, Dict[str, Any]] where each block is a dict with:
17
+ Blocks are DFM-specific and defined as Dict[str, Dict[str, Any]] where each block is a dict with:
18
18
  - factors: int (number of factors)
19
19
  - ar_lag: int (AR lag order)
20
20
  - clock: str (block clock frequency)
@@ -282,19 +282,18 @@ class BaseModelConfig:
282
282
  This base class contains the model structure that is common to both
283
283
  DFM (linear) and DDFM (deep) models:
284
284
  - Series definitions
285
- - Block structure
286
285
  - Clock frequency
287
286
  - Data preprocessing (missing data handling)
288
287
 
288
+ Note: Blocks are DFM-specific and are NOT included in BaseModelConfig.
289
+ DFMConfig adds block structure, while DDFMConfig does not use blocks.
290
+
289
291
  Subclasses (DFMConfig, DDFMConfig) add model-specific training parameters.
290
292
  """
291
293
  # ========================================================================
292
294
  # Model Structure (WHAT - defines the model)
293
295
  # ========================================================================
294
296
  series: List[SeriesConfig] # Series specifications
295
- blocks: Dict[str, Dict[str, Any]] # Block configurations (block_name -> {factors, ar_lag, clock, notes})
296
- block_names: List[str] = field(init=False) # Block names in order (derived from blocks dict)
297
- factors_per_block: List[int] = field(init=False) # Number of factors per block (derived from blocks)
298
297
 
299
298
  # ========================================================================
300
299
  # Shared Data Handling Parameters
@@ -304,15 +303,155 @@ class BaseModelConfig:
304
303
  clock: str = 'm' # Base frequency for nowcasting (global clock): 'd', 'w', 'm', 'q', 'sa', 'a' (defaults to 'm' for monthly)
305
304
  scaler: Optional[str] = 'standard' # Unified scaler type for all series: 'standard', 'robust', 'minmax', 'maxabs', 'quantile', or None (no scaling). Default: 'standard' for unified scaling.
306
305
 
306
+ def __post_init__(self):
307
+ """Validate basic model structure.
308
+
309
+ This method performs basic validation of the model configuration:
310
+ - Ensures at least one series is specified
311
+ - Validates clock frequency
312
+ - Auto-generates series_id if not provided
313
+
314
+ Raises
315
+ ------
316
+ ValueError
317
+ If any validation check fails, with a descriptive error message
318
+ indicating what needs to be fixed.
319
+ """
320
+ # Import frequency hierarchy for validation
321
+ from .utils import FREQUENCY_HIERARCHY
322
+
323
+ if not self.series:
324
+ raise ValueError(
325
+ "Model configuration must contain at least one series. "
326
+ "Please add series definitions to your configuration."
327
+ )
328
+
329
+ # Validate global clock
330
+ self.clock = validate_frequency(self.clock)
331
+
332
+ # Auto-generate series_id if not provided
333
+ for i, s in enumerate(self.series):
334
+ if s.series_id is None:
335
+ s.series_id = f"series_{i}"
336
+ if s.series_name is None:
337
+ s.series_name = s.series_id
338
+
307
339
  # ========================================================================
308
- # Internal cache (not user-configurable)
340
+ # Helper Methods (snake_case - recommended)
309
341
  # ========================================================================
310
- _cached_blocks: Optional[np.ndarray] = field(default=None, init=False, repr=False)
342
+
343
+ def get_series_ids(self) -> List[str]:
344
+ """Get list of series IDs (snake_case - recommended)."""
345
+ return [s.series_id if s.series_id is not None else f"series_{i}"
346
+ for i, s in enumerate(self.series)]
347
+
348
+ def get_series_names(self) -> List[str]:
349
+ """Get list of series names (snake_case - recommended)."""
350
+ return [s.series_name if s.series_name is not None else (s.series_id or f"series_{i}")
351
+ for i, s in enumerate(self.series)]
352
+
353
+ def get_frequencies(self) -> List[str]:
354
+ """Get list of frequencies (snake_case - recommended)."""
355
+ return [s.frequency for s in self.series]
356
+
357
+ def validate_and_report(self) -> Dict[str, Any]:
358
+ """Validate configuration and return structured report with issues and suggestions.
359
+
360
+ This method performs validation checks without raising exceptions, returning
361
+ a structured report that can be used for debugging and user guidance.
362
+
363
+ Returns
364
+ -------
365
+ Dict[str, Any]
366
+ Report dictionary with keys:
367
+ - 'valid': bool - Whether configuration is valid
368
+ - 'errors': List[str] - List of error messages
369
+ - 'warnings': List[str] - List of warning messages
370
+ - 'suggestions': List[str] - List of actionable suggestions
371
+ """
372
+ from .utils import FREQUENCY_HIERARCHY
373
+
374
+ report = {
375
+ 'valid': True,
376
+ 'errors': [],
377
+ 'warnings': [],
378
+ 'suggestions': []
379
+ }
380
+
381
+ # Check for empty series
382
+ if not self.series:
383
+ report['valid'] = False
384
+ report['errors'].append("Model configuration must contain at least one series.")
385
+ report['suggestions'].append("Add series definitions to your configuration.")
386
+ return report
387
+
388
+ return report
389
+
390
+
391
+ @dataclass
392
+ class DFMConfig(BaseModelConfig):
393
+ """Linear DFM configuration - EM algorithm parameters and block structure.
394
+
395
+ This configuration class extends BaseModelConfig with parameters specific
396
+ to linear Dynamic Factor Models trained using the Expectation-Maximization
397
+ (EM) algorithm. DFM uses block structure to organize factors (global + sector-specific).
398
+
399
+ The configuration can be built from:
400
+ - Main settings (estimation parameters) from config/default.yaml
401
+ - Series definitions from config/series/default.yaml or CSV
402
+ - Block definitions from config/blocks/default.yaml
403
+ """
404
+ # ========================================================================
405
+ # Block Structure (DFM-specific)
406
+ # ========================================================================
407
+ blocks: Dict[str, Dict[str, Any]] = field(default_factory=dict) # Block configurations (block_name -> {factors, ar_lag, clock, notes})
408
+ block_names: List[str] = field(init=False) # Block names in order (derived from blocks dict)
409
+ factors_per_block: List[int] = field(init=False) # Number of factors per block (derived from blocks)
410
+ _cached_blocks: Optional[np.ndarray] = field(default=None, init=False, repr=False) # Internal cache
411
+
412
+ # ========================================================================
413
+ # EM Algorithm Parameters (HOW - controls the algorithm)
414
+ # ========================================================================
415
+ ar_lag: int = 1 # Number of lags in AR transition equation (lookback window). Must be 1 or 2 (maximum supported order is VAR(2))
416
+ threshold: float = 1e-5 # EM convergence threshold
417
+ max_iter: int = 5000 # Maximum EM iterations
418
+
419
+ # ========================================================================
420
+ # Numerical Stability Parameters (transparent and configurable)
421
+ # ========================================================================
422
+ # AR Coefficient Clipping
423
+ clip_ar_coefficients: bool = True # Enable AR coefficient clipping for stationarity
424
+ ar_clip_min: float = -0.99 # Minimum AR coefficient (must be > -1 for stationarity)
425
+ ar_clip_max: float = 0.99 # Maximum AR coefficient (must be < 1 for stationarity)
426
+ warn_on_ar_clip: bool = True # Warn when AR coefficients are clipped (indicates near-unit root)
427
+
428
+ # Data Value Clipping
429
+ clip_data_values: bool = True # Enable clipping of extreme data values
430
+ data_clip_threshold: float = 100.0 # Clip values beyond this many standard deviations
431
+ warn_on_data_clip: bool = True # Warn when data values are clipped (indicates outliers)
432
+
433
+ # Regularization
434
+ use_regularization: bool = True # Enable regularization for numerical stability
435
+ regularization_scale: float = 1e-5 # Scale factor for ridge regularization (relative to trace, default 1e-5)
436
+ min_eigenvalue: float = 1e-8 # Minimum eigenvalue for positive definite matrices
437
+ max_eigenvalue: float = 1e6 # Maximum eigenvalue cap to prevent explosion
438
+ warn_on_regularization: bool = True # Warn when regularization is applied
439
+
440
+ # Damped Updates
441
+ use_damped_updates: bool = True # Enable damped updates when likelihood decreases
442
+ damping_factor: float = 0.8 # Damping factor (0.8 = 80% new, 20% old)
443
+ warn_on_damped_update: bool = True # Warn when damped updates are used
444
+
445
+ # Idiosyncratic Component Augmentation
446
+ augment_idio: bool = True # Enable state augmentation with idiosyncratic components (default: True)
447
+ augment_idio_slow: bool = True # Enable tent-length chains for slower-frequency series (default: True)
448
+ idio_rho0: float = 0.1 # Initial AR coefficient for idiosyncratic components (default: 0.1)
449
+ idio_min_var: float = 1e-8 # Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
311
450
 
312
451
  def __post_init__(self):
313
- """Validate blocks structure and consistency.
452
+ """Validate blocks structure and consistency for DFM.
314
453
 
315
- This method performs comprehensive validation of the model configuration:
454
+ This method performs comprehensive validation of the DFM configuration:
316
455
  - Derives block_names and factors_per_block from blocks dict
317
456
  - Ensures at least one series is specified
318
457
  - Validates block structure consistency across all series
@@ -327,24 +466,20 @@ class BaseModelConfig:
327
466
  If any validation check fails, with a descriptive error message
328
467
  indicating what needs to be fixed.
329
468
  """
469
+ # Call parent __post_init__ first (validates series and clock)
470
+ super().__post_init__()
471
+
330
472
  # Import frequency hierarchy for validation
331
473
  from .utils import FREQUENCY_HIERARCHY
332
474
 
333
- if not self.series:
334
- raise ValueError(
335
- "Model configuration must contain at least one series. "
336
- "Please add series definitions to your configuration."
337
- )
338
-
339
475
  if not self.blocks:
340
476
  raise ValueError(
341
- "Model configuration must contain at least one block. "
477
+ "DFM configuration must contain at least one block. "
342
478
  "Please add block definitions to your configuration."
343
479
  )
344
480
 
345
481
  # Derive block_names and factors_per_block from blocks dict
346
482
  # Ensure global block (first block) is present
347
- # Find the global block (first block in order or named 'Block_Global')
348
483
  block_names_list = list(self.blocks.keys())
349
484
  global_block_name = None
350
485
 
@@ -359,7 +494,7 @@ class BaseModelConfig:
359
494
 
360
495
  if global_block_name is None:
361
496
  raise ValueError(
362
- "Model configuration must include at least one block. "
497
+ "DFM configuration must include at least one block. "
363
498
  "The first block serves as the global/common factor that all series load on."
364
499
  )
365
500
 
@@ -370,7 +505,6 @@ class BaseModelConfig:
370
505
  [self.blocks[name].get('factors', 1) for name in self.block_names])
371
506
 
372
507
  # Validate global clock
373
- self.clock = validate_frequency(self.clock)
374
508
  global_clock_hierarchy = FREQUENCY_HIERARCHY.get(self.clock, 3)
375
509
 
376
510
  # Validate block clocks (must be >= global clock)
@@ -407,11 +541,6 @@ class BaseModelConfig:
407
541
  # Auto-generate series_id if not provided and convert blocks to indices
408
542
  n_blocks = len(self.block_names)
409
543
  for i, s in enumerate(self.series):
410
- if s.series_id is None:
411
- s.series_id = f"series_{i}"
412
- if s.series_name is None:
413
- s.series_name = s.series_id
414
-
415
544
  # Convert block names to indices if needed
416
545
  if isinstance(s.blocks, list) and len(s.blocks) > 0:
417
546
  if isinstance(s.blocks[0], str):
@@ -477,24 +606,6 @@ class BaseModelConfig:
477
606
  f"Each block must have at least one factor."
478
607
  )
479
608
 
480
- # ========================================================================
481
- # Helper Methods (snake_case - recommended)
482
- # ========================================================================
483
-
484
- def get_series_ids(self) -> List[str]:
485
- """Get list of series IDs (snake_case - recommended)."""
486
- return [s.series_id if s.series_id is not None else f"series_{i}"
487
- for i, s in enumerate(self.series)]
488
-
489
- def get_series_names(self) -> List[str]:
490
- """Get list of series names (snake_case - recommended)."""
491
- return [s.series_name if s.series_name is not None else (s.series_id or f"series_{i}")
492
- for i, s in enumerate(self.series)]
493
-
494
- def get_frequencies(self) -> List[str]:
495
- """Get list of frequencies (snake_case - recommended)."""
496
- return [s.frequency for s in self.series]
497
-
498
609
  def get_blocks_array(self) -> np.ndarray:
499
610
  """Get blocks as numpy array (snake_case - recommended, cached)."""
500
611
  if self._cached_blocks is None:
@@ -502,153 +613,6 @@ class BaseModelConfig:
502
613
  self._cached_blocks = np.array(blocks_list, dtype=int)
503
614
  return self._cached_blocks
504
615
 
505
- def validate_and_report(self) -> Dict[str, Any]:
506
- """Validate configuration and return structured report with issues and suggestions.
507
-
508
- This method performs validation checks without raising exceptions, returning
509
- a structured report that can be used for debugging and user guidance.
510
-
511
- Returns
512
- -------
513
- Dict[str, Any]
514
- Report dictionary with keys:
515
- - 'valid': bool - Whether configuration is valid
516
- - 'errors': List[str] - List of error messages
517
- - 'warnings': List[str] - List of warning messages
518
- - 'suggestions': List[str] - List of actionable suggestions
519
- """
520
- from .utils import FREQUENCY_HIERARCHY
521
-
522
- report = {
523
- 'valid': True,
524
- 'errors': [],
525
- 'warnings': [],
526
- 'suggestions': []
527
- }
528
-
529
- # Check for empty series
530
- if not self.series:
531
- report['valid'] = False
532
- report['errors'].append("Model configuration must contain at least one series.")
533
- report['suggestions'].append("Add series definitions to your configuration.")
534
- return report
535
-
536
- # Check for empty blocks
537
- if not self.blocks:
538
- report['valid'] = False
539
- report['errors'].append("Model configuration must contain at least one block.")
540
- report['suggestions'].append("Add block definitions to your configuration.")
541
- return report
542
-
543
- # Check frequency constraints
544
- global_clock_hierarchy = FREQUENCY_HIERARCHY.get(self.clock, 3)
545
- for i, s in enumerate(self.series):
546
- series_freq_hierarchy = FREQUENCY_HIERARCHY.get(s.frequency, 3)
547
-
548
- for block_idx, loads_on_block in enumerate(s.blocks):
549
- if loads_on_block == 1:
550
- if block_idx < len(self.block_names):
551
- block_name = self.block_names[block_idx]
552
- block_cfg = self.blocks[block_name]
553
- block_clock = block_cfg.get('clock', self.clock)
554
- block_clock_hierarchy = FREQUENCY_HIERARCHY.get(block_clock, 3)
555
-
556
- if series_freq_hierarchy < block_clock_hierarchy:
557
- valid_freqs = [freq for freq, hier in FREQUENCY_HIERARCHY.items()
558
- if hier >= block_clock_hierarchy]
559
- valid_freqs_str = ', '.join(sorted(valid_freqs))
560
- report['valid'] = False
561
- report['errors'].append(
562
- f"Series '{s.series_id}' has frequency '{s.frequency}' which is faster than "
563
- f"block '{block_name}' clock '{block_clock}'."
564
- )
565
- report['suggestions'].append(
566
- f"For series '{s.series_id}': change frequency to one of [{valid_freqs_str}], "
567
- f"or set block '{block_name}' clock to '{s.frequency}' or faster."
568
- )
569
-
570
- # Check block clock constraints
571
- for block_name, block_cfg in self.blocks.items():
572
- block_clock = block_cfg.get('clock', self.clock)
573
- block_clock_hierarchy = FREQUENCY_HIERARCHY.get(block_clock, 3)
574
- if block_clock_hierarchy < global_clock_hierarchy:
575
- report['valid'] = False
576
- report['errors'].append(
577
- f"Block '{block_name}' has clock '{block_clock}' which is faster than "
578
- f"global clock '{self.clock}'."
579
- )
580
- report['suggestions'].append(
581
- f"Change block '{block_name}' clock to '{self.clock}' or slower, "
582
- f"or set global clock to '{block_clock}' or faster."
583
- )
584
-
585
- # Check factors_per_block
586
- if any(f < 1 for f in self.factors_per_block):
587
- invalid_blocks = [i for i, f in enumerate(self.factors_per_block) if f < 1]
588
- report['valid'] = False
589
- report['errors'].append(
590
- f"factors_per_block must contain positive integers (>= 1). "
591
- f"Invalid values found at block indices {invalid_blocks}."
592
- )
593
- report['suggestions'].append(
594
- f"Set factors_per_block[{invalid_blocks[0]}] to at least 1 for block '{self.block_names[invalid_blocks[0]]}'."
595
- )
596
-
597
- return report
598
-
599
-
600
- @dataclass
601
- class DFMConfig(BaseModelConfig):
602
- """Linear DFM configuration - EM algorithm parameters.
603
-
604
- This configuration class extends BaseModelConfig with parameters specific
605
- to linear Dynamic Factor Models trained using the Expectation-Maximization
606
- (EM) algorithm.
607
-
608
- The configuration can be built from:
609
- - Main settings (estimation parameters) from config/default.yaml
610
- - Series definitions from config/series/default.yaml or CSV
611
- - Block definitions from config/blocks/default.yaml
612
- """
613
- # ========================================================================
614
- # EM Algorithm Parameters (HOW - controls the algorithm)
615
- # ========================================================================
616
- ar_lag: int = 1 # Number of lags in AR transition equation (lookback window). Must be 1 or 2 (maximum supported order is VAR(2))
617
- threshold: float = 1e-5 # EM convergence threshold
618
- max_iter: int = 5000 # Maximum EM iterations
619
-
620
- # ========================================================================
621
- # Numerical Stability Parameters (transparent and configurable)
622
- # ========================================================================
623
- # AR Coefficient Clipping
624
- clip_ar_coefficients: bool = True # Enable AR coefficient clipping for stationarity
625
- ar_clip_min: float = -0.99 # Minimum AR coefficient (must be > -1 for stationarity)
626
- ar_clip_max: float = 0.99 # Maximum AR coefficient (must be < 1 for stationarity)
627
- warn_on_ar_clip: bool = True # Warn when AR coefficients are clipped (indicates near-unit root)
628
-
629
- # Data Value Clipping
630
- clip_data_values: bool = True # Enable clipping of extreme data values
631
- data_clip_threshold: float = 100.0 # Clip values beyond this many standard deviations
632
- warn_on_data_clip: bool = True # Warn when data values are clipped (indicates outliers)
633
-
634
- # Regularization
635
- use_regularization: bool = True # Enable regularization for numerical stability
636
- regularization_scale: float = 1e-5 # Scale factor for ridge regularization (relative to trace, default 1e-5)
637
- min_eigenvalue: float = 1e-8 # Minimum eigenvalue for positive definite matrices
638
- max_eigenvalue: float = 1e6 # Maximum eigenvalue cap to prevent explosion
639
- warn_on_regularization: bool = True # Warn when regularization is applied
640
-
641
- # Damped Updates
642
- use_damped_updates: bool = True # Enable damped updates when likelihood decreases
643
- damping_factor: float = 0.8 # Damping factor (0.8 = 80% new, 20% old)
644
- warn_on_damped_update: bool = True # Warn when damped updates are used
645
-
646
- # Idiosyncratic Component Augmentation
647
- augment_idio: bool = True # Enable state augmentation with idiosyncratic components (default: True)
648
- augment_idio_slow: bool = True # Enable tent-length chains for slower-frequency series (default: True)
649
- idio_rho0: float = 0.1 # Initial AR coefficient for idiosyncratic components (default: 0.1)
650
- idio_min_var: float = 1e-8 # Minimum variance for idiosyncratic innovation covariance (default: 1e-8)
651
-
652
616
  # ========================================================================
653
617
  # Factory Methods
654
618
  # ========================================================================
@@ -703,14 +667,12 @@ class DDFMConfig(BaseModelConfig):
703
667
  This configuration class extends BaseModelConfig with parameters specific
704
668
  to Deep Dynamic Factor Models trained using neural networks (autoencoders).
705
669
 
706
- Note: DDFM does not use block structure. Blocks are only required because
707
- DDFMConfig inherits from BaseModelConfig. The blocks field will be ignored
708
- by DDFM - use num_factors directly to specify the number of factors.
670
+ Note: DDFM does NOT use block structure. Use num_factors directly to specify
671
+ the number of factors. Blocks are DFM-specific and not needed for DDFM.
709
672
 
710
673
  The configuration can be built from:
711
674
  - Main settings (training parameters) from config/default.yaml
712
675
  - Series definitions from config/series/default.yaml or CSV
713
- - Block definitions are not used by DDFM (only for DFM compatibility)
714
676
  """
715
677
  # ========================================================================
716
678
  # Neural Network Training Parameters
@@ -863,13 +825,9 @@ class DDFMConfig(BaseModelConfig):
863
825
  config_type = _detect_config_type(data)
864
826
 
865
827
  if config_type == 'ddfm':
866
- # DDFM does not use block structure - create minimal default block if needed
867
- # This is required by BaseModelConfig but will be ignored by DDFM
868
- if not blocks_dict_final:
869
- blocks_dict_final = {DEFAULT_BLOCK_NAME: {'factors': 1, 'ar_lag': 1, 'clock': 'm'}}
828
+ # DDFM does not use block structure - no blocks needed
870
829
  return DDFMConfig(
871
830
  series=series_list,
872
- blocks=blocks_dict_final,
873
831
  **DDFMConfig._extract_ddfm(data)
874
832
  )
875
833
  else:
@@ -913,9 +871,9 @@ class DDFMConfig(BaseModelConfig):
913
871
  config_type = _detect_config_type(data)
914
872
 
915
873
  if config_type == 'ddfm':
874
+ # DDFM does not use block structure - no blocks needed
916
875
  return DDFMConfig(
917
876
  series=series_list,
918
- blocks=blocks_dict,
919
877
  **DDFMConfig._extract_ddfm(data)
920
878
  )
921
879
  else:
@@ -984,7 +942,7 @@ def _dfm_from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig'
984
942
  raise ValueError(f"blocks must be a dict, got {type(blocks_data)}")
985
943
  else:
986
944
  # Infer blocks from series using helper
987
- blocks_dict = _infer_blocks_from_series(series_list, data)
945
+ blocks_dict = _infer_blocks(series_list, data)
988
946
 
989
947
  # Determine config type using helper function
990
948
  config_type = _detect_config_type(data)
@@ -1047,19 +1005,15 @@ def _ddfm_from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig
1047
1005
  raise ValueError(f"blocks must be a dict, got {type(blocks_data)}")
1048
1006
  else:
1049
1007
  # Infer blocks from series using helper
1050
- blocks_dict = _infer_blocks_from_series(series_list, data)
1008
+ blocks_dict = _infer_blocks(series_list, data)
1051
1009
 
1052
1010
  # Determine config type using helper function
1053
1011
  config_type = _detect_config_type(data)
1054
1012
 
1055
1013
  if config_type == 'ddfm':
1056
- # DDFM does not use block structure - create minimal default block if needed
1057
- # This is required by BaseModelConfig but will be ignored by DDFM
1058
- if not blocks_dict:
1059
- blocks_dict = {DEFAULT_BLOCK_NAME: {'factors': 1, 'ar_lag': 1, 'clock': 'm'}}
1014
+ # DDFM does not use block structure - no blocks needed
1060
1015
  return DDFMConfig(
1061
1016
  series=series_list,
1062
- blocks=blocks_dict,
1063
1017
  **DDFMConfig._extract_ddfm(data)
1064
1018
  )
1065
1019
  else:
@@ -1073,12 +1027,9 @@ def _ddfm_from_dict(cls, data: Dict[str, Any]) -> Union['DFMConfig', 'DDFMConfig
1073
1027
  config_type = _detect_config_type(data)
1074
1028
 
1075
1029
  if config_type == 'ddfm':
1076
- # DDFM does not use block structure - create minimal default block if needed
1077
- # This is required by BaseModelConfig but will be ignored by DDFM
1078
- if 'blocks' not in data or not data.get('blocks'):
1079
- data = data.copy()
1080
- data['blocks'] = {DEFAULT_BLOCK_NAME: {'factors': 1, 'ar_lag': 1, 'clock': 'm'}}
1081
- return DDFMConfig(**data)
1030
+ # DDFM does not use block structure - remove blocks from data if present
1031
+ data_clean = {k: v for k, v in data.items() if k != 'blocks'}
1032
+ return DDFMConfig(**data_clean)
1082
1033
  else:
1083
1034
  return DFMConfig(**data)
1084
1035
 
dfm_python/data/utils.py CHANGED
@@ -239,12 +239,11 @@ def load_data(
239
239
  mask = Time >= sample_start
240
240
  if isinstance(mask, pd.Series):
241
241
  mask = mask.values
242
- elif not isinstance(mask, np.ndarray):
243
- # Convert to numpy array if needed
244
- mask = np.asarray(mask)
245
- Z = Z[mask]
246
- # TimeIndex has filter() method
247
- Time = Time.filter(mask)
242
+ # Ensure mask is boolean numpy array
243
+ mask = np.asarray(mask, dtype=bool)
244
+ Z = Z.iloc[mask] if isinstance(Z, pd.DataFrame) else Z[mask]
245
+ # TimeIndex has filter() method - convert mask to list for filter()
246
+ Time = Time.filter(mask.tolist())
248
247
  _logger.info(f"Filtered to start date: {sample_start}")
249
248
 
250
249
  if sample_end is not None:
@@ -253,12 +252,11 @@ def load_data(
253
252
  mask = Time <= sample_end
254
253
  if isinstance(mask, pd.Series):
255
254
  mask = mask.values
256
- elif not isinstance(mask, np.ndarray):
257
- # Convert to numpy array if needed
258
- mask = np.asarray(mask)
259
- Z = Z[mask]
260
- # TimeIndex has filter() method
261
- Time = Time.filter(mask)
255
+ # Ensure mask is boolean numpy array
256
+ mask = np.asarray(mask, dtype=bool)
257
+ Z = Z.iloc[mask] if isinstance(Z, pd.DataFrame) else Z[mask]
258
+ # TimeIndex has filter() method - convert mask to list for filter()
259
+ Time = Time.filter(mask.tolist())
262
260
  _logger.info(f"Filtered to end date: {sample_end}")
263
261
 
264
262
  # Return raw data (transformations should be applied via custom sktime transformer in DFMDataModule)
File without changes