Repository: statsmodels/statsmodels

Issue:
BUG NegativeBinomial ignores exposure and offset
I have a failing test with negative binomial and exposure
https://github.com/statsmodels/statsmodels/pull/1610/files#diff-4d8b0a6a9f7f5bf288f18ba71e67ffa6R110

skimming the code: NegativeBinomial takes exposure and offset as argument, but ignores it in most of the code

either raise exception that offset and exposure are not supported or fix it.

BUG NegativeBinomial ignores exposure and offset
I have a failing test with negative binomial and exposure
https://github.com/statsmodels/statsmodels/pull/1610/files#diff-4d8b0a6a9f7f5bf288f18ba71e67ffa6R110

skimming the code: NegativeBinomial takes exposure and offset as argument, but ignores it in most of the code

either raise exception that offset and exposure are not supported or fix it.



Discussion:
My target solution is to through out all linear code and call predict instead. DRYing out some code, instead of adding offset and exposure 10 (?) times.

My target solution is to through out all linear code and call predict instead. DRYing out some code, instead of adding offset and exposure 10 (?) times.


Resolving patch:
diff --git a/statsmodels/discrete/discrete_model.py b/statsmodels/discrete/discrete_model.py
--- a/statsmodels/discrete/discrete_model.py
+++ b/statsmodels/discrete/discrete_model.py
@@ -667,7 +667,6 @@ def _get_init_kwds(self):
         return kwds
 
 
-    #TODO: are these two methods only for Poisson? or also Negative Binomial?
     def predict(self, params, exog=None, exposure=None, offset=None,
                 linear=False):
         """
@@ -1834,7 +1833,7 @@ def __setstate__(self, indict):
 
     def _ll_nbin(self, params, alpha, Q=0):
         endog = self.endog
-        mu = np.exp(np.dot(self.exog, params))
+        mu = self.predict(params)
         size = 1/alpha * mu**Q
         prob = size/(size+mu)
         coeff = (gammaln(size+endog) - gammaln(endog+1) -
@@ -1900,7 +1899,7 @@ def loglike(self, params):
     def _score_geom(self, params):
         exog = self.exog
         y = self.endog[:,None]
-        mu = np.exp(np.dot(exog, params))[:,None]
+        mu = self.predict(params)[:,None]
         dparams = exog * (y-mu)/(mu+1)
         return dparams.sum(0)
 
@@ -1915,7 +1914,7 @@ def _score_nbin(self, params, Q=0):
         params = params[:-1]
         exog = self.exog
         y = self.endog[:,None]
-        mu = np.exp(np.dot(exog, params))[:,None]
+        mu = self.predict(params)[:,None]
         a1 = 1/alpha * mu**Q
         if Q: # nb1
             dparams = exog*mu/alpha*(np.log(1/(alpha + 1)) +
@@ -1947,7 +1946,7 @@ def _score_nb1(self, params):
     def _hessian_geom(self, params):
         exog = self.exog
         y = self.endog[:,None]
-        mu = np.exp(np.dot(exog, params))[:,None]
+        mu = self.predict(params)[:,None]
 
         # for dl/dparams dparams
         dim = exog.shape[1]
@@ -1976,7 +1975,7 @@ def _hessian_nb1(self, params):
         params = params[:-1]
         exog = self.exog
         y = self.endog[:,None]
-        mu = np.exp(np.dot(exog, params))[:,None]
+        mu = self.predict(params)[:,None]
 
         a1 = mu/alpha
 
@@ -2043,7 +2042,7 @@ def _hessian_nb2(self, params):
 
         exog = self.exog
         y = self.endog[:,None]
-        mu = np.exp(np.dot(exog, params))[:,None]
+        mu = self.predict(params)[:,None]
 
         # for dl/dparams dparams
         dim = exog.shape[1]


Test patch:
diff --git a/statsmodels/discrete/tests/test_sandwich_cov.py b/statsmodels/discrete/tests/test_sandwich_cov.py
--- a/statsmodels/discrete/tests/test_sandwich_cov.py
+++ b/statsmodels/discrete/tests/test_sandwich_cov.py
@@ -107,8 +107,7 @@ def setup_class(cls):
         cls.get_robust_clu()
 
 
-# TODO: check what's going on with the params
-class T_estNegbinCluExposure(CheckCountRobustMixin):
+class TestNegbinCluExposure(CheckCountRobustMixin):
 
     @classmethod
     def setup_class(cls):