@@ -1441,13 +1441,13 @@ class DynamicFactorModelOptimizer:
1441
1441
requires it to be any diagonal matrix (uncorrelated errors), and
1442
1442
"scalar" requires it to be a scalar times the identity matrix. Default
1443
1443
is "diagonal".
1444
- error_order : int, optional
1445
- The order of the vector autoregression followed by the observation
1446
- error component. Default is None , corresponding to white noise errors.
1444
+ error_order_max : int, optional
1445
+ The maximum order of the vector autoregression followed by the observation
1446
+ error component. Default is 0 , corresponding to white noise errors.
1447
1447
error_var : bool, optional
1448
1448
Whether or not to model the errors jointly via a vector autoregression,
1449
1449
rather than as individual autoregressions. Has no effect unless
1450
- `error_order ` is set. Default is False.
1450
+ `error_order_max ` is set. Default is False.
1451
1451
enforce_stationarity : bool, optional
1452
1452
Whether or not to transform the AR parameters to enforce stationarity
1453
1453
in the autoregressive component of the model. Default is True.
@@ -1463,7 +1463,7 @@ class DynamicFactorModelOptimizer:
1463
1463
"""
1464
1464
1465
1465
def __init__ (self , endog , k_factors_max , factor_lag_max , factor_order_max , exog = None ,
1466
- error_order = 0 , error_var = False , error_cov_type = 'diagonal' ,
1466
+ error_order_max = 0 , error_var = False , error_cov_type = 'diagonal' ,
1467
1467
enforce_stationarity = True , ** kwargs ):
1468
1468
1469
1469
# Model properties
@@ -1474,8 +1474,8 @@ def __init__(self, endog, k_factors_max, factor_lag_max, factor_order_max, exog=
1474
1474
self .factor_lag_max = factor_lag_max
1475
1475
self .factor_order_max = factor_order_max
1476
1476
1477
- # Error-related properties (TODO: Add IC-based optimizer for error order)
1478
- self .error_order = error_order
1477
+ # Error-related properties
1478
+ self .error_order_max = error_order_max
1479
1479
self .error_var = error_var
1480
1480
self .error_cov_type = error_cov_type
1481
1481
@@ -1512,7 +1512,7 @@ def _Dstat(self, cov_matrix, k, Dstat): # Bai & Ng (2007)
1512
1512
# Compute D statistics
1513
1513
return np .power ((eigenvalues_ [r - k - 1 ] if Dstat == 1 else eigenvalues_ [:r - k ].sum ()) / eigenvalues_ .sum (), 0.5 )
1514
1514
1515
- def fit (self , factor_ic = 2 , evolution_ic = 'aic' , Dstat = 2 , delta = 0.1 , m = 1 , verbose = False , ** kwargs ):
1515
+ def fit (self , factor_ic = 2 , evolution_ic = 'aic' , error_ic = 'aic' , Dstat = 2 , delta = 0.1 , m = 1 , verbose = False , ** kwargs ):
1516
1516
"""
1517
1517
Run optimizer for model parameters following Bai & Ng (2007)
1518
1518
@@ -1522,6 +1522,8 @@ def fit(self, factor_ic=2, evolution_ic='aic', Dstat=2, delta=0.1, m=1, verbose=
1522
1522
The information criterion to use in optimizing the number of static factors in Bai & Ng (2002).
1523
1523
evolution_ic : string, optional
1524
1524
The information criterion to use in optimizing factor evolution order.
1525
+ error_ic : string, optional
1526
+ The information criterion to use in optimizing error evolution order.
1525
1527
Dstat : int, optional
1526
1528
The type of D statistics to use in optimizing the number of dynamic factors in Bai & Ng (2007).
1527
1529
delta : float, optional
@@ -1531,8 +1533,9 @@ def fit(self, factor_ic=2, evolution_ic='aic', Dstat=2, delta=0.1, m=1, verbose=
1531
1533
verbose : bool, optional
1532
1534
Flag indicating whether to print the fitted orders.
1533
1535
"""
1534
- endog = np .asarray (self .endog )
1535
- endog = endog [np .logical_not (np .isnan (endog )).any (1 )]
1536
+ mask = ~ np .any (np .isnan (self .endog ), axis = 1 )
1537
+ endog = np .asarray (self .endog )[mask ]
1538
+ exog = np .asarray (self .exog )[mask ] if self .exog else None
1536
1539
T , n = endog .shape
1537
1540
1538
1541
# Determine optimal number of static factors (r)
@@ -1563,10 +1566,26 @@ def fit(self, factor_ic=2, evolution_ic='aic', Dstat=2, delta=0.1, m=1, verbose=
1563
1566
# Optimize factor lag (cap to factor order - 1) (s)
1564
1567
self .factor_lag = max (0 , min (round (static_factors_ / self .k_factors ) - 1 , self .factor_order - 1 , self .factor_lag_max ))
1565
1568
1569
+ # Optimize error order
1570
+ res_pca = DynamicPCA (endog , ncomp = self .k_factors , M = self .factor_lag )
1571
+ lagged_factors = np .hstack ([res_pca ._shift (res_pca ._factors , periods = p , pad = np .nan )
1572
+ for p in np .arange (self .factor_lag + 1 )])
1573
+ resid = OLS (endog , lagged_factors , missing = 'drop' ).fit ().resid
1574
+ resid = res_pca ._padna (resid , before = self .factor_lag , after = self .factor_lag )
1575
+ resid = OLS (resid , exog , missing = 'drop' ).fit ().resid if exog else resid
1576
+
1577
+ if self .error_var :
1578
+ self .error_order = VAR (resid ).fit (self .error_order_max , trend = 'n' , ic = error_ic ).k_ar
1579
+ else :
1580
+ self .error_order = 0
1581
+ for i in range (n ):
1582
+ ar_order_ = ar_select_order (resid [:, i ], maxlag = self .error_order_max , trend = 'n' , ic = error_ic ).ar_lags
1583
+ self .error_order = max (self .error_order , ar_order_ [- 1 ]) if ar_order_ else self .error_order
1584
+
1566
1585
# Print fitted orders
1567
1586
if verbose :
1568
1587
print ('DynamicFactorModelOptimizer results' )
1569
- print (f'k_factors: { self .k_factors } , factor_lag: { self .factor_lag } , factor_order: { self .factor_order } ' )
1588
+ print (f'k_factors: { self .k_factors } , factor_lag: { self .factor_lag } , factor_order: { self .factor_order } , error_order: { self . error_order } ' )
1570
1589
1571
1590
return DynamicFactorModel (self .endog , self .k_factors , self .factor_lag , self .factor_order , exog = self .exog ,
1572
1591
error_order = self .error_order , error_var = self .error_var , error_cov_type = self .error_cov_type ,
0 commit comments