Repository: statsmodels/statsmodels

Issue:
BUG?: GLM working residuals
Wanted to document this in a separate issue. 

There is some discussion in #2872 about working residuals. I believe there is an error with our derivation. 

Currently, the code is...

``` python
    def resid_working(self):
        # Isn't self.resid_response is already adjusted by _n_trials?
        val = (self.resid_response / self.family.link.deriv(self.mu))
        val *= self._n_trials
        return val
```

I suggest it really ought to be... 

``` python
    def resid_working(self):
        val = (self.resid_response * self.family.link.deriv(self.mu))
        return val
```

This change breaks some unit tests... @josef-pkt found [this link](http://www.stata.com/manuals14/rglmpostestimation.pdf) (see page 6) which shows we should multiply, not divide, by the derivative. 

(For the record, I don't use working residuals, nor do I plan to start using them. But I feel bad about writing a test in #2872 with an intentionally incorrect result to match what I think is incorrect.) 

Thanks!



Discussion:
skip testing the working residuals. That's what I do in the weights testing PR.
I added the verified results but did not add an `assert` for them, so I can add it later.

The problem is that we need to redo the current unit test results which have coded the wrong numbers. If we just fix the bug, then the unit tests will fail. (all residuals are combined together so it's not so easy just to replace the incorrect working residuals in the test results file.)

Yes sir. Thanks!


Resolving patch:
diff --git a/statsmodels/genmod/generalized_linear_model.py b/statsmodels/genmod/generalized_linear_model.py
--- a/statsmodels/genmod/generalized_linear_model.py
+++ b/statsmodels/genmod/generalized_linear_model.py
@@ -1309,7 +1309,7 @@ def resid_pearson(self):
     @cache_readonly
     def resid_working(self):
         # Isn't self.resid_response is already adjusted by _n_trials?
-        val = (self.resid_response / self.family.link.deriv(self.mu))
+        val = (self.resid_response * self.family.link.deriv(self.mu))
         val *= self._n_trials
         return val
 


Test patch:
diff --git a/statsmodels/genmod/tests/test_glm.py b/statsmodels/genmod/tests/test_glm.py
--- a/statsmodels/genmod/tests/test_glm.py
+++ b/statsmodels/genmod/tests/test_glm.py
@@ -66,10 +66,19 @@ def test_standard_errors(self):
 
     decimal_resids = DECIMAL_4
     def test_residuals(self):
+        # fix incorrect numbers in resid_working results
+        # residuals for Poisson are also tested in test_glm_weights.py
+        import copy
+        # new numpy would have copy method
+        resid2 = copy.copy(self.res2.resids)
+        resid2[:, 2] *= self.res1.family.link.deriv(self.res1.mu)**2
+
+        atol = 10**(-self.decimal_resids)
         resids = np.column_stack((self.res1.resid_pearson,
                 self.res1.resid_deviance, self.res1.resid_working,
                 self.res1.resid_anscombe, self.res1.resid_response))
-        assert_almost_equal(resids, self.res2.resids, self.decimal_resids)
+        assert_allclose(resids, resid2, rtol=1e-6, atol=atol)
+
 
     decimal_aic_R = DECIMAL_4
     def test_aic_R(self):
@@ -1423,13 +1432,13 @@ def test_resid(self):
                         np.concatenate((self.res2.resid_deviance[:17],
                                         [self.res2.resid_deviance[l2]])),
                         rtol=1e-5, atol=1e-5)
-        # Not working either...
-        if not isinstance(self, (TestTweedieLog1, TestTweedieLog15Fair)):
-            assert_allclose(np.concatenate((self.res1.resid_working[:17],
-                                            [self.res1.resid_working[l]])),
-                            np.concatenate((self.res2.resid_working[:17],
-                                            [self.res2.resid_working[l2]])),
-                            rtol=1e-5, atol=1e-5)
+
+        assert_allclose(np.concatenate((self.res1.resid_working[:17],
+                                        [self.res1.resid_working[l]])),
+                        np.concatenate((self.res2.resid_working[:17],
+                                        [self.res2.resid_working[l2]])),
+                        rtol=1e-5, atol=1e-5)
+
 
     def test_bse(self):
         assert_allclose(self.res1.bse, self.res2.bse, atol=1e-6, rtol=1e6)
diff --git a/statsmodels/genmod/tests/test_glm_weights.py b/statsmodels/genmod/tests/test_glm_weights.py
--- a/statsmodels/genmod/tests/test_glm_weights.py
+++ b/statsmodels/genmod/tests/test_glm_weights.py
@@ -50,6 +50,7 @@ def test_residuals(self):
         assert_allclose(res1.resid_pearson, resid_all['resid_pearson'], atol= 1e-6, rtol=2e-6)
         assert_allclose(res1.resid_deviance, resid_all['resid_deviance'], atol= 1e-6, rtol=2e-6)
         assert_allclose(res1.resid_anscombe, resid_all['resid_anscombe'], atol= 1e-6, rtol=2e-6)
+        assert_allclose(res1.resid_working, resid_all['resid_working'], atol= 1e-6, rtol=2e-6)
 
 
 class TestGlmPoissonPlain(CheckWeight):