Repository: statsmodels/statsmodels

Issue:
BUG: summary2 output does not include extra_text

random OLS model

```
model1.summary2().extra_txt
[]
model1.summary().extra_txt
'Notes:\n[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.'
```

If there is an extra text as in `summary_col`, then it is not included in the html output, but is included in print output.
https://github.com/statsmodels/statsmodels/pull/8658#issuecomment-1426013984



Discussion:
This looks like an issue of `RegressionResults`, a class in `regression.linear_model`.

The method `summary2` creates an instance of `iolib.summary2.Summary` but does not provide the variable `etext` that is provided when the analogous `summary.Summary` is called by `summary`.

I guess that `summary2.Summary` is worth fixing anyway. But I'm wondering how `summary2` (method) ended up in `RegressionResults`. I thought `iolib.summary2` was being used to compare the results of multiple regressions. 

Resolving patch:
diff --git a/statsmodels/iolib/summary2.py b/statsmodels/iolib/summary2.py
--- a/statsmodels/iolib/summary2.py
+++ b/statsmodels/iolib/summary2.py
@@ -103,7 +103,7 @@ def add_dict(self, d, ncols=2, align='l', float_format="%.4f"):
 
     def add_text(self, string):
         """Append a note to the bottom of the summary table. In ASCII tables,
-        the note will be wrapped to table width. Notes are not indendented.
+        the note will be wrapped to table width. Notes are not indented.
         """
         self.extra_txt.append(string)
 
@@ -201,7 +201,12 @@ def as_html(self):
         tab = [x.as_html() for x in simple_tables]
         tab = '\n'.join(tab)
 
-        return tab
+        temp_txt = [st.replace('\n', '<br/>\n')for st in self.extra_txt]
+        txt = '<br/>\n'.join(temp_txt)
+
+        out = '<br/>\n'.join([tab, txt])
+
+        return out
 
     def as_latex(self, label=''):
         """Generate LaTeX Summary Table
@@ -234,8 +239,12 @@ def as_latex(self, label=''):
             # create single tabular object for summary_col
             tab = re.sub(to_replace, r'\\midrule\n', tab)
 
-        out = '\\begin{table}', title, label, tab, '\\end{table}'
-        out = '\n'.join(out)
+        non_captioned = '\\begin{table}', title, label, tab, '\\end{table}'
+        non_captioned = '\n'.join(non_captioned)
+
+        txt = ' \\newline \n'.join(self.extra_txt)
+        out = non_captioned + '\n\\bigskip\n' + txt
+
         return out
 
 
diff --git a/statsmodels/regression/linear_model.py b/statsmodels/regression/linear_model.py
--- a/statsmodels/regression/linear_model.py
+++ b/statsmodels/regression/linear_model.py
@@ -721,7 +721,8 @@ class WLS(RegressionModel):
     >>> results.tvalues
     array([ 2.0652652 ,  0.35684428])
     >>> print(results.t_test([1, 0]))
-    <T test: effect=array([ 2.91666667]), sd=array([[ 1.41224801]]), t=array([[ 2.0652652]]), p=array([[ 0.04690139]]), df_denom=5>
+    <T test: effect=array([ 2.91666667]), sd=array([[ 1.41224801]]),
+     t=array([[ 2.0652652]]), p=array([[ 0.04690139]]), df_denom=5>
     >>> print(results.f_test([0, 1]))
     <F test: F=array([[ 0.12733784]]), p=[[ 0.73577409]], df_denom=5, df_num=1>
     """ % {'params': base._model_params_doc,
@@ -907,7 +908,8 @@ class OLS(WLS):
     ==============================================================================
 
     >>> print(results.f_test(np.identity(2)))
-    <F test: F=array([[159.63031026]]), p=1.2607168903696672e-20, df_denom=43, df_num=2>
+    <F test: F=array([[159.63031026]]), p=1.2607168903696672e-20,
+     df_denom=43, df_num=2>
     """ % {'params': base._model_params_doc,
            'extra_params': base._missing_param_doc + base._extra_param_doc}
 
@@ -1257,9 +1259,11 @@ class GLSAR(GLS):
     >>> results.tvalues
     array([ -2.10304127,  21.8047269 ])
     >>> print(results.t_test([1, 0]))
-    <T test: effect=array([-0.66661205]), sd=array([[ 0.31697526]]), t=array([[-2.10304127]]), p=array([[ 0.06309969]]), df_denom=3>
+    <T test: effect=array([-0.66661205]), sd=array([[ 0.31697526]]),
+     t=array([[-2.10304127]]), p=array([[ 0.06309969]]), df_denom=3>
     >>> print(results.f_test(np.identity(2)))
-    <F test: F=array([[ 1815.23061844]]), p=[[ 0.00002372]], df_denom=3, df_num=2>
+    <F test: F=array([[ 1815.23061844]]), p=[[ 0.00002372]],
+     df_denom=3, df_num=2>
 
     Or, equivalently
 
@@ -1488,7 +1492,10 @@ def yule_walker(x, order=1, method="adjusted", df=None, inv=False,
             raise
 
     sigmasq = r[0] - (r[1:]*rho).sum()
-    sigma = np.sqrt(sigmasq) if not np.isnan(sigmasq) and sigmasq > 0 else np.nan
+    if not np.isnan(sigmasq) and sigmasq > 0:
+        sigma = np.sqrt(sigmasq)
+    else:
+        sigma = np.nan
     if inv:
         return rho, sigma, np.linalg.inv(R)
     else:
@@ -2637,9 +2644,13 @@ def get_robustcov_results(self, cov_type='HC1', use_t=None, **kwargs):
                 raise ValueError('either time or groups needs to be given')
             groupidx = lzip([0] + tt, tt + [nobs_])
             self.n_groups = n_groups = len(groupidx)
-            res.cov_params_default = sw.cov_nw_panel(self, maxlags, groupidx,
-                                                     weights_func=weights_func,
-                                                     use_correction=use_correction)
+            res.cov_params_default = sw.cov_nw_panel(
+                self,
+                maxlags,
+                groupidx,
+                weights_func=weights_func,
+                use_correction=use_correction
+            )
             res.cov_kwds['description'] = descriptions['HAC-Panel']
 
         elif cov_type.lower() == 'hac-groupsum':
@@ -2777,7 +2788,8 @@ def summary(
             diagn_left = diagn_right = []
             top_left = [elem for elem in top_left if elem[0] in slimlist]
             top_right = [elem for elem in top_right if elem[0] in slimlist]
-            top_right = top_right + [("",[])] * (len(top_left) - len(top_right))
+            top_right = top_right + \
+                [("", [])] * (len(top_left) - len(top_right))
         else:
             diagn_left = [('Omnibus:', ["%#6.3f" % omni]),
                           ('Prob(Omnibus):', ["%#6.3f" % omnipv]),
@@ -2893,7 +2905,6 @@ def summary2(
         dw = durbin_watson(self.wresid)
         eigvals = self.eigenvals
         condno = self.condition_number
-        eigvals = np.sort(eigvals)  # in increasing order
         diagnostic = dict([
             ('Omnibus:',  "%.3f" % omni),
             ('Prob(Omnibus):', "%.3f" % omnipv),
@@ -2912,16 +2923,38 @@ def summary2(
                       xname=xname, yname=yname, title=title)
         smry.add_dict(diagnostic)
 
+        etext = []
+
+        if not self.k_constant:
+            etext.append(
+                "R² is computed without centering (uncentered) since the \
+                model does not contain a constant."
+            )
+        if hasattr(self, 'cov_type'):
+            etext.append(self.cov_kwds['description'])
+        if self.model.exog.shape[0] < self.model.exog.shape[1]:
+            wstr = "The input rank is higher than the number of observations."
+            etext.append(wstr)
+
         # Warnings
         if eigvals[-1] < 1e-10:
             warn = "The smallest eigenvalue is %6.3g. This might indicate that\
-            there are strong multicollinearity problems or that the design\
-            matrix is singular." % eigvals[-1]
-            smry.add_text(warn)
-        if condno > 1000:
-            warn = "* The condition number is large (%.g). This might indicate \
-            strong multicollinearity or other numerical problems." % condno
-            smry.add_text(warn)
+                there are strong multicollinearity problems or that the design\
+                matrix is singular." % eigvals[-1]
+            etext.append(warn)
+        elif condno > 1000:
+            warn = "The condition number is large, %6.3g. This might indicate\
+                that there are strong multicollinearity or other numerical\
+                problems." % condno
+            etext.append(warn)
+
+        if etext:
+            etext = ["[{0}] {1}".format(i + 1, text)
+                     for i, text in enumerate(etext)]
+            etext.insert(0, "Notes:")
+
+        for line in etext:
+            smry.add_text(line)
 
         return smry
 


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
@@ -33,6 +33,8 @@ def test_summarycol(self):
 \end{tabular}
 \end{center}
 \end{table}
+\bigskip
+Standard errors in parentheses.
 '''
         x = [1, 5, 7, 3, 5]
         x = add_constant(x)
@@ -135,6 +137,8 @@ def test__repr_latex_(self):
 \end{tabular}
 \end{center}
 \end{table}
+\bigskip
+Standard errors in parentheses.
 '''
         x = [1, 5, 7, 3, 5]
         x = add_constant(x)