Repository: statsmodels/statsmodels

Issue:
`statsmodels.iolib.summary2.summary_col` bug in `regressor_order`
![image](https://user-images.githubusercontent.com/24930289/27343801-115d10ae-55b2-11e7-9430-31b483a5c930.png)
regs is a list of `RegressionResults`. The `regressor_order` command seems to change only the regressor names and not permute the coefficients appropriately


Discussion:

Resolving patch:
diff --git a/statsmodels/iolib/summary2.py b/statsmodels/iolib/summary2.py
--- a/statsmodels/iolib/summary2.py
+++ b/statsmodels/iolib/summary2.py
@@ -1,14 +1,17 @@
 from statsmodels.compat.python import (lrange, iterkeys, iteritems, lzip,
                                        reduce, itervalues, zip, string_types,
                                        range)
-from statsmodels.compat.collections import OrderedDict
-import numpy as np
-import pandas as pd
+
+from collections import OrderedDict
 import datetime
+import re
 import textwrap
+
+import numpy as np
+import pandas as pd
+
 from .table import SimpleTable
 from .tableformatting import fmt_latex, fmt_txt
-import re
 
 
 class Summary(object):
@@ -426,23 +429,24 @@ def _make_unique(list_of_names):
     return header
 
 
-def summary_col(results, float_format='%.4f', model_names=[], stars=False,
-                info_dict=None, regressor_order=[]):
+def summary_col(results, float_format='%.4f', model_names=(), stars=False,
+                info_dict=None, regressor_order=(), drop_omitted=False):
     """
     Summarize multiple results instances side-by-side (coefs and SEs)
 
     Parameters
     ----------
     results : statsmodels results instance or list of result instances
-    float_format : string
+    float_format : string, optional
         float format for coefficients and standard errors
         Default : '%.4f'
-    model_names : list of strings of length len(results) if the names are not
+    model_names : list of strings, optional
+        Must have same length as the number of results. If the names are not
         unique, a roman number will be appended to all model names
     stars : bool
         print significance stars
     info_dict : dict
-        dict of lambda functions to be applied to results instances to retrieve
+        dict of functions to be applied to results instances to retrieve
         model info. To use specific information for different models, add a
         (nested) info_dict with model name as the key.
         Example: `info_dict = {"N":..., "R2": ..., "OLS":{"R2":...}}` would
@@ -450,9 +454,13 @@ def summary_col(results, float_format='%.4f', model_names=[], stars=False,
         all other results.
         Default : None (use the info_dict specified in
         result.default_model_infos, if this property exists)
-    regressor_order : list of strings
+    regressor_order : list of strings, optional
         list of names of the regressors in the desired order. All regressors
         not specified will be appended to the end of the list.
+    drop_omitted : bool, optional
+        Includes regressors that are not specified in regressor_order. If False,
+        regressors not specified will be appended to end of the list. If True,
+        only regressors in regressors_list will be included.
     """
 
     if not isinstance(results, list):
@@ -483,6 +491,8 @@ def summary_col(results, float_format='%.4f', model_names=[], stars=False,
         summ.index = f(np.unique(varnames))
         summ = summ.reindex(f(order))
         summ.index = [x[:-4] for x in summ.index]
+        if drop_omitted:
+            summ = summ.loc[regressor_order]
 
     idx = pd.Series(lrange(summ.shape[0])) % 2 == 1
     summ.index = np.where(idx, '', summ.index.get_level_values(0))
@@ -495,7 +505,7 @@ def summary_col(results, float_format='%.4f', model_names=[], stars=False,
         cols = [_col_info(x, getattr(x, "default_model_infos", None)) for x in
                 results]
     # use unique column names, otherwise the merge will not succeed
-    for df , name in zip(cols, _make_unique([df.columns[0] for df in cols])):
+    for df, name in zip(cols, _make_unique([df.columns[0] for df in cols])):
         df.columns = [name]
     merg = lambda x, y: x.merge(y, how='outer', right_index=True,
                                 left_index=True)


Test patch:
diff --git a/statsmodels/iolib/tests/test_summary2.py b/statsmodels/iolib/tests/test_summary2.py
--- a/statsmodels/iolib/tests/test_summary2.py
+++ b/statsmodels/iolib/tests/test_summary2.py
@@ -1,7 +1,10 @@
 import warnings
-from numpy.testing import assert_equal, assert_
-from statsmodels.regression.linear_model import OLS, add_constant
+
+import numpy as np
+from numpy.testing import assert_equal
+
 from statsmodels.iolib.summary2 import summary_col
+from statsmodels.regression.linear_model import OLS, add_constant
 
 
 class TestSummaryLatex(object):
@@ -36,6 +39,44 @@ def test_summarycol(self):
         actual = '\n%s\n' % actual
         assert_equal(desired, actual)
 
+    def test_summarycol_drop_omitted(self):
+        # gh-3702
+        x = [1, 5, 7, 3, 5]
+        x = add_constant(x)
+        x2 = np.concatenate([x, np.array([[3], [9], [-1], [4], [0]])], 1)
+        y1 = [6, 4, 2, 7, 4]
+        y2 = [8, 5, 0, 12, 4]
+        reg1 = OLS(y1, x).fit()
+        reg2 = OLS(y2, x2).fit()
+        actual = summary_col([reg1, reg2], regressor_order=['const', 'x1'],
+                             drop_omitted=True)
+        assert 'x2' not in str(actual)
+        actual = summary_col([reg1, reg2], regressor_order=['x1'],
+                             drop_omitted=False)
+        assert 'const' in str(actual)
+        assert 'x2' in str(actual)
+
+    def test_summary_col_ordering_preserved(self):
+        # gh-3767
+        x = [1, 5, 7, 3, 5]
+        x = add_constant(x)
+        x2 = np.concatenate([x, np.array([[3], [9], [-1], [4], [0]])], 1)
+        y1 = [6, 4, 2, 7, 4]
+        y2 = [8, 5, 0, 12, 4]
+        reg1 = OLS(y1, x2).fit()
+        reg2 = OLS(y2, x2).fit()
+        info_dict = {'R2': lambda x: '{:.3f}'.format(int(x.rsquared)),
+                     'N': lambda x: '{0:d}'.format(int(x.nobs))}
+        original = actual = summary_col([reg1, reg2], float_format='%0.4f')
+        actual = summary_col([reg1, reg2], regressor_order=['x2', 'x1'],
+                             float_format='%0.4f',
+                             info_dict=info_dict)
+        variables = ('const', 'x1', 'x2')
+        for line in str(original).split('\n'):
+            for variable in variables:
+                if line.startswith(variable):
+                    assert line in str(actual)
+
     def test_OLSsummary(self):
         # Test that latex output of regular OLS output still contains
         # multiple tables