Skip to content

Commit b451e9f

Browse files
Added Error Order Optimizer
1 parent c58e47b commit b451e9f

File tree

2 files changed

+48
-19
lines changed

2 files changed

+48
-19
lines changed

dynamicfactoranalysis/DynamicFactorModel.py

+30-11
Original file line numberDiff line numberDiff line change
@@ -1441,13 +1441,13 @@ class DynamicFactorModelOptimizer:
14411441
requires it to be any diagonal matrix (uncorrelated errors), and
14421442
"scalar" requires it to be a scalar times the identity matrix. Default
14431443
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.
14471447
error_var : bool, optional
14481448
Whether or not to model the errors jointly via a vector autoregression,
14491449
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.
14511451
enforce_stationarity : bool, optional
14521452
Whether or not to transform the AR parameters to enforce stationarity
14531453
in the autoregressive component of the model. Default is True.
@@ -1463,7 +1463,7 @@ class DynamicFactorModelOptimizer:
14631463
"""
14641464

14651465
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',
14671467
enforce_stationarity=True, **kwargs):
14681468

14691469
# Model properties
@@ -1474,8 +1474,8 @@ def __init__(self, endog, k_factors_max, factor_lag_max, factor_order_max, exog=
14741474
self.factor_lag_max = factor_lag_max
14751475
self.factor_order_max = factor_order_max
14761476

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
14791479
self.error_var = error_var
14801480
self.error_cov_type = error_cov_type
14811481

@@ -1512,7 +1512,7 @@ def _Dstat(self, cov_matrix, k, Dstat): # Bai & Ng (2007)
15121512
# Compute D statistics
15131513
return np.power((eigenvalues_[r - k - 1] if Dstat == 1 else eigenvalues_[:r - k].sum()) / eigenvalues_.sum(), 0.5)
15141514

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):
15161516
"""
15171517
Run optimizer for model parameters following Bai & Ng (2007)
15181518
@@ -1522,6 +1522,8 @@ def fit(self, factor_ic=2, evolution_ic='aic', Dstat=2, delta=0.1, m=1, verbose=
15221522
The information criterion to use in optimizing the number of static factors in Bai & Ng (2002).
15231523
evolution_ic : string, optional
15241524
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.
15251527
Dstat : int, optional
15261528
The type of D statistics to use in optimizing the number of dynamic factors in Bai & Ng (2007).
15271529
delta : float, optional
@@ -1531,8 +1533,9 @@ def fit(self, factor_ic=2, evolution_ic='aic', Dstat=2, delta=0.1, m=1, verbose=
15311533
verbose : bool, optional
15321534
Flag indicating whether to print the fitted orders.
15331535
"""
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
15361539
T, n = endog.shape
15371540

15381541
# 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=
15631566
# Optimize factor lag (cap to factor order - 1) (s)
15641567
self.factor_lag = max(0, min(round(static_factors_ / self.k_factors) - 1, self.factor_order - 1, self.factor_lag_max))
15651568

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+
15661585
# Print fitted orders
15671586
if verbose:
15681587
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}')
15701589

15711590
return DynamicFactorModel(self.endog, self.k_factors, self.factor_lag, self.factor_order, exog=self.exog,
15721591
error_order=self.error_order, error_var=self.error_var, error_cov_type=self.error_cov_type,

examples/DynamicFactorModel/DynamicFactorModel.ipynb

+18-8
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)