Repository: statsmodels/statsmodels

Issue:
Discrete NegativeBinomialModel regularized_fit ValueError: matrices are not aligned
This code returns:
ValueError: matrices are not aligned
The params array is always one element too short.

```
import numpy as np
import statsmodels.api as sm
sm.version.full_version
sm.show_versions()
rand_data = sm.datasets.randhie.load()
rand_exog = rand_data.exog.view(float).reshape(len(rand_data.exog), -1)
rand_exog = sm.add_constant(rand_exog, prepend=False)

mod_nbin = sm.NegativeBinomial(rand_data.endog, rand_exog)
res_nbin = mod_nbin.fit_regularized(disp=False)
print res_nbin.summary()

Output:
INSTALLED VERSIONS
------------------
Python: 2.7.6.final.0
OS: Linux 2.6.32-042stab076.8 #1 SMP Tue May 14 20:38:14 MSK 2013 x86_64
byteorder: little
LC_ALL: None
LANG: None

Statsmodels
===========

Installed: 0.5.0 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels)

Required Dependencies
=====================

cython: 0.20 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/Cython)
numpy: 1.8.0 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/numpy)
scipy: 0.13.3 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/scipy)
pandas: 0.13.1 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/pandas)
    dateutil: 1.5 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/dateutil)
patsy: 0.2.1 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/patsy)

Optional Dependencies
=====================

matplotlib: 1.3.1 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/matplotlib)
cvxopt: Not installed

Developer Tools
================

IPython: 2.0.0-dev (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/IPython)
    jinja2: 2.7.2 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/jinja2)
sphinx: 1.2.1 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/sphinx)
    pygments: 1.6 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/pygments)
nose: 1.3.0 (/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/nose)
virtualenv: Not installed


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-5c06d718e7a6> in <module>()
      8 
      9 mod_nbin = sm.NegativeBinomial(rand_data.endog, rand_exog)
---> 10 res_nbin = mod_nbin.fit_regularized(disp=False)
     11 print res_nbin.summary()

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/discrete/discrete_model.pyc in fit_regularized(self, start_params, method, maxiter, full_output, disp, callback, alpha, trim_mode, auto_trim_tol, size_trim_tol, qc_tol, **kwargs)
    754                 full_output=full_output, disp=disp, callback=callback,
    755                 alpha=alpha, trim_mode=trim_mode, auto_trim_tol=auto_trim_tol,
--> 756                 size_trim_tol=size_trim_tol, qc_tol=qc_tol, **kwargs)
    757         if method in ['l1', 'l1_cvxopt_cp']:
    758             discretefit = L1CountResults(self, cntfit)

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/discrete/discrete_model.pyc in fit_regularized(self, start_params, method, maxiter, full_output, disp, callback, alpha, trim_mode, auto_trim_tol, size_trim_tol, qc_tol, qc_verbose, **kwargs)
    310                 method=method, maxiter=maxiter, full_output=full_output,
    311                 disp=disp, callback=callback, extra_fit_funcs=extra_fit_funcs,
--> 312                 cov_params_func=cov_params_func, **kwargs)
    313 
    314         return mlefit # up to subclasses to wrap results

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/base/model.pyc in fit(self, start_params, method, maxiter, full_output, disp, fargs, callback, retall, **kwargs)
    355                              disp=disp, maxiter=maxiter, callback=callback,
    356                              retall=retall, full_output=full_output,
--> 357                              hess=hess)
    358 
    359         if not full_output: # xopt should be None and retvals is argmin

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/base/l1_slsqp.pyc in fit_l1_slsqp(f, score, start_params, args, kwargs, disp, maxiter, callback, retall, full_output, hess)
     77         func, x0, f_ieqcons=f_ieqcons_wrap, fprime=fprime_wrap, acc=acc,
     78         iter=maxiter, disp=disp_slsqp, full_output=full_output,
---> 79         fprime_ieqcons=fprime_ieqcons_wrap)
     80     params = np.asarray(results[0][:k_params])
     81 

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/scipy/optimize/slsqp.pyc in fmin_slsqp(func, x0, eqcons, f_eqcons, ieqcons, f_ieqcons, bounds, fprime, fprime_eqcons, fprime_ieqcons, args, iter, acc, iprint, disp, full_output, epsilon)
    198 
    199     res = _minimize_slsqp(func, x0, args, jac=fprime, bounds=bounds,
--> 200                           constraints=cons, **opts)
    201     if full_output:
    202         return res['x'], res['fun'], res['nit'], res['status'], res['message']

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/scipy/optimize/slsqp.pyc in _minimize_slsqp(func, x0, args, jac, bounds, constraints, maxiter, ftol, iprint, disp, eps, **unknown_options)
    352 
    353             # Compute objective function
--> 354             fx = func(x)
    355             # Compute the constraints
    356             if cons['eq']:

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/scipy/optimize/optimize.pyc in function_wrapper(*wrapper_args)
    279     def function_wrapper(*wrapper_args):
    280         ncalls[0] += 1
--> 281         return function(*(wrapper_args + args))
    282 
    283     return ncalls, function_wrapper

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/base/l1_slsqp.pyc in <lambda>(x_full)
     68 
     69     ### Wrap up for use in fmin_slsqp
---> 70     func = lambda x_full: _objective_func(f, x_full, k_params, alpha, *args)
     71     f_ieqcons_wrap = lambda x_full: _f_ieqcons(x_full, k_params)
     72     fprime_wrap = lambda x_full: _fprime(score, x_full, k_params, alpha)

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/base/l1_slsqp.pyc in _objective_func(f, x_full, k_params, alpha, *args)
    133     x_added = x_full[k_params:]
    134     ## Return
--> 135     return f(x_params, *args) + (alpha * x_added).sum()
    136 
    137 

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/base/model.pyc in <lambda>(params, *args)
    327 
    328         nobs = self.endog.shape[0]
--> 329         f = lambda params, *args: -self.loglike(params, *args) / nobs
    330         score = lambda params: -self.score(params) / nobs
    331         try:

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/discrete/discrete_model.pyc in loglike(self, params)
   1840 
   1841         """
-> 1842         llf = np.sum(self.loglikeobs(params))
   1843         return llf
   1844 

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/discrete/discrete_model.pyc in _ll_nb2(self, params)
   1793         else:
   1794             alpha = params[-1]
-> 1795         return self._ll_nbin(params[:-1], alpha, Q=0)
   1796 
   1797     def _ll_nb1(self, params):

/opt/anaconda/envs/np18py27-1.9/lib/python2.7/site-packages/statsmodels/discrete/discrete_model.pyc in _ll_nbin(self, params, alpha, Q)
   1780     def _ll_nbin(self, params, alpha, Q=0):
   1781         endog = self.endog
-> 1782         mu = np.exp(np.dot(self.exog, params))
   1783         size = 1/alpha * mu**Q
   1784         prob = size/(size+mu)

ValueError: matrices are not aligned
```

REF DRYing fit methods
It looks like we are adding or copying many fit functions just because they need to choose the results class. In some cases they also have special code to calculate start_params.

Two possibilities
- register return class in a module global, as alternative it could be **another** attribute
- add method for start_params()

similar use case: 
currently it's not possible to subclass the results class without also subclasses the model and overwriting fit, 
example: least squares (WLS) with predefined scale, scipy curve_fit discussion, and Stata's `vwls` which would need to redefine results.scale 
(I just realized that I could pick this up as option for `cov_type`)

(while looking at the missing NegativeBinomial.fit_regularized #1454)



Discussion:
Thanks for reporting, I get the same

first guess: start_params have the wrong length

If I use an explicit start_params, as in the following, I don't get any error

```
mod_nbin = sm.NegativeBinomial(rand_data.endog, rand_exog)
res = mod_nbin.fit(disp=False)
res_nbin = mod_nbin.fit_regularized(start_params=res.params, disp=False)
print res_nbin.summary()
```

however, the params with fit and fit_regularized lead to the essentially the same parameters

```
>>> res.params
array([-0.05795413, -0.26781396,  0.04121061, -0.03814017,  0.26895441,
        0.03816294, -0.04411766,  0.01724125,  0.17800361,  0.66356013,
        1.29295351])
>>> res_nbin.params
array([-0.05795374, -0.26781391,  0.04121068, -0.03813948,  0.26895435,
        0.03816315, -0.04411768,  0.01724126,  0.1780036 ,  0.66356014,
        1.29295351])
```

the unregularized `fit` says converged=False in the summary
regularized fit has converged=True

Is this correct?
need to try out the fit_regularized options

I don't remember what qc check is.

Below I get nans in gopt and hopt. why?

```
>>> res_nbin2 = mod_nbin.fit_regularized(start_params=res.params,alpha=0.25*amask, qc_verbose=True, retall=True)
  NIT    FC           OBJFUN            GNORM
    1     1     2.148790E+00     5.345830E-05
Optimization terminated successfully.    (Exit mode 0)
            Current function value: 2.1487897904
            Iterations: 1
            Function evaluations: 3
            Gradient evaluations: 1
QC check did not pass for 3 out of 11 parameters
Try increasing solver accuracy or number of iterations, decreasing alpha, or switch solvers
------ Recall the problem was rescaled by 1 / nobs ---
|passed    |alpha     |fprime    |param     |
--------------------------------------------
|True      |1.238e-05 |8.252e-06 |-5.795e-02|
|True      |1.238e-05 |1.305e-07 |-2.678e-01|
|False     |1.238e-05 |4.232e-05 |4.121e-02 |
|False     |1.238e-05 |2.823e-05 |-3.814e-02|
|True      |1.238e-05 |3.439e-06 |2.690e-01 |
|False     |1.238e-05 |8.643e-05 |3.816e-02 |
|True      |1.238e-05 |4.983e-06 |-4.412e-02|
|True      |1.238e-05 |8.597e-08 |1.724e-02 |
|True      |1.238e-05 |6.610e-07 |1.780e-01 |
|True      |1.238e-05 |7.236e-06 |6.636e-01 |
|True      |0.000e+00 |1.472e-08 |1.293e+00 |
Could not trim params automatically due to failed QC check.  Trimming using trim_mode == 'size' will still work.
>>> res_nbin2.mle_retvals
{'fopt': 2.1487897904014321, 'gopt': nan, 'hopt': nan, 'trimmed': array([False, False, False, False, False, False, False, False, False,
       False, False], dtype=bool), 'iterations': 1, 'converged': 'True'}
```

something looks weird, score doesn't look close to zero for all versions of fit that I tried, e.g. for regularized

```
>>> res_nbin.model.score(res_nbin.params)
array([-0.08566124, -0.00530527, -0.07763577, -0.10601139, -0.02636937,
       -0.01563745, -0.01031334, -0.01058   , -0.00927419, -0.01385042,
        0.02808423])
```

aside: I think we shouldn't penalize the NegativeBinomial shape parameter in the same way as the coefficients for the linear part, e.g.

```
>>> amask = np.ones(len(res.params))
>>> amask[-1] = 0
>>> res_nbin2 = mod_nbin.fit_regularized(start_params=res.params, alpha=0.25*amask)
```

using "nb1" works, but it looks difficult to figure out what alpha should be

```
>>> res_nbin3 = mod_nbin.fit_regularized(start_params=res.params,alpha=100*amask, qc_verbose=True, retall=True)
  NIT    FC           OBJFUN            GNORM
    1     1     2.151538E+00     1.566259E-02
    2     3     2.151532E+00     5.249264E-02
    3     5     2.151495E+00     4.557341E-02
    4     7     2.151429E+00     3.590288E-02
    5     8     2.151119E+00     3.016578E-02
    6     9     2.150752E+00     2.210603E-02
    7    10     2.150661E+00     2.018570E-02
    8    11     2.150639E+00     2.084313E-02
    9    12     2.150569E+00     2.570237E-02
   10    13     2.150544E+00     2.481202E-02
   11    14     2.150518E+00     2.287230E-02
   12    15     2.150501E+00     2.261447E-02
   13    16     2.150468E+00     2.281161E-02
   14    17     2.150457E+00     2.157790E-02
   15    18     2.150453E+00     2.095163E-02
   16    19     2.150452E+00     2.112453E-02
   17    20     2.150452E+00     2.123148E-02
   18    21     2.150452E+00     2.134927E-02
   19    22     2.150451E+00     2.134492E-02
   20    23     2.150451E+00     2.122605E-02
   21    24     2.150451E+00     2.114686E-02
   22    25     2.150451E+00     2.110636E-02
   23    26     2.150451E+00     2.106363E-02
   24    27     2.150451E+00     2.103672E-02
   25    28     2.150451E+00     2.104644E-02
   26    29     2.150451E+00     2.109452E-02
   27    30     2.150451E+00     2.113491E-02
   28    31     2.150451E+00     2.115827E-02
   29    32     2.150451E+00     2.118305E-02
   30    33     2.150450E+00     2.118928E-02
   31    34     2.150450E+00     2.116706E-02
   32    35     2.150450E+00     2.114533E-02
   33    36     2.150450E+00     2.113928E-02
Optimization terminated successfully.    (Exit mode 0)
            Current function value: 2.15045044517
            Iterations: 33
            Function evaluations: 36
            Gradient evaluations: 33
>>> print res_nbin3.summary()
                     NegativeBinomial Regression Results                      
==============================================================================
Dep. Variable:                      y   No. Observations:                20190
Model:               NegativeBinomial   Df Residuals:                    20182
Method:                           MLE   Df Model:                            7
Date:                Sat, 08 Mar 2014   Pseudo R-squ.:                 0.02053
Time:                        16:34:52   Log-Likelihood:                -43292.
converged:                       True   LL-Null:                       -44199.
                                        LLR p-value:                     0.000
==============================================================================
                 coef    std err          z      P>|z|      [95.0% Conf. Int.]
------------------------------------------------------------------------------
x1            -0.0603      0.005    -11.471      0.000        -0.071    -0.050
x2            -0.2536      0.019    -13.073      0.000        -0.292    -0.216
x3             0.0422      0.003     12.466      0.000         0.036     0.049
x4            -0.0325      0.003    -10.945      0.000        -0.038    -0.027
x5             0.1367      0.024      5.811      0.000         0.091     0.183
x6             0.0339      0.001     32.318      0.000         0.032     0.036
x7                  0        nan        nan        nan           nan       nan
x8                  0        nan        nan        nan           nan       nan
x9                  0        nan        nan        nan           nan       nan
const          0.6978      0.021     33.228      0.000         0.657     0.739
alpha          3.7085      0.062     59.518      0.000         3.586     3.831
==============================================================================
```

which looks pretty good compared to alpha=0, `res_nbin`, 
the least significant params are set to zero, the most of the others don't change much

```
>>> res_nbin3.params / res_nbin.params
array([ 0.92337179,  0.8565406 ,  1.02416303,  1.01404461,  0.71715496,
        1.06321644, -0.        , -0.        ,  0.        ,  0.92140039,
        0.99392089])

>>> res_nbin.pvalues
array([  3.73543154e-034,   4.95150690e-051,   1.39309038e-034,
         2.04536907e-026,   1.13813373e-015,   1.55134219e-193,
         4.66928683e-002,   1.03639840e-001,   4.08960285e-002,
         2.03136069e-272,   0.00000000e+000])
```

maybe everything is fine except for the starting value, `nb2` also looks good if alpha is large enough

```
mod_nbin = sm.NegativeBinomial(rand_data.endog, rand_exog, loglike_method='nb2')
res = mod_nbin.fit(disp=False, method='bfgs')
res_nbin = mod_nbin.fit_regularized(start_params=res.params, disp=False)
print res_nbin.summary()

res_nbin3 = mod_nbin.fit_regularized(start_params=res.params,alpha=100*amask, qc_verbose=True, retall=True)
print res_nbin3.summary()
```

score at optimum is large in absolute terms, but not in relative terms

```
>>> res_nbin.model.score(res_nbin.params)
array([  1.35411669e-01,   2.11502368e-02,  -2.83535542e-01,
         1.39332197e-01,  -5.81716604e-02,  -6.38596202e-01,
        -6.46757111e-02,   5.31774935e-03,  -1.22772331e-02,
        -5.08886427e-02,  -1.03765058e-04])

>>> res_nbin.model.score(res_nbin.params) / res_nbin.llf
array([ -3.12125953e-06,  -4.87516170e-07,   6.53553731e-06,
        -3.21162830e-06,   1.34086561e-06,   1.47197394e-05,
         1.49078496e-06,  -1.22574930e-07,   2.82992087e-07,
         1.17299094e-06,   2.39180035e-09])
```

IIRC, we used this dataset for the Poisson test case, and it was the reason to normalize loglike by nobs in the optimization, because of the large number of observations and large llf.sum() instead of llf.mean()



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
@@ -223,6 +223,9 @@ def fit_regularized(self, start_params=None, method='l1',
 
         Notes
         -----
+        Extra parameters are not penalized if alpha is given as a scalar.
+        An example is the shape parameter in NegativeBinomial `nb1` and `nb2`.
+
         Optional arguments for the solvers (available in Results.mle_settings)::
 
             'l1'
@@ -431,7 +434,7 @@ def _derivative_exog(self, params, exog=None, transform='dydx',
         """
         #note, this form should be appropriate for
         ## group 1 probit, logit, logistic, cloglog, heckprob, xtprobit
-        if exog == None:
+        if exog is None:
             exog = self.exog
         margeff = np.dot(self.pdf(np.dot(exog, params))[:,None],
                                                           params[None,:])
@@ -605,7 +608,7 @@ def _derivative_exog(self, params, exog=None, transform='dydx',
         K = int(self.K) # number of variables
         #note, this form should be appropriate for
         ## group 1 probit, logit, logistic, cloglog, heckprob, xtprobit
-        if exog == None:
+        if exog is None:
             exog = self.exog
         if params.ndim == 1: # will get flatted from approx_fprime
             params = params.reshape(K, J-1, order='F')
@@ -729,7 +732,7 @@ def _derivative_exog(self, params, exog=None, transform="dydx",
         but checks are done in the results in get_margeff.
         """
         # group 3 poisson, nbreg, zip, zinb
-        if exog == None:
+        if exog is None:
             exog = self.exog
         margeff = self.predict(params, exog)[:,None] * params[None,:]
         if 'ex' in transform:
@@ -1878,6 +1881,9 @@ def __init__(self, endog, exog, loglike_method='nb2', offset=None,
         self._initialize()
         if loglike_method in ['nb2', 'nb1']:
             self.exog_names.append('alpha')
+            self.k_extra = 1
+        else:
+            self.k_extra = 0
         # store keys for extras if we need to recreate model instance
         # we need to append keys that don't go to super
         self._init_keys.append('loglike_method')
@@ -2170,7 +2176,7 @@ def fit(self, start_params=None, method='bfgs', maxiter=35,
         elif self.loglike_method.startswith('nb'): # method is newton/ncg
             self._transparams = False # because we need to step in alpha space
 
-        if start_params == None:
+        if start_params is None:
             # Use poisson fit as first guess.
             start_params = Poisson(self.endog, self.exog).fit(disp=0).params
             if self.loglike_method.startswith('nb'):
@@ -2187,11 +2193,51 @@ def fit(self, start_params=None, method='bfgs', maxiter=35,
             if method not in ["newton", "ncg"]:
                 mlefit._results.params[-1] = np.exp(mlefit._results.params[-1])
 
-            nbinfit = NegativeBinomialAncillaryResults(self, mlefit._results)
-            return NegativeBinomialAncillaryResultsWrapper(nbinfit)
+            nbinfit = NegativeBinomialResults(self, mlefit._results)
+            return NegativeBinomialResultsWrapper(nbinfit)
         else:
             return mlefit
 
+
+    def fit_regularized(self, start_params=None, method='l1',
+            maxiter='defined_by_method', full_output=1, disp=1, callback=None,
+            alpha=0, trim_mode='auto', auto_trim_tol=0.01, size_trim_tol=1e-4,
+            qc_tol=0.03, **kwargs):
+
+        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))
+            alpha[-1] = 0
+
+        # alpha for regularized poisson to get starting values
+        alpha_p = alpha[:-1] if (self.k_extra and np.size(alpha) > 1) else alpha
+
+        self._transparams = False
+        if start_params is None:
+            # Use poisson fit as first guess.
+            start_params = Poisson(self.endog, self.exog).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,
+                size_trim_tol=size_trim_tol, qc_tol=qc_tol, **kwargs).params
+            if self.loglike_method.startswith('nb'):
+                start_params = np.append(start_params, 0.1)
+
+        cntfit = super(CountModel, self).fit_regularized(
+                start_params=start_params, method=method, maxiter=maxiter,
+                full_output=full_output, disp=disp, callback=callback,
+                alpha=alpha, trim_mode=trim_mode, auto_trim_tol=auto_trim_tol,
+                size_trim_tol=size_trim_tol, qc_tol=qc_tol, **kwargs)
+        if method in ['l1', 'l1_cvxopt_cp']:
+            discretefit = L1NegativeBinomialResults(self, cntfit)
+        else:
+            raise Exception(
+                    "argument method == %s, which is not handled" % method)
+
+        return L1NegativeBinomialResultsWrapper(discretefit)
+
+
 ### Results Class ###
 
 class DiscreteResults(base.LikelihoodModelResults):
@@ -2470,12 +2516,10 @@ def resid(self):
         """
         return self.model.endog - self.predict()
 
-class NegativeBinomialAncillaryResults(CountResults):
+class NegativeBinomialResults(CountResults):
     __doc__ = _discrete_results_docs % {
         "one_line_description" : "A results class for NegativeBinomial 1 and 2",
                     "extra_attr" : ""}
-    def __init__(self, model, mlefit):
-        super(NegativeBinomialAncillaryResults, self).__init__(model, mlefit)
 
     @cache_readonly
     def lnalpha(self):
@@ -2488,13 +2532,15 @@ def lnalpha_std_err(self):
     @cache_readonly
     def aic(self):
         # + 1 because we estimate alpha
-        return -2*(self.llf - (self.df_model + self.k_constant + 1))
+        k_extra = getattr(self.model, 'k_extra', 0)
+        return -2*(self.llf - (self.df_model + self.k_constant + k_extra))
 
     @cache_readonly
     def bic(self):
         # + 1 because we estimate alpha
+        k_extra = getattr(self.model, 'k_extra', 0)
         return -2*self.llf + np.log(self.nobs)*(self.df_model +
-                                                self.k_constant + 1)
+                                                self.k_constant + k_extra)
 
 class L1CountResults(DiscreteResults):
     __doc__ = _discrete_results_docs % {"one_line_description" :
@@ -2508,9 +2554,14 @@ def __init__(self, model, cntfit):
         # entry in params has been set zero'd out.
         self.trimmed = cntfit.mle_retvals['trimmed']
         self.nnz_params = (self.trimmed == False).sum()
-        #update degrees of freedom
+        # update degrees of freedom
         self.model.df_model = self.nnz_params - 1
         self.model.df_resid = float(self.model.endog.shape[0] - self.nnz_params)
+        # adjust for extra parameter in NegativeBinomial nb1 and nb2
+        # extra parameter is not included in df_model
+        k_extra = getattr(self.model, 'k_extra', 0)
+        self.model.df_model -= k_extra
+        self.model.df_resid += k_extra
         self.df_model = self.model.df_model
         self.df_resid = self.model.df_resid
 
@@ -2547,6 +2598,9 @@ def predict_prob(self, n=None, exog=None, exposure=None, offset=None,
 class L1PoissonResults(L1CountResults, PoissonResults):
     pass
 
+class L1NegativeBinomialResults(L1CountResults, NegativeBinomialResults):
+    pass
+
 class OrderedResults(DiscreteResults):
     __doc__ = _discrete_results_docs % {"one_line_description" : "A results class for ordered discrete data." , "extra_attr" : ""}
     pass
@@ -2906,10 +2960,10 @@ class CountResultsWrapper(lm.RegressionResultsWrapper):
     pass
 wrap.populate_wrapper(CountResultsWrapper, CountResults)
 
-class NegativeBinomialAncillaryResultsWrapper(lm.RegressionResultsWrapper):
+class NegativeBinomialResultsWrapper(lm.RegressionResultsWrapper):
     pass
-wrap.populate_wrapper(NegativeBinomialAncillaryResultsWrapper,
-                      NegativeBinomialAncillaryResults)
+wrap.populate_wrapper(NegativeBinomialResultsWrapper,
+                      NegativeBinomialResults)
 
 class PoissonResultsWrapper(lm.RegressionResultsWrapper):
     pass
@@ -2934,6 +2988,11 @@ class L1PoissonResultsWrapper(lm.RegressionResultsWrapper):
     #                            _methods)
 wrap.populate_wrapper(L1PoissonResultsWrapper, L1PoissonResults)
 
+class L1NegativeBinomialResultsWrapper(lm.RegressionResultsWrapper):
+    pass
+wrap.populate_wrapper(L1NegativeBinomialResultsWrapper,
+                      L1NegativeBinomialResults)
+
 class BinaryResultsWrapper(lm.RegressionResultsWrapper):
     _attrs = {"resid_dev" : "rows",
               "resid_generalized" : "rows",


Test patch:
diff --git a/statsmodels/discrete/tests/results/results_discrete.py b/statsmodels/discrete/tests/results/results_discrete.py
--- a/statsmodels/discrete/tests/results/results_discrete.py
+++ b/statsmodels/discrete/tests/results/results_discrete.py
@@ -937,8 +937,8 @@ def negativebinomial_geometric_bfgs(self):
         self.z = [-10.41567024, -12.92281571, 10.8755779 , -12.27364916,
                 9.95160202,  28.76323587,  -2.36514487,   0.59040434,
                 2.70792943,  29.54148082]
-        self.aic = 87101.160011780419
-        self.bic = 87180.289438893495
+        self.aic = 87101.159433012392 # old value 87101.160011780419
+        self.bic = 87180.288860125467 # old value 87180.289438893495
         self.df_model = 9.0
         self.df_resid = 20180.0
         self.llf = -43540.58000589021
diff --git a/statsmodels/discrete/tests/test_discrete.py b/statsmodels/discrete/tests/test_discrete.py
--- a/statsmodels/discrete/tests/test_discrete.py
+++ b/statsmodels/discrete/tests/test_discrete.py
@@ -540,15 +540,18 @@ class CheckL1Compatability(object):
     def test_params(self):
         m = self.m
         assert_almost_equal(
-            self.res_unreg.params, self.res_reg.params[:m], DECIMAL_4)
+            self.res_unreg.params[:m], self.res_reg.params[:m], DECIMAL_4)
         # The last entry should be close to zero
-        assert_almost_equal(0, self.res_reg.params[m:], DECIMAL_4)
+        # handle extra parameter of NegativeBinomial
+        kvars = self.res_reg.model.exog.shape[1]
+        assert_almost_equal(0, self.res_reg.params[m:kvars], DECIMAL_4)
 
     def test_cov_params(self):
         m = self.m
         # The restricted cov_params should be equal
         assert_almost_equal(
-            self.res_unreg.cov_params(), self.res_reg.cov_params()[:m, :m],
+            self.res_unreg.cov_params()[:m, :m],
+            self.res_reg.cov_params()[:m, :m],
             DECIMAL_1)
 
     def test_df(self):
@@ -558,20 +561,24 @@ def test_df(self):
     def test_t_test(self):
         m = self.m
         kvars = self.kvars
-        t_unreg = self.res_unreg.t_test(np.eye(m))
-        t_reg = self.res_reg.t_test(np.eye(kvars))
-        assert_almost_equal(t_unreg.effect, t_reg.effect[:m], DECIMAL_3)
-        assert_almost_equal(t_unreg.sd, t_reg.sd[:m], DECIMAL_3)
+        # handle extra parameter of NegativeBinomial
+        extra = getattr(self, 'k_extra', 0)
+        t_unreg = self.res_unreg.t_test(np.eye(len(self.res_unreg.params)))
+        t_reg = self.res_reg.t_test(np.eye(kvars + extra))
+        assert_almost_equal(t_unreg.effect[:m], t_reg.effect[:m], DECIMAL_3)
+        assert_almost_equal(t_unreg.sd[:m], t_reg.sd[:m], DECIMAL_3)
         assert_almost_equal(np.nan, t_reg.sd[m])
-        assert_almost_equal(t_unreg.tvalue, t_reg.tvalue[:m], DECIMAL_3)
+        assert_allclose(t_unreg.tvalue[:m], t_reg.tvalue[:m], atol=3e-3)
         assert_almost_equal(np.nan, t_reg.tvalue[m])
 
     def test_f_test(self):
         m = self.m
         kvars = self.kvars
-        f_unreg = self.res_unreg.f_test(np.eye(m))
-        f_reg = self.res_reg.f_test(np.eye(kvars)[:m])
-        assert_almost_equal(f_unreg.fvalue, f_reg.fvalue, DECIMAL_2)
+        # handle extra parameter of NegativeBinomial
+        extra = getattr(self, 'k_extra', 0)
+        f_unreg = self.res_unreg.f_test(np.eye(len(self.res_unreg.params))[:m])
+        f_reg = self.res_reg.f_test(np.eye(kvars + extra)[:m])
+        assert_allclose(f_unreg.fvalue, f_reg.fvalue, rtol=3e-5, atol=1e-3)
         assert_almost_equal(f_unreg.pvalue, f_reg.pvalue, DECIMAL_3)
 
     def test_bad_r_matrix(self):
@@ -589,8 +596,8 @@ def setupClass(cls):
         rand_exog = sm.add_constant(rand_exog, prepend=True)
         # Drop some columns and do an unregularized fit
         exog_no_PSI = rand_exog[:, :cls.m]
-        cls.res_unreg = sm.Poisson(
-            rand_data.endog, exog_no_PSI).fit(method="newton", disp=False)
+        mod_unreg = sm.Poisson(rand_data.endog, exog_no_PSI)
+        cls.res_unreg = mod_unreg.fit(method="newton", disp=False)
         # Do a regularized fit with alpha, effectively dropping the last column
         alpha = 10 * len(rand_data.endog) * np.ones(cls.kvars)
         alpha[:cls.m] = 0
@@ -599,6 +606,56 @@ def setupClass(cls):
             trim_mode='auto')
 
 
+class TestNegativeBinomialL1Compatability(CheckL1Compatability):
+    @classmethod
+    def setupClass(cls):
+        cls.kvars = 10 # Number of variables
+        cls.m = 7 # Number of unregularized parameters
+        rand_data = sm.datasets.randhie.load()
+        rand_exog = rand_data.exog.view(float).reshape(len(rand_data.exog), -1)
+        rand_exog_st = (rand_exog - rand_exog.mean(0)) / rand_exog.std(0)
+        rand_exog = sm.add_constant(rand_exog_st, prepend=True)
+        # Drop some columns and do an unregularized fit
+        exog_no_PSI = rand_exog[:, :cls.m]
+        mod_unreg = sm.NegativeBinomial(rand_data.endog, exog_no_PSI)
+        cls.res_unreg = mod_unreg.fit(method="newton", disp=False)
+        # Do a regularized fit with alpha, effectively dropping the last column
+        alpha = 10 * len(rand_data.endog) * np.ones(cls.kvars + 1)
+        alpha[:cls.m] = 0
+        alpha[-1] = 0  # don't penalize alpha
+
+        mod_reg = sm.NegativeBinomial(rand_data.endog, rand_exog)
+        cls.res_reg = mod_reg.fit_regularized(
+            method='l1', alpha=alpha, disp=False, acc=1e-10, maxiter=2000,
+            trim_mode='auto')
+        cls.k_extra = 1  # 1 extra parameter in nb2
+
+
+class TestNegativeBinomialGeoL1Compatability(CheckL1Compatability):
+    @classmethod
+    def setupClass(cls):
+        cls.kvars = 10 # Number of variables
+        cls.m = 7 # Number of unregularized parameters
+        rand_data = sm.datasets.randhie.load()
+        rand_exog = rand_data.exog.view(float).reshape(len(rand_data.exog), -1)
+        rand_exog = sm.add_constant(rand_exog, prepend=True)
+        # Drop some columns and do an unregularized fit
+        exog_no_PSI = rand_exog[:, :cls.m]
+        mod_unreg = sm.NegativeBinomial(rand_data.endog, exog_no_PSI,
+                                         loglike_method='geometric')
+        cls.res_unreg = mod_unreg.fit(method="newton", disp=False)
+        # Do a regularized fit with alpha, effectively dropping the last columns
+        alpha = 10 * len(rand_data.endog) * np.ones(cls.kvars)
+        alpha[:cls.m] = 0
+        mod_reg = sm.NegativeBinomial(rand_data.endog, rand_exog,
+                                      loglike_method='geometric')
+        cls.res_reg = mod_reg.fit_regularized(
+            method='l1', alpha=alpha, disp=False, acc=1e-10, maxiter=2000,
+            trim_mode='auto')
+
+        assert_equal(mod_reg.loglike_method, 'geometric')
+
+
 class TestLogitL1Compatability(CheckL1Compatability):
     @classmethod
     def setupClass(cls):
@@ -1021,11 +1078,13 @@ def setupClass(cls):
         res2.negativebinomial_geometric_bfgs()
         cls.res2 = res2
 
+    # the following are regression tests, could be inherited instead
+
     def test_aic(self):
-        assert_almost_equal(self.res1.aic, self.res2.aic, DECIMAL_1)
+        assert_almost_equal(self.res1.aic, self.res2.aic, DECIMAL_3)
 
     def test_bic(self):
-        assert_almost_equal(self.res1.bic, self.res2.bic, DECIMAL_1)
+        assert_almost_equal(self.res1.bic, self.res2.bic, DECIMAL_3)
 
     def test_conf_int(self):
         assert_almost_equal(self.res1.conf_int(), self.res2.conf_int, DECIMAL_3)