Repository: statsmodels/statsmodels

Issue:
GLS uncentered_tss and rsquared
see also #1170 for definitions in WLS

"this is probably broken for GLS"

```
    def centered_tss(self):
        model = self.model
        weights = getattr(model, 'weights', None)
        if weights is not None:
            return np.sum(weights*(model.endog - np.average(model.endog,
                                                        weights=weights))**2)
        else: # this is probably broken for GLS
            centered_endog = model.wendog - model.wendog.mean()
            return np.dot(centered_endog, centered_endog)
```

I guess:
`model.wendog.mean()` should be replaced by the y_fitted for a regression on constant only

something like, based on GLS of y on constant:

```
wconst = model.whiten(np.ones(nobs))  # should be wexog[:, const_idx] if available
wfitted_const = wconst * np.dot(wconst, wconst) * np.dot(wconst, wendog)
 centered_endog = model.wendog - wfitted_const
```



Discussion:

Resolving patch:
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
@@ -1520,10 +1520,19 @@ def ssr(self):
     def centered_tss(self):
         model = self.model
         weights = getattr(model, 'weights', None)
+        sigma = getattr(model, 'sigma', None)
         if weights is not None:
-            return np.sum(weights * (
-                model.endog - np.average(model.endog, weights=weights))**2)
-        else:  # this is probably broken for GLS
+            mean = np.average(model.endog, weights=weights)
+            return np.sum(weights * (model.endog - mean)**2)
+        elif sigma is not None:
+            # Exactly matches WLS when sigma is diagonal
+            iota = np.ones_like(model.endog)
+            iota = model.whiten(iota)
+            mean = model.wendog.dot(iota) / iota.dot(iota)
+            err = model.endog - mean
+            err = model.whiten(err)
+            return np.sum(err**2)
+        else:
             centered_endog = model.wendog - model.wendog.mean()
             return np.dot(centered_endog, centered_endog)
 


Test patch:
diff --git a/statsmodels/regression/tests/test_regression.py b/statsmodels/regression/tests/test_regression.py
--- a/statsmodels/regression/tests/test_regression.py
+++ b/statsmodels/regression/tests/test_regression.py
@@ -484,6 +484,7 @@ def test_missing(self):
         assert_equal(mod.exog.shape[0], 13)
         assert_equal(mod.sigma.shape, (13, 13))
 
+
 class TestGLS_alt_sigma(CheckRegressionResults):
     """
     Test that GLS with no argument is equivalent to OLS.
@@ -663,15 +664,6 @@ def setup_class(cls):
         cls.results.append(GLS(y, X, 100 * w_inv).fit())
         cls.results.append(GLS(y, X, np.diag(0.1 * w_inv)).fit())
 
-    def test_rsquared(self):
-        # TODO: WLS rsquared is ok, GLS might have wrong centered_tss
-        # We only check that WLS and GLS rsquared is invariant to scaling
-        # WLS and GLS have different rsquared
-        assert_almost_equal(self.results[1].rsquared, self.results[0].rsquared,
-                            DECIMAL_7)
-        assert_almost_equal(self.results[3].rsquared, self.results[2].rsquared,
-                            DECIMAL_7)
-
 
 class TestNonFit(object):
     @classmethod