Repository: statsmodels/statsmodels

Issue:
BUG: dynamic factor start_params may be explosive with autoregressive errors
Currently we just take a shortcut in `start_params` where if the error terms are individual ARs, rather than jointly as a VAR, we still estimate a VAR and then take the diagonal elements of each coefficient matrix to get the AR parameters. But these may not lead to stationary models.


Discussion:
Hey, I'd like to contribute towards this issue,but I'm new to opensource,can you provide some description/way-points to start.Thanks.
Thanks @aghinsa! You can start by taking a look at lines 518 - 544 of the file [`statsmodels/tsa/statespace/dynamic_factor.py`](https://github.com/statsmodels/statsmodels/blob/master/statsmodels/tsa/statespace/dynamic_factor.py#L518).

Basically, the idea here is that the error terms in the dynamic factor model can be modeled as autocorrelated rather than as white noise. This is specified in the model by setting the `error_order` argument to be greater than zero.

In that case, we need to estimate starting parameters for the autoregressive model that generates those errors. There are two ways to do this: first, we might think that each individual error term follows an AR(p) process, or second we might think that all of the error terms jointly follow a VAR(p) process. This is specified in the model by setting the `error_var` argument to be False (for the individual AR models) or True (for the joint VAR model).

This is what is done in the lines 518 - 544, as follows:

- In lines 518-520 we estimate a VAR model to get starting coefficients for the error transitions.
- In lines 522 - 533 we make sure those coefficients are stationary *for a VAR process*.
- In lines 537 - 538, we set the coefficients in the joint VAR case
- In lines 543 - 544, we set the coefficients in the individual AR(p) case by just taking the diagonal elements of the VAR coefficients, but this isn't always going to work, and that's why this message is here.

So what I think we need to do is put the `if self.error_var` conditional around all of the code in lines 518 - 538, and then create a new procedure for the `else` case.

That alternative procedure should be to get start params for an AR(p) model from each of the columns of `endog` separately. In fact, I would just set up an SARIMAX model and then get that model's `start_params` (i.e. no need to `fit` the model).
Hey,I'll go through the code and try to figure out,this is my first time contributing, so pretty sure I'll need some help.Please to consider.Thanks.

Resolving patch:
diff --git a/statsmodels/tsa/statespace/dynamic_factor.py b/statsmodels/tsa/statespace/dynamic_factor.py
--- a/statsmodels/tsa/statespace/dynamic_factor.py
+++ b/statsmodels/tsa/statespace/dynamic_factor.py
@@ -19,6 +19,7 @@
 from statsmodels.multivariate.pca import PCA
 from statsmodels.regression.linear_model import OLS
 from statsmodels.tsa.vector_ar.var_model import VAR
+from statsmodels.tsa.arima.model import ARIMA
 from statsmodels.tools.tools import Bunch
 from statsmodels.tools.data import _is_using_pandas
 from statsmodels.tsa.tsatools import lagmat
@@ -516,7 +517,7 @@ def start_params(self):
                 cov_factor = np.diag(endog.std(axis=0))
                 params[self._params_error_cov] = (
                     cov_factor[self._idx_lower_error_cov].ravel())
-        else:
+        elif self.error_var:
             mod_errors = VAR(endog)
             res_errors = mod_errors.fit(maxlags=self.error_order, ic=None,
                                         trend='nc')
@@ -535,15 +536,8 @@ def start_params(self):
                                  ' `enforce_stationarity` set to True.')
 
             # Get the error autoregressive parameters
-            if self.error_var:
-                params[self._params_error_transition] = (
+            params[self._params_error_transition] = (
                     np.array(res_errors.params.T).ravel())
-            else:
-                # In the case of individual autoregressions, extract just the
-                # diagonal elements
-                # TODO: can lead to explosive parameterizations
-                params[self._params_error_transition] = (
-                    res_errors.params.T[self._idx_error_diag])
 
             # Get the error covariance parameters
             if self.error_cov_type == 'scalar':
@@ -561,6 +555,18 @@ def start_params(self):
                     res_errors.sigma_u.diagonal().mean()**0.5)
                 params[self._params_error_cov] = (
                     cov_factor[self._idx_lower_error_cov].ravel())
+        else:
+            error_ar_params = []
+            error_cov_params = []
+            for i in range(self.k_endog):
+                mod_error = ARIMA(endog[:, i], order=(self.error_order, 0, 0),
+                                  trend='n', enforce_stationarity=True)
+                res_error = mod_error.fit(method='burg')
+                error_ar_params += res_error.params[:self.error_order].tolist()
+                error_cov_params += res_error.params[-1:].tolist()
+
+            params[self._params_error_transition] = np.r_[error_ar_params]
+            params[self._params_error_cov] = np.r_[error_cov_params]
 
         return params
 


Test patch: