Repository: statsmodels/statsmodels

Issue:
BUG NegativeBinomial: start_params ignores offset and exposure
The example from the mailing list raises exception (Singular Matrix) in Poisson without offset or exposure.
NegativeBinomial.fit uses Poisson for start_params but doesn't use offset and exposure
same problem in fit_regularized

```

from statsmodels.compat import StringIO

titanicgrpcsv = StringIO('''\
"","survive","cases","age","sex","whichclass"
"1",1,1,0,0,1
"2",13,13,0,0,2
"3",14,31,0,0,3
"4",5,5,0,1,1
"5",11,11,0,1,2
"6",13,48,0,1,3
"7",140,144,1,0,1
"8",80,93,1,0,2
"9",76,165,1,0,3
"10",57,175,1,1,1
"11",14,168,1,1,2
"12",75,462,1,1,3
''')


import statsmodels.formula.api as smf
import statsmodels.api as sm
import pandas as pd, numpy as np
#df = pd.read_csv("titanicgrp.csv",sep=',',index_col=0)
df = pd.read_csv(titanicgrpcsv,sep=',',index_col=0)
df['lncases'] = df['cases'].map(lambda x:np.log(x))
model=smf.glm("survive ~ age + sex + C(whichclass)", data=df, offset=df['lncases'],
             family=sm.families.NegativeBinomial()).fit()
print(model.summary()) 

mod_nb = smf.negativebinomial("survive ~ age + sex + C(whichclass)", 
                              data=df, offset=df['lncases'])
# There is a bug in start_params with offset exposure, 
#res_nb = mod_nb.fit(method='lbfgs', start_params=np.concatenate((model.params, [0.5])))
res_nb = mod_nb.fit(start_params=np.concatenate((model.params, [0.5])))                              
print(res_nb.summary())
print(mod_nb.fit().summary())

```



Discussion:

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
@@ -2269,7 +2269,12 @@ def fit(self, start_params=None, method='bfgs', maxiter=35,
 
         if start_params is None:
             # Use poisson fit as first guess.
-            start_params = Poisson(self.endog, self.exog).fit(disp=0).params
+            #TODO, Warning: this assumes exposure is logged
+            offset = getattr(self, "offset", 0) + getattr(self, "exposure", 0)
+            if np.size(offset) == 1 and offset == 0:
+                offset = None
+            mod_poi = Poisson(self.endog, self.exog, offset=offset)
+            start_params = mod_poi.fit(disp=0).params
             if self.loglike_method.startswith('nb'):
                 start_params = np.append(start_params, 0.1)
         mlefit = super(NegativeBinomial, self).fit(start_params=start_params,
@@ -2304,7 +2309,8 @@ def fit_regularized(self, start_params=None, method='l1',
         if self.loglike_method.startswith('nb') and (np.size(alpha) == 1 and
                                                      alpha != 0):
             # don't penalize alpha if alpha is scalar
-            alpha = alpha * np.ones(len(start_params))
+            k_params = self.exog.shape[1] + self.k_extra
+            alpha = alpha * np.ones(k_params)
             alpha[-1] = 0
 
         # alpha for regularized poisson to get starting values
@@ -2313,7 +2319,12 @@ def fit_regularized(self, start_params=None, method='l1',
         self._transparams = False
         if start_params is None:
             # Use poisson fit as first guess.
-            start_params = Poisson(self.endog, self.exog).fit_regularized(
+            #TODO, Warning: this assumes exposure is logged
+            offset = getattr(self, "offset", 0) + getattr(self, "exposure", 0)
+            if np.size(offset) == 1 and offset == 0:
+                offset = None
+            mod_poi = Poisson(self.endog, self.exog, offset=offset)
+            start_params = mod_poi.fit_regularized(
                 start_params=start_params, method=method, maxiter=maxiter,
                 full_output=full_output, disp=0, callback=callback,
                 alpha=alpha_p, trim_mode=trim_mode, auto_trim_tol=auto_trim_tol,


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
@@ -60,7 +60,7 @@ def test_basic(self):
 
         assert_allclose(res1._results.params, res2_params, 1e-4)
 
-        assert_allclose(self.bse_rob / self.corr_fact, res2_bse, 1e-5)
+        assert_allclose(self.bse_rob / self.corr_fact, res2_bse, 6e-5)
 
     @classmethod
     def get_robust_clu(cls):