Repository: statsmodels/statsmodels

Issue:
BUG: maxlags parameter in VAR model shouldn't effect #exogenous variables in the model
#### Bug description

First, the "maxlags" parameter in VAR.fit() method is misleading. In principle, it should just be the endogenous "lag" value extracted from "select_order" as indicated in the code snippet below.

Second, the maxlags parameters in VAR.fit() should only effect the number endogenous variables based on the maxlag value provided. It should never effect the total number of exogenous variables included in each of the VAR model equation as shown in the example below (in details section).


#### Code Sample


```python
from statsmodels.tsa.api import VAR

# this is just a sample code for illustration purposes.
model = VAR(endog=endo_df, exog=exo_df)
lag_dict = model.select_order(4)
lag = lag_dict.selected_orders['aic']

# lets, say the lag value is 2 in this case
result = model.fit(maxlags=lag)
```
<details>

Example:

Consider a scenario, where the number of exogenous variables is 5, number of endogenous variable is 2 and the I am providing the maxlag parameter value as 2 in VAR.fit() model.

Each of the VAR model for both the endogenous variables should always included 5 exogenous variables in addition to the lagged values of the two endogenous variables.

In my usecase, the maxlag parameter is affecting the total number of exogenous variables included in the model apart from the endogenous variables.

#### Expected Output
Expected equation for the endogenous variables from the VAR model:

```math 
endo_V1 = L1.endo_V1 + L2.endo_V1 + L1.endo_V2 + L2.endo_V2 + exo_V1 + ..... + exo_V5

endo_V2 = L1.endo_V1 + L2.endo_V1 + L1.endo_V2 + L2.endo_V2 + exo_V1 + ..... + exo_V5

```

#### Output of ``import statsmodels.api as sm; sm.show_versions()``
version: '0.10.2'
</details>



Discussion:
Thanks for submitting this issue. Can you post a fully replicable example, including data (simulated data is fine if it replicates the problem).

I wasn't immediately able to replicate a change in the number of `exog` variables included in the model coming from the `maxlags` argument.
Are you looking at summary or params? I think summary might not be correct
in 0.10.2.

On Mon, Dec 30, 2019, 00:39 Manish Kumar <notifications@github.com> wrote:

> Describe the bug
>
> First, the "maxlags" parameter in VAR.fit() method is misleading. In
> principle, it should just be the endogenous "lag" value extracted from
> "select_order" as indicated in the code snippet below.
>
> Second, the maxlags parameters in VAR.fit() should only effect the number
> endogenous variables based on the maxlag value provided. It should never
> effect the total number of exogenous variables included in each of the VAR
> model equation as shown in the example below (in details section).
> Code Sample, a copy-pastable example if possible
>
> from statsmodels.tsa.api import VAR
> # this is just a sample code for illustration purposes.
> model = VAR(endog=endo_df, exog=exo_df)
> lag_dict = model.select_order(4)
> lag = lag_dict.selected_orders['aic']
> # lets, say the lag value is 2 in this case
> result = model.fit(maxlags=lag)
>
> Example
>
> Consider a scenario, where the number of exogenous variables is 5, number
> of endogenous variable is 2 and the I am providing the maxlag parameter
> value as 2 in VAR.fit() model.
>
> Each of the VAR model for both the endogenous variables should always
> included 5 exogenous variables in addition to the lagged values of the two
> endogenous variables.
>
> In my usecase, the maxlag parameter is affecting the total number of
> exogenous variables included in the model apart from the endogenous
> variables.
> Expected Output
>
> Expected equation for the endogenous variables from the VAR model:
>
> endo_V1 = L1.endo_V1 + L2.endo_V1 + L1.endo_V2 + L2.endo_V2 + exo_V1 + ..... + exo_V5
>
> endo_V2 = L1.endo_V1 + L2.endo_V1 + L1.endo_V2 + L2.endo_V2 + exo_V1 + ..... + exo_V5
>
>
> Output of import statsmodels.api as sm; sm.show_versions()
>
> version: '0.10.2'
>
> [paste the output of import statsmodels.api as sm; sm.show_versions()
> here below this line]
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/statsmodels/statsmodels/issues/6376?email_source=notifications&email_token=ABKTSRK65APDIW5YIQHI7I3Q3EYKPA5CNFSM4KBEOVS2YY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4IDHBYBQ>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABKTSRNIEEN4BBRTXHDU5CTQ3EYKPANCNFSM4KBEOVSQ>
> .
>

@bashtage I am looking at the summary. Is the issue with summary been resolved in later version of statsmodels?
Apart from this, I also want to extract each equation from the VAR model so that I can analyse feature importance separately for each endogenos variable. Could you please comment on that.?
> Thanks for submitting this issue. Can you post a fully replicable example, including data (simulated data is fine if it replicates the problem).
> 
> I wasn't immediately able to replicate a change in the number of `exog` variables included in the model coming from the `maxlags` argument.

@ChadFulton Please see the [`https://drive.google.com/file/d/1r7b3tR2f-CCe56emjVPhhCYODgG5vD1f/view?usp=sharing`](url) notebook attached for your reference. I have also added comments to explain my queries better in the notebook. You can download the .html from the link added.
Thank you.
> Apart from this, I also want to extract each equation from the VAR model so that I can analyse feature importance separately for each endogenos variable. Could you please comment on that.?

There are no methods available that simplify this operation.  You would need to manually use OLS.  


Resolving patch:
diff --git a/statsmodels/tsa/vector_ar/util.py b/statsmodels/tsa/vector_ar/util.py
--- a/statsmodels/tsa/vector_ar/util.py
+++ b/statsmodels/tsa/vector_ar/util.py
@@ -79,8 +79,18 @@ def make_lag_names(names, lag_order, trendorder=1, exog=None):
     if trendorder > 2:
         lag_names.insert(2, 'trend**2')
     if exog is not None:
+        if isinstance(exog, pd.Series):
+            exog = pd.DataFrame(exog)
+        elif not hasattr(exog, 'ndim'):
+            exog = np.asarray(exog)
+        if exog.ndim == 1:
+            exog = exog[:, None]
         for i in range(exog.shape[1]):
-            lag_names.insert(trendorder + i, "exog" + str(i))
+            if isinstance(exog, pd.DataFrame):
+                exog_name = str(exog.columns[i])
+            else:
+                exog_name = "exog" + str(i)
+            lag_names.insert(trendorder + i, exog_name)
     return lag_names
 
 
diff --git a/statsmodels/tsa/vector_ar/var_model.py b/statsmodels/tsa/vector_ar/var_model.py
--- a/statsmodels/tsa/vector_ar/var_model.py
+++ b/statsmodels/tsa/vector_ar/var_model.py
@@ -626,14 +626,18 @@ def fit(self, maxlags=None, method='ols', ic=None, trend='c',
                 lags = 1
 
         k_trend = util.get_trendorder(trend)
+        orig_exog_names = self.exog_names
         self.exog_names = util.make_lag_names(self.endog_names, lags, k_trend)
         self.nobs = self.n_totobs - lags
 
         # add exog to data.xnames (necessary because the length of xnames also
         # determines the allowed size of VARResults.params)
         if self.exog is not None:
-            x_names_to_add = [("exog%d" % i)
-                              for i in range(self.exog.shape[1])]
+            if orig_exog_names:
+                x_names_to_add = orig_exog_names
+            else:
+                x_names_to_add = [("exog%d" % i)
+                                  for i in range(self.exog.shape[1])]
             self.data.xnames = (self.data.xnames[:k_trend] +
                                 x_names_to_add +
                                 self.data.xnames[k_trend:])
@@ -1207,7 +1211,8 @@ def __init__(self, endog, endog_lagged, params, sigma_u, lag_order,
         self.nobs = self.n_totobs - lag_order
         self.trend = trend
         k_trend = util.get_trendorder(trend)
-        self.exog_names = util.make_lag_names(names, lag_order, k_trend, exog)
+        self.exog_names = util.make_lag_names(names, lag_order, k_trend,
+                                              model.data.orig_exog)
         self.params = params
         self.exog = exog
 


Test patch:
diff --git a/statsmodels/tsa/vector_ar/tests/test_var.py b/statsmodels/tsa/vector_ar/tests/test_var.py
--- a/statsmodels/tsa/vector_ar/tests/test_var.py
+++ b/statsmodels/tsa/vector_ar/tests/test_var.py
@@ -831,14 +831,14 @@ def test_summaries_exog(reset_randomstate):
 
     res = VAR(endog=endog, exog=exog).fit(maxlags=0)
     summ = res.summary().summary
-    assert 'exog0' in summ
-    assert 'exog1' in summ
-    assert 'exog2' in summ
-    assert 'exog3' in summ
+    assert 'exog_0' in summ
+    assert 'exog_1' in summ
+    assert 'exog_2' in summ
+    assert 'exog_3' in summ
 
     res = VAR(endog=endog, exog=exog).fit(maxlags=2)
     summ = res.summary().summary
-    assert 'exog0' in summ
-    assert 'exog1' in summ
-    assert 'exog2' in summ
-    assert 'exog3' in summ
+    assert 'exog_0' in summ
+    assert 'exog_1' in summ
+    assert 'exog_2' in summ
+    assert 'exog_3' in summ