Repository: statsmodels/statsmodels

Issue:
BUG: GMM summary has incorrect param_names
AFAICS, GMM does not have param_names and uses exog_names in summary() which does not have the correct length. summary table is truncated at the length of provided names

https://stackoverflow.com/questions/49272902/i-have-a-probable-with-using-statsmodels-sandbox-regression-gmm-gmm/49279673?s=1|0.0000#49279673


work around: Change the exog_names to have the correct length, e.g.

```
res.model.exog_names[:] = 'p0 p1 p2 p3'.split()
print(res.summary())
```


Discussion:
GMM doesn't seem to have unit tests for this case (xnames to short)

adding `assert_equal(len(summ.tables[1]), len(res1.params) + 1)` to test_gmm_poisson.CheckGMM doesn't fail, i.e. correct number or params in summary params_table

`test_gmm.py` has smoke tests for summary in some classes, but not in CheckGMM

`_fix_param_names(self, params, param_names=None)` does not correctly handle the case where len(xnames) < len(params), AFAICS
maybe we can use some pattern from GenericLikelihoodResults, which I fixed later than the code here, .IIRC

Needs another example as a test case, e.g. gamma or the one above, for which I have examples in notebooks.


according to test coverage, the incorrect if else branch is not used
https://coveralls.io/builds/16330861/source?filename=statsmodels%2Fsandbox%2Fregression%2Fgmm.py#L532

I will replace the xnames in this case by generic names.

Resolving patch:
diff --git a/statsmodels/iolib/summary.py b/statsmodels/iolib/summary.py
--- a/statsmodels/iolib/summary.py
+++ b/statsmodels/iolib/summary.py
@@ -463,6 +463,9 @@ def summary_params(results, yname=None, xname=None, alpha=.05, use_t=True,
 
     _, xname = _getnames(results, yname=yname, xname=xname)
 
+    if len(xname) != len(params):
+        raise ValueError('xnames and params do not have the same length')
+
     params_stubs = xname
 
     exog_idx = lrange(len(xname))
diff --git a/statsmodels/sandbox/regression/gmm.py b/statsmodels/sandbox/regression/gmm.py
--- a/statsmodels/sandbox/regression/gmm.py
+++ b/statsmodels/sandbox/regression/gmm.py
@@ -528,8 +528,32 @@ def _fix_param_names(self, params, param_names=None):
                 # cut in front for poisson multiplicative
                 self.data.xnames = xnames[-len(params):]
             elif len(params) > len(xnames):
-                # cut at the end
-                self.data.xnames = xnames[:len(params)]
+                # use generic names
+                self.data.xnames = ['p%2d' % i for i in range(len(params))]
+
+    def set_param_names(self, param_names, k_params=None):
+        """set the parameter names in the model
+
+        Parameters
+        ----------
+        param_names : list of strings
+            param_names should have the same length as the number of params
+        k_params : None or int
+            If k_params is None, then the k_params attribute is used, unless
+            it is None.
+            If k_params is not None, then it will also set the k_params
+            attribute.
+        """
+        if k_params is not None:
+            self.k_params = k_params
+        else:
+            k_params = self.k_params
+
+        if k_params == len(param_names):
+            self.data.xnames = param_names
+        else:
+            raise ValueError('param_names has the wrong length')
+
 
     def fit(self, start_params=None, maxiter=10, inv_weights=None,
                   weights_method='cov', wargs=(),


Test patch:
diff --git a/statsmodels/sandbox/regression/tests/test_gmm.py b/statsmodels/sandbox/regression/tests/test_gmm.py
--- a/statsmodels/sandbox/regression/tests/test_gmm.py
+++ b/statsmodels/sandbox/regression/tests/test_gmm.py
@@ -237,6 +237,12 @@ def test_hypothesis(self):
         # Smoke test for Wald
         res_wald = res1.wald_test(restriction[:-1])
 
+    def test_smoke(self):
+        res1 = self.res1
+        summ = res1.summary()
+        # len + 1 is for header line
+        assert_equal(len(summ.tables[1]), len(res1.params) + 1)
+
 
 class TestGMMSt1(CheckGMM):
 
@@ -678,7 +684,8 @@ def test_hausman(self):
 
     def test_smoke(self):
         res1 = self.res1
-        res1.summary()
+        summ = res1.summary()
+        assert_equal(len(summ.tables[1]), len(res1.params) + 1)
 
 
 
@@ -752,4 +759,45 @@ def test_noconstant():
 
     assert_equal(res.fvalue, np.nan)
     # smoke test
-    res.summary()
+    summ = res.summary()
+    assert_equal(len(summ.tables[1]), len(res.params) + 1)
+
+
+def test_gmm_basic():
+    # this currently tests mainly the param names, exog_names
+    # see #4340
+    cd = np.array([1.5, 1.5, 1.7, 2.2, 2.0, 1.8, 1.8, 2.2, 1.9, 1.6, 1.8, 2.2,
+                   2.0, 1.5, 1.1, 1.5, 1.4, 1.7, 1.42, 1.9])
+    dcd = np.array([0, 0.2 ,0.5, -0.2, -0.2, 0, 0.4, -0.3, -0.3, 0.2, 0.4,
+                    -0.2, -0.5, -0.4, 0.4, -0.1, 0.3, -0.28, 0.48, 0.2])
+    inst = np.column_stack((np.ones(len(cd)), cd))
+
+    class GMMbase(gmm.GMM):
+        def momcond(self, params):
+            p0, p1, p2, p3 = params
+            endog = self.endog[:, None]
+            exog = self.exog
+            inst = self.instrument
+
+            mom0 = (endog - p0 - p1 * exog) * inst
+            mom1 = ((endog - p0 - p1 * exog)**2 -
+                    p2 * (exog**(2 * p3)) / 12) * inst
+            g = np.column_stack((mom0, mom1))
+            return g
+
+    beta0 = np.array([0.1, 0.1, 0.01, 1])
+    res = GMMbase(endog=dcd, exog=cd, instrument=inst, k_moms=4,
+                  k_params=4).fit(beta0, optim_args={'disp': 0})
+    summ = res.summary()
+    assert_equal(len(summ.tables[1]), len(res.params) + 1)
+    pnames = ['p%2d' % i for i in range(len(res.params))]
+    assert_equal(res.model.exog_names, pnames)
+
+    # check set_param_names method
+    mod = GMMbase(endog=dcd, exog=cd, instrument=inst, k_moms=4,
+                  k_params=4)
+    # use arbitrary names
+    pnames = ['beta', 'gamma', 'psi', 'phi']
+    mod.set_param_names(pnames)
+    res1 = mod.fit(beta0, optim_args={'disp': 0})
+    assert_equal(res1.model.exog_names, pnames)
diff --git a/statsmodels/sandbox/regression/tests/test_gmm_poisson.py b/statsmodels/sandbox/regression/tests/test_gmm_poisson.py
--- a/statsmodels/sandbox/regression/tests/test_gmm_poisson.py
+++ b/statsmodels/sandbox/regression/tests/test_gmm_poisson.py
@@ -119,7 +119,8 @@ def test_other(self):
 
     def test_smoke(self):
         res1 = self.res1
-        res1.summary()
+        summ = res1.summary()
+        assert_equal(len(summ.tables[1]), len(res1.params) + 1)
 
 
 class TestGMMAddOnestep(CheckGMM):