Repository: statsmodels/statsmodels

Issue:
BUG: User-defined parameter ordering in simulation
#### Description

It seems the element ordering of `params` in VARMAX.simulate and `mod.param_names` is inconsistent. This results in unexpected simulation behavior with user-defined, fixed parameters. See also [this question](https://stackoverflow.com/questions/63631670/statsmodels-user-defined-parameter-ordering-in-simulate/) in StackOverflow. 

#### Steps to reproduce

```python
import statsmodels as statsmodels 
import statsmodels.api as sm
import pandas as pd
import numpy as np
endog = pd.DataFrame({"y":[np.nan, np.nan], "y2":[np.nan, np.nan]}, index=[0, 1])
statsmodels.__version__
>>>>>>>>>
'0.12.0'
```


*Example 1*: Two-variable VAR(1) with intercepts and drifts, all parameters fixed to 0 except for  intercept of ```y``` set to 1. Yields expected results.

```python
params = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
mod1 = sm.tsa.VARMAX(endog, order=(1, 0), trend=[1, 1], trend_offset=0)
print('\n'.join('{} :{}'.format(*k) for k in zip(mod1.param_names, params)))
print(mod1.simulate(params=params, nsimulations=5, initial_state=[0, 0]))

>>>>>>>>>
intercept.y :1
intercept.y2 :0
drift.y :0
drift.y2 :0
L1.y.y :0
L1.y2.y :0
L1.y.y2 :0
L1.y2.y2 :0
sqrt.var.y :0
sqrt.cov.y.y2 :0
sqrt.var.y2 :0

     y   y2
0  0.0  0.0
1  1.0  0.0
2  1.0  0.0
3  1.0  0.0
4  1.0  0.0 
```

*Example 2*: Same as in Example 1 but now intercept of variable ```y``` is set to 0 and intercept of variables ```y2``` is set to 1. Rather than yielding the expected result it seems that the second element in ```params``` actually controls the drift parameter of ```y``` rather than the intercept term of ```y2```. This contradicts the parameter ordering given by ```mod2.param_names```.

```python
params = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
mod2 = sm.tsa.VARMAX(endog, order=(1, 0), trend=[1, 1], trend_offset=0)
print('\n'.join('{} :{}'.format(*k) for k in zip(mod2.param_names, params)))
print(mod2.simulate(params=params, nsimulations=5, initial_state=[0, 0]))

>>>>>>>>>
intercept.y :0
intercept.y2 :1
drift.y :0
drift.y2 :0
L1.y.y :0
L1.y2.y :0
L1.y.y2 :0
L1.y2.y2 :0
sqrt.var.y :0
sqrt.cov.y.y2 :0
sqrt.var.y2 :0

     y   y2
0  0.0  0.0
1  1.0  0.0
2  2.0  0.0
3  3.0  0.0
4  4.0  0.0
```


Discussion:
Thanks very much for this report. This is definitely a bug.

Resolving patch:
diff --git a/statsmodels/tsa/statespace/varmax.py b/statsmodels/tsa/statespace/varmax.py
--- a/statsmodels/tsa/statespace/varmax.py
+++ b/statsmodels/tsa/statespace/varmax.py
@@ -454,16 +454,14 @@ def param_names(self):
 
         # 1. Intercept terms
         if self.k_trend > 0:
-            for i in self.polynomial_trend.nonzero()[0]:
-                if i == 0:
-                    param_names += ['intercept.%s' % endog_names[j]
-                                    for j in range(self.k_endog)]
-                elif i == 1:
-                    param_names += ['drift.%s' % endog_names[j]
-                                    for j in range(self.k_endog)]
-                else:
-                    param_names += ['trend.%d.%s' % (i, endog_names[j])
-                                    for j in range(self.k_endog)]
+            for j in range(self.k_endog):
+                for i in self.polynomial_trend.nonzero()[0]:
+                    if i == 0:
+                        param_names += ['intercept.%s' % endog_names[j]]
+                    elif i == 1:
+                        param_names += ['drift.%s' % endog_names[j]]
+                    else:
+                        param_names += ['trend.%d.%s' % (i, endog_names[j])]
 
         # 2. AR terms
         param_names += [
@@ -724,7 +722,7 @@ def update(self, params, transformed=True, includes_fixed=False,
             # just += later
             if not self.mle_regression:
                 zero = np.array(0, dtype=params.dtype)
-                self.ssm[self._idx_state_intercept] = zero
+                self.ssm['state_intercept', :] = zero
 
             trend_params = params[self._params_trend].reshape(
                 self.k_endog, self.k_trend).T


Test patch:
diff --git a/statsmodels/tsa/statespace/tests/test_varmax.py b/statsmodels/tsa/statespace/tests/test_varmax.py
--- a/statsmodels/tsa/statespace/tests/test_varmax.py
+++ b/statsmodels/tsa/statespace/tests/test_varmax.py
@@ -1223,3 +1223,60 @@ def test_vma1_exog():
     # Have to ignore first 2 observations due to differences in initialization
     assert_allclose(res_mva.llf_obs[2:],
                     (res_ma1.llf_obs + res_ma2.llf_obs)[2:])
+
+
+def test_param_names_trend():
+    endog = np.zeros((3, 2))
+    base_names = ['L1.y1.y1', 'L1.y2.y1', 'L1.y1.y2', 'L1.y2.y2',
+                  'sqrt.var.y1', 'sqrt.cov.y1.y2', 'sqrt.var.y2']
+    base_params = [0.5, 0, 0, 0.4, 1.0, 0.0, 1.0]
+
+    # No trend
+    mod = varmax.VARMAX(endog, order=(1, 0), trend='n')
+    desired = base_names
+    assert_equal(mod.param_names, desired)
+
+    # Intercept
+    mod = varmax.VARMAX(endog, order=(1, 0), trend=[1])
+    desired = ['intercept.y1', 'intercept.y2'] + base_names
+    assert_equal(mod.param_names, desired)
+    mod.update([1.2, -0.5] + base_params)
+    assert_allclose(mod['state_intercept'], [1.2, -0.5])
+
+    # Intercept + drift
+    mod = varmax.VARMAX(endog, order=(1, 0), trend=[1, 1])
+    desired = (['intercept.y1', 'drift.y1',
+                'intercept.y2', 'drift.y2'] + base_names)
+    assert_equal(mod.param_names, desired)
+    mod.update([1.2, 0, -0.5, 0] + base_params)
+    assert_allclose(mod['state_intercept', 0], 1.2)
+    assert_allclose(mod['state_intercept', 1], -0.5)
+    mod.update([0, 1, 0, 1.1] + base_params)
+    assert_allclose(mod['state_intercept', 0], np.arange(2, 5))
+    assert_allclose(mod['state_intercept', 1], 1.1 * np.arange(2, 5))
+    mod.update([1.2, 1, -0.5, 1.1] + base_params)
+    assert_allclose(mod['state_intercept', 0], 1.2 + np.arange(2, 5))
+    assert_allclose(mod['state_intercept', 1], -0.5 + 1.1 * np.arange(2, 5))
+
+    # Drift only
+    mod = varmax.VARMAX(endog, order=(1, 0), trend=[0, 1])
+    desired = ['drift.y1', 'drift.y2'] + base_names
+    assert_equal(mod.param_names, desired)
+    mod.update([1, 1.1] + base_params)
+    assert_allclose(mod['state_intercept', 0], np.arange(2, 5))
+    assert_allclose(mod['state_intercept', 1], 1.1 * np.arange(2, 5))
+
+    # Intercept + third order
+    mod = varmax.VARMAX(endog, order=(1, 0), trend=[1, 0, 1])
+    desired = (['intercept.y1', 'trend.2.y1',
+                'intercept.y2', 'trend.2.y2'] + base_names)
+    assert_equal(mod.param_names, desired)
+    mod.update([1.2, 0, -0.5, 0] + base_params)
+    assert_allclose(mod['state_intercept', 0], 1.2)
+    assert_allclose(mod['state_intercept', 1], -0.5)
+    mod.update([0, 1, 0, 1.1] + base_params)
+    assert_allclose(mod['state_intercept', 0], np.arange(2, 5)**2)
+    assert_allclose(mod['state_intercept', 1], 1.1 * np.arange(2, 5)**2)
+    mod.update([1.2, 1, -0.5, 1.1] + base_params)
+    assert_allclose(mod['state_intercept', 0], 1.2 + np.arange(2, 5)**2)
+    assert_allclose(mod['state_intercept', 1], -0.5 + 1.1 * np.arange(2, 5)**2)