Repository: statsmodels/statsmodels

Issue:
genmod Links numerical improvements
reported by Brent Pedersen on mailing list

In doing so, I noticed that using families.links.Logit.inverse gives
nan with these Z's:

```
z = [1123.4910007309222, 1483.952316802719, 1344.86033748641,
706.339159002542, 1167.9986375146532, 663.8345826933115,
1496.3691686913917, 1563.0763842182257, 1587.4309332296314,
697.1173174974248, 1333.7256198289665, 1388.7667560586933,
819.7605431778434, 1479.9204150555015, 1078.5642245164856,
480.10338454985896, 1112.691659145772, 534.1061908007274,
918.2011296406588, 1280.8808515887802, 758.3890788775948,
673.503699841035, 1556.7043357878208, 819.5269028006679,
1262.5711060356423, 1098.7271535253608, 1482.811928490097,
796.198809756532, 893.7946963941745, 470.3304989319786,
1427.77079226037, 1365.2050226373822, 1492.4193201661922,
871.9922191949931, 768.4735925445908, 732.9222777654679,
812.2382651982667, 495.06449978924525]
```

If I use: `1 / (1. + np.exp(-z))`
as the inverse, it works fine.

there are a few more places similar to `t/(1+t)`, for example logit deriv2 and negative binomial link

Related: scipy.special has now some new special functions for these kinds of expressions with better corner case behavior. (several changes in scipy.stats benefit currently from improvements.)



Discussion:
Improvements here would be helpful.  I have been working with a Poisson regression in which 40% of the deviance residuals are NaN.

About Poisson deviance residuals: 
scipy.special got a xlogy function that handles 0*log(0)  I don't remember which version it was added.

Poisson.deviance has a specific code for the `endog==0` case, but Poisson.resid_dev doesn't


Resolving patch:
diff --git a/statsmodels/genmod/families/family.py b/statsmodels/genmod/families/family.py
--- a/statsmodels/genmod/families/family.py
+++ b/statsmodels/genmod/families/family.py
@@ -1,8 +1,8 @@
 '''
 The one parameter exponential family distributions used by GLM.
 '''
-#TODO: quasi, quasibinomial, quasipoisson
-#see http://www.biostat.jhsph.edu/~qli/biostatistics_r_doc/library/stats/html/family.html
+# TODO: quasi, quasibinomial, quasipoisson
+# see http://www.biostat.jhsph.edu/~qli/biostatistics_r_doc/library/stats/html/family.html
 # for comparison to R, and McCullagh and Nelder
 
 import numpy as np
@@ -12,6 +12,7 @@
 from . import varfuncs as V
 FLOAT_EPS = np.finfo(float).eps
 
+
 class Family(object):
     """
     The parent class for one-parameter exponential families.
@@ -30,7 +31,7 @@ class Family(object):
     :ref:`links`
 
     """
-#TODO: change these class attributes, use valid somewhere...
+    # TODO: change these class attributes, use valid somewhere...
     valid = [-np.inf, np.inf]
 
     links = []
@@ -46,32 +47,29 @@ def _setlink(self, link):
         See glm.GLM for a list of appropriate links for each family but note
         that not all of these are currently available.
         """
-#TODO: change the links class attribute in the families to hold meaningful
-# information instead of a list of links instances such as
-#[<statsmodels.family.links.Log object at 0x9a4240c>,
-# <statsmodels.family.links.Power object at 0x9a423ec>,
-# <statsmodels.family.links.Power object at 0x9a4236c>]
-# for Poisson...
+        # TODO: change the links class attribute in the families to hold
+        # meaningful information instead of a list of links instances such as
+        # [<statsmodels.family.links.Log object at 0x9a4240c>,
+        #  <statsmodels.family.links.Power object at 0x9a423ec>,
+        #  <statsmodels.family.links.Power object at 0x9a4236c>]
+        # for Poisson...
         self._link = link
         if not isinstance(link, L.Link):
             raise TypeError("The input should be a valid Link object.")
         if hasattr(self, "links"):
             validlink = link in self.links
-#            validlink = max([isinstance(link, _.__class__) for _ in self.links])
             validlink = max([isinstance(link, _) for _ in self.links])
             if not validlink:
                 errmsg = "Invalid link for family, should be in %s. (got %s)"
                 raise ValueError(errmsg % (repr(self.links), link))
 
-
     def _getlink(self):
         """
         Helper method to get the link for a family.
         """
         return self._link
 
-    #link property for each family
-    #pointer to link instance
+    # link property for each family is a pointer to link instance
     link = property(_getlink, _setlink, doc="Link function for family")
 
     def __init__(self, link, variance):
@@ -139,14 +137,19 @@ def deviance(self, endog, mu, scale=1.):
 
         Returns
         -------
-        DEV : array
+        Deviance : array
             The value of deviance function defined below.
 
         Notes
         -----
-        DEV = (sum_i(2*loglike(endog_i,endog_i) - 2*loglike(endog_i,mu_i)) / scale
+        Deviance is defined
+
+        .. math::
 
-        The deviance functions are analytically defined for each family.
+           \sum_i(2 loglike(y_i, y_i) - 2 * loglike(y_i, mu_i)) / scale
+
+        where y is the endogenous variable. The deviance functions are
+        analytically defined for each family.
         """
         raise NotImplementedError
 
@@ -226,8 +229,9 @@ def loglike(self, endog, mu, scale=1.):
         Notes
         -----
         This is defined for each family.  endog and mu are not restricted to
-        `endog` and `mu` respectively.  For instance, the deviance function calls
-        both loglike(endog,endog) and loglike(endog,mu) to get the likelihood ratio.
+        `endog` and `mu` respectively.  For instance, the deviance function
+        calls both loglike(endog,endog) and loglike(endog,mu) to get the
+        likelihood ratio.
         """
         raise NotImplementedError
 
@@ -237,11 +241,12 @@ def resid_anscombe(self, endog, mu):
 
         See also
         --------
-        statsmodels.families.family.Family docstring and the `resid_anscombe` for
-        the individual families for more information.
+        statsmodels.families.family.Family docstring and the `resid_anscombe`
+        for the individual families for more information.
         """
         raise NotImplementedError
 
+
 class Poisson(Family):
     """
     Poisson exponential family.
@@ -276,6 +281,19 @@ def __init__(self, link=L.log):
         self.variance = Poisson.variance
         self.link = link()
 
+
+    def _clean(self, x):
+        """
+        Helper function to trim the data so that is in (0,inf)
+
+        Notes
+        -----
+        The need for this function was discovered through usage and its
+        possible that other families might need a check for validity of the
+        domain.
+        """
+        return np.clip(x, FLOAT_EPS, np.inf)
+
     def resid_dev(self, endog, mu, scale=1.):
         """Poisson deviance residual
 
@@ -297,7 +315,10 @@ def resid_dev(self, endog, mu, scale=1.):
         -----
         resid_dev = sign(endog-mu)*sqrt(2*endog*log(endog/mu)-2*(endog-mu))
         """
-        return np.sign(endog-mu) * np.sqrt(2*endog*np.log(endog/mu)-2*(endog-mu))/scale
+        endog_mu = self._clean(endog/mu)
+        return np.sign(endog - mu) * np.sqrt(2 * endog *
+                                             np.log(endog_mu) -
+                                             2 * (endog - mu))/scale
 
     def deviance(self, endog, mu, scale=1.):
         '''
@@ -323,16 +344,8 @@ def deviance(self, endog, mu, scale=1.):
 
         :math:`deviance = 2*\\sum_{i}(Y*\\log(Y/\\mu))`
         '''
-        if np.any(endog==0):
-            retarr = np.zeros(endog.shape)
-            endog_mu = endog/mu
-            mask = endog_mu != 0
-            endog_mu_Masked = endog_mu[mask]
-            endog_masked = endog[mask]
-            np.putmask(retarr, mask, endog_masked*np.log(endog_mu_Masked)/scale)
-            return 2*np.sum(retarr)
-        else:
-            return 2*np.sum(endog*np.log(endog/mu))/scale
+        endog_mu = self._clean(endog/mu)
+        return 2*np.sum(endog*np.log(endog_mu))/scale
 
     def loglike(self, endog, mu, scale=1.):
         """
@@ -350,8 +363,8 @@ def loglike(self, endog, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         -----
@@ -378,10 +391,15 @@ def resid_anscombe(self, endog, mu):
 
         Notes
         -----
-        resid_anscombe = :math:`(3/2.)*(endog^{2/3.} - \\mu**(2/3.))/\\mu^{1/6.}`
+        resid_anscombe is defined
+
+        .. math:
+
+           (3/2.)*(endog^{2/3.} - \\mu**(2/3.))/\\mu^{1/6.}
         """
         return (3/2.)*(endog**(2/3.)-mu**(2/3.))/mu**(1/6.)
 
+
 class Gaussian(Family):
     """
     Gaussian exponential family distribution.
@@ -479,8 +497,8 @@ def loglike(self, endog, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         -----
@@ -495,16 +513,16 @@ def loglike(self, endog, mu, scale=1.):
             (1/2.)*log(2*pi*`scale`))
         """
         if isinstance(self.link, L.Power) and self.link.power == 1:
-        # This is just the loglikelihood for classical OLS
+            # This is just the loglikelihood for classical OLS
             nobs2 = endog.shape[0]/2.
             SSR = ss(endog-self.fitted(mu))
             llf = -np.log(SSR) * nobs2
             llf -= (1+np.log(np.pi/nobs2))*nobs2
             return llf
         else:
-        # Return the loglikelihood for Gaussian GLM
-            return np.sum((endog*mu-mu**2/2)/scale-endog**2/(2*scale)-\
-                    .5*np.log(2*np.pi*scale))
+            # Return the loglikelihood for Gaussian GLM
+            return np.sum((endog * mu - mu**2/2)/scale - endog**2/(2 * scale)
+                          - .5*np.log(2 * np.pi * scale))
 
     def resid_anscombe(self, endog, mu):
         """
@@ -528,6 +546,7 @@ def resid_anscombe(self, endog, mu):
         """
         return endog-mu
 
+
 class Gamma(Family):
     """
     Gamma exponential family distribution.
@@ -560,7 +579,6 @@ def __init__(self, link=L.inverse_power):
         self.variance = Gamma.variance
         self.link = link()
 
-#TODO: note the note
     def _clean(self, x):
         """
         Helper function to trim the data so that is in (0,inf)
@@ -599,7 +617,7 @@ def deviance(self, endog, mu, scale=1.):
         return 2 * np.sum((endog - mu)/mu - np.log(endog_mu))
 
     def resid_dev(self, endog, mu, scale=1.):
-        """
+        r"""
         Gamma deviance residuals
 
         Parameters
@@ -618,10 +636,15 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `resid_dev` = sign(endog - mu) * sqrt(-2*(-(endog-mu)/mu + log(endog/mu)))
+        `resid_dev` is defined
+
+        .. math:
+
+           sign(endog - \mu) * \sqrt{-2*(-(endog-\mu)/\mu + \log(endog/\mu))}
         """
         endog_mu = self._clean(endog/mu)
-        return np.sign(endog-mu) * np.sqrt(-2*(-(endog-mu)/mu + np.log(endog_mu)))
+        return np.sign(endog - mu) * np.sqrt(-2 * (-(endog - mu)/mu +
+                                                   np.log(endog_mu)))
 
     def loglike(self, endog, mu, scale=1.):
         """
@@ -639,20 +662,21 @@ def loglike(self, endog, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         --------
-        llf = -1/scale * sum(endog/mu + log(mu) + (scale-1)*log(endog) + log(scale) +\
-            scale*gammaln(1/scale))
+        llf = -1/scale * sum(endog/mu + log(mu) + (scale-1)*log(endog) +\
+              log(scale) + scale*gammaln(1/scale))
         where gammaln is the log gamma function.
         """
-        return - 1./scale * np.sum(endog/mu+np.log(mu)+(scale-1)*np.log(endog)\
-                +np.log(scale)+scale*special.gammaln(1./scale))
-# in Stata scale is set to equal 1 for reporting llf
-# in R it's the dispersion, though there is a loss of precision vs. our
-# results due to an assumed difference in implementation
+        return - 1./scale * np.sum(endog/mu + np.log(mu) + (scale - 1) *
+                                   np.log(endog) + np.log(scale) + scale *
+                                   special.gammaln(1./scale))
+        # in Stata scale is set to equal 1 for reporting llf
+        # in R it's the dispersion, though there is a loss of precision vs.
+        # our results due to an assumed difference in implementation
 
     def resid_anscombe(self, endog, mu):
         """
@@ -676,6 +700,7 @@ def resid_anscombe(self, endog, mu):
         """
         return 3*(endog**(1/3.)-mu**(1/3.))/mu**(1/3.)
 
+
 class Binomial(Family):
     """
     Binomial exponential family distribution.
@@ -706,14 +731,14 @@ class Binomial(Family):
     """
 
     links = [L.logit, L.probit, L.cauchy, L.log, L.cloglog, L.identity]
-    variance = V.binary # this is not used below in an effort to include n
-
-    def __init__(self, link=L.logit):  #, n=1.):
-#TODO: it *should* work for a constant n>1 actually, if data_weights is
-# equal to n
-        self.n = 1 # overwritten by initialize if needed but
-                   # always used to initialize variance
-                   # since endog is assumed/forced to be (0,1)
+    variance = V.binary  # this is not used below in an effort to include n
+
+    def __init__(self, link=L.logit):  # , n=1.):
+        # TODO: it *should* work for a constant n>1 actually, if data_weights
+        # is equal to n
+        self.n = 1
+        # overwritten by initialize if needed but always used to initialize
+        # variance since endog is assumed/forced to be (0,1)
         self.variance = V.Binomial(n=self.n)
         self.link = link()
 
@@ -746,8 +771,8 @@ def initialize(self, endog):
         successes + failures.
         '''
         if (endog.ndim > 1 and endog.shape[1] > 1):
-            y = endog[:,0]
-            self.n = endog.sum(1) # overwrite self.n for deviance below
+            y = endog[:, 0]
+            self.n = endog.sum(1)  # overwrite self.n for deviance below
             return y*1./self.n
         else:
             return endog
@@ -777,8 +802,9 @@ def deviance(self, endog, mu, scale=1.):
 
         `deviance` = -2*sum(I_one * log(mu) + (I_zero)*log(1-mu))
 
-        where I_one is an indicator function that evalueates to 1 if endog_i == 1.
-        and I_zero is an indicator function that evaluates to 1 if endog_i == 0.
+        where I_one is an indicator function that evalueates to 1 if
+        endog_i == 1. and I_zero is an indicator function that evaluates to
+        1 if endog_i == 0.
 
         If the model is ninomial:
 
@@ -786,11 +812,15 @@ def deviance(self, endog, mu, scale=1.):
         where endog and n are as defined in Binomial.initialize.
         '''
         if np.shape(self.n) == () and self.n == 1:
-            one = np.equal(endog,1)
-            return -2 * np.sum(one * np.log(mu+1e-200) + (1-one) * np.log(1-mu+1e-200))
+            one = np.equal(endog, 1)
+            return -2 * np.sum(one * np.log(mu + 1e-200) + (1-one) *
+                               np.log(1 - mu + 1e-200))
 
         else:
-            return 2*np.sum(self.n*(endog*np.log(endog/mu+1e-200)+(1-endog)*np.log((1-endog)/(1-mu)+1e-200)))
+            return 2 * np.sum(self.n * (endog * np.log(endog/mu + 1e-200) +
+                                        (1 - endog) * np.log((1 - endog) /
+                                                             (1 - mu) +
+                                                             1e-200)))
 
     def resid_dev(self, endog, mu, scale=1.):
         """
@@ -821,19 +851,22 @@ def resid_dev(self, endog, mu, scale=1.):
 
         If `endog` is binomial:
 
-        resid_dev = sign(endog-mu)*sqrt(2*n*(endog*log(endog/mu)+(1-endog)*log((1-endog)/(1-mu))))
+        resid_dev = sign(endog - mu) * sqrt(2 * n * (endog * log(endog/mu) +
+                    (1 - endog) * log((1 - endog)/(1 - mu))))
 
         where endog and n are as defined in Binomial.initialize.
         """
 
         mu = self.link._clean(mu)
         if np.shape(self.n) == () and self.n == 1:
-            one = np.equal(endog,1)
-            return np.sign(endog-mu)*np.sqrt(-2*np.log(one*mu+(1-one)*(1-mu)))\
-                    /scale
+            one = np.equal(endog, 1)
+            return np.sign(endog-mu)*np.sqrt(-2 * np.log(one * mu + (1 - one) *
+                                                         (1 - mu)))/scale
         else:
-            return np.sign(endog-mu) * np.sqrt(2*self.n*(endog*np.log(endog/mu+1e-200)+(1-endog)*\
-                        np.log((1-endog)/(1-mu)+1e-200)))/scale
+            return (np.sign(endog - mu) *
+                    np.sqrt(2 * self.n * (endog * np.log(endog/mu + 1e-200) +
+                            (1 - endog) * np.log((1 - endog)/(1 - mu) +
+                                                 1e-200)))/scale)
 
     def loglike(self, endog, mu, scale=1.):
         """
@@ -851,8 +884,8 @@ def loglike(self, endog, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         --------
@@ -863,18 +896,21 @@ def loglike(self, endog, mu, scale=1.):
         `llf` = scale*sum(gammaln(n+1) - gammaln(y+1) - gammaln(n-y+1) +\
                 y*log(mu/(1-mu)) + n*log(1-mu)
 
-        where gammaln is the log gamma function and y = endog*n with endog and n
-        as defined in Binomial initialize.  This simply makes y the original
-        number of successes.
+        where gammaln is the log gamma function and y = endog*n with endog
+        and n as defined in Binomial initialize.  This simply makes y the
+        original number of successes.
         """
 
         if np.shape(self.n) == () and self.n == 1:
-            return scale*np.sum(endog*np.log(mu/(1-mu)+1e-200)+np.log(1-mu))
+            return scale * np.sum(endog * np.log(mu/(1 - mu) + 1e-200) +
+                                  np.log(1 - mu))
         else:
-            y=endog*self.n  #convert back to successes
-            return scale * np.sum(special.gammaln(self.n+1)-\
-                special.gammaln(y+1)-special.gammaln(self.n-y+1)\
-                +y*np.log(mu/(1-mu))+self.n*np.log(1-mu))
+            y = endog * self.n  # convert back to successes
+            return scale * np.sum(special.gammaln(self.n + 1) -
+                                  special.gammaln(y + 1) -
+                                  special.gammaln(self.n - y + 1) + y *
+                                  np.log(mu/(1 - mu)) + self.n *
+                                  np.log(1 - mu))
 
     def resid_anscombe(self, endog, mu):
         '''
@@ -917,10 +953,11 @@ def resid_anscombe(self, endog, mu):
             Journal of the Royal Statistical Society B. 30, 248-75.
 
         '''
-        cox_snell = lambda x: special.betainc(2/3., 2/3., x)\
-                            *special.beta(2/3.,2/3.)
-        return np.sqrt(self.n)*(cox_snell(endog)-cox_snell(mu))/\
-                        (mu**(1/6.)*(1-mu)**(1/6.))
+        cox_snell = lambda x: (special.betainc(2/3., 2/3., x)
+                               * special.beta(2/3., 2/3.))
+        return np.sqrt(self.n) * ((cox_snell(endog) - cox_snell(mu)) /
+                                  (mu**(1/6.) * (1 - mu)**(1/6.)))
+
 
 class InverseGaussian(Family):
     """
@@ -1024,16 +1061,16 @@ def loglike(self, endog, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         -----
-        `llf` = -(1/2.)*sum((endog-mu)**2/(endog*mu**2*scale) + log(scale*endog**3)\
-                 + log(2*pi))
+        `llf` = -(1/2.)*sum((endog-mu)**2/(endog*mu**2*scale)
+                 + log(scale*endog**3) + log(2*pi))
         """
-        return -.5 * np.sum((endog-mu)**2/(endog*mu**2*scale)\
-                + np.log(scale*endog**3) + np.log(2*np.pi))
+        return -.5 * np.sum((endog - mu)**2/(endog * mu**2 * scale)
+                            + np.log(scale * endog**3) + np.log(2 * np.pi))
 
     def resid_anscombe(self, endog, mu):
         """
@@ -1058,6 +1095,7 @@ def resid_anscombe(self, endog, mu):
         """
         return np.log(endog/mu)/np.sqrt(mu)
 
+
 class NegativeBinomial(Family):
     """
     Negative Binomial exponential family.
@@ -1092,8 +1130,8 @@ class NegativeBinomial(Family):
 
     """
     links = [L.log, L.cloglog, L.identity, L.nbinom, L.Power]
-#TODO: add the ability to use the power links with an if test
-# similar to below
+    # TODO: add the ability to use the power links with an if test
+    # similar to below
     variance = V.nbinom
 
     def __init__(self, link=L.log, alpha=1.):
@@ -1117,7 +1155,7 @@ def _clean(self, x):
         return np.clip(x, FLOAT_EPS, np.inf)
 
     def deviance(self, endog, mu, scale=1.):
-        """
+        r"""
         Returns the value of the deviance function.
 
         Parameters
@@ -1142,23 +1180,26 @@ def deviance(self, endog, mu, scale=1.):
 
         if :math:`Y_{i} == 0:`
 
-        piecewise_i = :math:`2\\log\\left(1+\\alpha*\\mu\\right)/\\alpha`
+        piecewise_i = :math:`2\log\left(1+\alpha*\mu\right)/\alpha`
 
         if :math:`Y_{i} > 0`:
 
-        piecewise_i = :math:`2 Y \\log(Y/\\mu)-2/\\alpha(1+\\alpha Y)*\\log((1+\\alpha Y)/(1+\\alpha\\mu))`
+        piecewise_i = :math:`2 Y \log(Y/\mu)-2/\alpha(1+\alpha Y)*
+                            \log((1+\alpha Y)/(1+\alpha\mu))`
         """
-        iszero = np.equal(endog,0)
+        iszero = np.equal(endog, 0)
         notzero = 1 - iszero
         tmp = np.zeros(len(endog))
         endog_mu = self._clean(endog/mu)
-        tmp = iszero*2*np.log(1+self.alpha*mu)/self.alpha
-        tmp += notzero*(2*endog*np.log(endog_mu)-2/self.alpha*(1+self.alpha*endog)*\
-                np.log((1+self.alpha*endog)/(1+self.alpha*mu)))
+        tmp = iszero * 2 * np.log(1 + self.alpha * mu)/self.alpha
+        tmp += notzero * (2 * endog * np.log(endog_mu) - 2/self.alpha *
+                          (1 + self.alpha*endog) *
+                          np.log((1 + self.alpha * endog) /
+                                 (1 + self.alpha * mu)))
         return np.sum(tmp)/scale
 
     def resid_dev(self, endog, mu, scale=1.):
-        '''
+        r'''
         Negative Binomial Deviance Residual
 
         Parameters
@@ -1184,15 +1225,18 @@ def resid_dev(self, endog, mu, scale=1.):
         :math:`piecewise_i = 2*log(1+alpha*mu)/alpha`
 
         if :math:`Y_i > 0`:
-        :math:`piecewise_i = 2*Y*log(Y/\\mu)-2/\\alpha*(1+\\alpha*Y)*log((1+\\alpha*Y)/(1+\\alpha*\\mu))`
+        :math:`piecewise_i = 2*Y*log(Y/\mu) - 2/\alpha * (1 + \alpha * Y) *
+                             \log((1 + \alpha * Y)/(1 + \alpha * \mu))`
         '''
-        iszero = np.equal(endog,0)
+        iszero = np.equal(endog, 0)
         notzero = 1 - iszero
-        tmp=np.zeros(len(endog))
-        tmp = iszero*2*np.log(1+self.alpha*mu)/self.alpha
-        tmp += notzero*(2*endog*np.log(endog/mu)-2/self.alpha*(1+self.alpha*endog)*\
-                np.log((1+self.alpha*endog)/(1+self.alpha*mu)))
-        return np.sign(endog-mu)*np.sqrt(tmp)/scale
+        tmp = np.zeros(len(endog))
+        tmp = iszero * 2 * np.log(1 + self.alpha * mu)/self.alpha
+        tmp += notzero * (2 * endog * np.log(endog/mu) - 2/self.alpha *
+                          (1 + self.alpha * endog) *
+                          np.log((1 + self.alpha * endog) /
+                                 (1 + self.alpha * mu)))
+        return np.sign(endog - mu) * np.sqrt(tmp)/scale
 
     def loglike(self, endog, lin_pred=None):
         """
@@ -1209,29 +1253,31 @@ def loglike(self, endog, lin_pred=None):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (endog,mu,scale)
-            as defined below.
+            The value of the loglikelihood function evaluated at
+            (endog,mu,scale) as defined below.
 
         Notes
         -----
-        sum(endog*log(alpha*exp(lin_pred)/(1+alpha*exp(lin_pred))) -\
+        sum(endog*log(alpha*exp(lin_pred)/(1+alpha*exp(lin_pred))) -
                 log(1+alpha*exp(lin_pred))/alpha + constant)
 
-        where constant is defined as
-        constant = gammaln(endog + 1/alpha) - gammaln(endog + 1) - gammaln(1/alpha)
+        where constant is defined as::
+
+           constant = gammaln(endog + 1/alpha) - gammaln(endog + 1) -
+                      gammaln(1/alpha)
         """
         # don't need to specify mu
         if lin_pred is None:
-            raise AttributeError('The loglikelihood for the negative binomial \
-requires that the fitted values be provided via the `lin_pred` keyword \
-argument.')
-        constant = special.gammaln(endog + 1/self.alpha) - special.gammaln(endog+1)\
-                    -special.gammaln(1/self.alpha)
+            raise AttributeError('The loglikelihood for the negative binomial'
+                                 ' requires that the fitted values be '
+                                 'provided via the `lin_pred` keyword '
+                                 'argument.')
+        constant = (special.gammaln(endog + 1/self.alpha) -
+                    special.gammaln(endog+1) - special.gammaln(1/self.alpha))
         exp_lin_pred = np.exp(lin_pred)
-        return np.sum(endog*np.log(self.alpha*exp_lin_pred/\
-            (1 + self.alpha*exp_lin_pred)) - \
-            np.log(1+self.alpha*exp_lin_pred)/self.alpha\
-            + constant)
+        return (np.sum(endog * np.log(self.alpha * exp_lin_pred /
+                                      (1 + self.alpha * exp_lin_pred)) -
+                np.log(1 + self.alpha * exp_lin_pred)/self.alpha + constant))
 
     def resid_anscombe(self, endog, mu):
         """
@@ -1258,6 +1304,7 @@ def resid_anscombe(self, endog, mu):
         hyp2f1(x) = hyp2f1(2/3.,1/3.,5/3.,x)
         """
 
-        hyp2f1 = lambda x : special.hyp2f1(2/3.,1/3.,5/3.,x)
-        return (hyp2f1(-self.alpha*endog)-hyp2f1(-self.alpha*mu)+1.5*(endog**(2/3.)-\
-                mu**(2/3.)))/(mu+self.alpha*mu**2)**(1/6.)
+        hyp2f1 = lambda x : special.hyp2f1(2/3., 1/3., 5/3., x)
+        return ((hyp2f1(-self.alpha * endog) - hyp2f1(-self.alpha * mu) +
+                 1.5 * (endog**(2/3.)-mu**(2/3.))) /
+                (mu + self.alpha*mu**2)**(1/6.))
diff --git a/statsmodels/genmod/families/links.py b/statsmodels/genmod/families/links.py
--- a/statsmodels/genmod/families/links.py
+++ b/statsmodels/genmod/families/links.py
@@ -6,11 +6,8 @@
 import scipy.stats
 FLOAT_EPS = np.finfo(float).eps
 
-#TODO: are the instance actually "aliases"
-# I used this terminology in varfuncs as well -ss
 
 class Link(object):
-
     """
     A generic link function for one-parameter exponential family.
 
@@ -64,7 +61,6 @@ def deriv(self, p):
         """
         return NotImplementedError
 
-
     def deriv2(self, p):
         """Second derivative of the link function g''(p)
 
@@ -74,15 +70,14 @@ def deriv2(self, p):
         # TODO: workaround proplem with numdiff for 1d
         return np.diag(approx_fprime_cs(p, self.deriv))
 
-
     def inverse_deriv(self, z):
         """
         Derivative of the inverse link function g^(-1)(z).
 
         Notes
         -----
-        This reference implementation gives the correct result but it inefficient,
-        so it can be overriden in subclasses.
+        This reference implementation gives the correct result but it
+        inefficient, so it can be overriden in subclasses.
 
         Parameters
         ----------
@@ -165,8 +160,9 @@ def inverse(self, z):
         -----
         g^(-1)(z) = exp(z)/(1+exp(z))
         """
-        t = np.exp(z)
-        return t / (1. + t)
+        z = np.asarray(z)
+        t = np.exp(-z)
+        return 1. / (1. + t)
 
     def deriv(self, p):
 
@@ -208,13 +204,13 @@ def inverse_deriv(self, z):
 
         """
         t = np.exp(z)
-        return t/(1+t)**2
+        return t/(1 + t)**2
 
 
-#logit = Logit()
 class logit(Logit):
     pass
 
+
 class Power(Link):
     """
     The power transform
@@ -309,12 +305,12 @@ def inverse_deriv(self, z):
 
         Returns
         -------
-        The value of the derivative of the inverse of the power transform function
-
+        The value of the derivative of the inverse of the power transform
+        function
         """
         return np.power(z, (1 - self.power)/self.power) / self.power
 
-#inverse = Power(power=-1.)
+
 class inverse_power(Power):
     """
     The inverse transform
@@ -328,7 +324,7 @@ class inverse_power(Power):
     def __init__(self):
         super(inverse_power, self).__init__(power=-1.)
 
-#sqrt = Power(power=0.5)
+
 class sqrt(Power):
     """
     The square-root transform
@@ -342,8 +338,8 @@ class sqrt(Power):
     def __init__(self):
         super(sqrt, self).__init__(power=.5)
 
+
 class inverse_squared(Power):
-#inverse_squared = Power(power=-2.)
     """
     The inverse squared transform
 
@@ -356,6 +352,7 @@ class inverse_squared(Power):
     def __init__(self):
         super(inverse_squared, self).__init__(power=-2.)
 
+
 class identity(Power):
     """
     The identity transform
@@ -369,6 +366,7 @@ class identity(Power):
     def __init__(self):
         super(identity, self).__init__(power=1.)
 
+
 class Log(Link):
     """
     The log transform
@@ -401,7 +399,7 @@ def __call__(self, p, **extra):
         g(p) = log(p)
         """
         x = self._clean(p)
-        return np.log(p)
+        return np.log(x)
 
     def inverse(self, z):
         """
@@ -459,6 +457,7 @@ def inverse_deriv(self, z):
         """
         return np.exp(z)
 
+
 class log(Log):
     """
     The log transform
@@ -469,7 +468,8 @@ class log(Log):
     """
     pass
 
-#TODO: the CDFLink is untested
+
+# TODO: the CDFLink is untested
 class CDFLink(Logit):
     """
     The use the CDF of a scipy.stats distribution
@@ -552,7 +552,6 @@ def deriv(self, p):
         p = self._clean(p)
         return 1. / self.dbn.pdf(self.dbn.ppf(p))
 
-
     def deriv2(self, p):
         """Second derivative of the link function g''(p)
 
@@ -562,7 +561,6 @@ def deriv2(self, p):
         # Note: speciaf function for norm.ppf does not support complex
         return np.diag(approx_fprime(p, self.deriv, centered=True))
 
-
     def inverse_deriv(self, z):
         """
         Derivative of the inverse of the CDF transformation link function
@@ -579,8 +577,6 @@ def inverse_deriv(self, z):
         return 1/self.deriv(self.inverse(z))
 
 
-
-#probit = CDFLink()
 class probit(CDFLink):
     """
     The probit (standard normal CDF) transform
@@ -593,6 +589,7 @@ class probit(CDFLink):
     """
     pass
 
+
 class cauchy(CDFLink):
     """
     The Cauchy (standard Cauchy CDF) transform
@@ -606,7 +603,8 @@ class cauchy(CDFLink):
     def __init__(self):
         super(cauchy, self).__init__(dbn=scipy.stats.cauchy)
 
-#TODO: CLogLog is untested
+
+# TODO: CLogLog is untested
 class CLogLog(Logit):
     """
     The complementary log-log transform
@@ -637,7 +635,7 @@ def __call__(self, p):
         g(p) = log(-log(1-p))
         """
         p = self._clean(p)
-        return np.log(-np.log(1-p))
+        return np.log(-np.log(1 - p))
 
     def inverse(self, z):
         """
@@ -658,7 +656,7 @@ def inverse(self, z):
         -----
         g^(-1)(`z`) = 1-exp(-exp(`z`))
         """
-        return 1-np.exp(-np.exp(z))
+        return 1 - np.exp(-np.exp(z))
 
     def deriv(self, p):
         """
@@ -679,7 +677,7 @@ def deriv(self, p):
         g'(p) = - 1 / (log(p) * p)
         """
         p = self._clean(p)
-        return 1. / ((p-1)*(np.log(1-p)))
+        return 1. / ((p - 1) * (np.log(1 - p)))
 
     def inverse_deriv(self, z):
         """
@@ -696,6 +694,7 @@ def inverse_deriv(self, z):
         """
         return np.exp(z - np.exp(z))
 
+
 class cloglog(CLogLog):
     """
     The CLogLog transform link function.
@@ -709,6 +708,7 @@ class cloglog(CLogLog):
     """
     pass
 
+
 class NegativeBinomial(object):
     '''
     The negative binomial link function
@@ -716,9 +716,9 @@ class NegativeBinomial(object):
     Parameters
     ----------
     alpha : float, optional
-        Alpha is the ancillary parameter of the Negative Binomial link function.
-        It is assumed to be nonstochastic.  The default value is 1. Permissible
-        values are usually assumed to be in (.01,2).
+        Alpha is the ancillary parameter of the Negative Binomial link
+        function. It is assumed to be nonstochastic.  The default value is 1.
+        Permissible values are usually assumed to be in (.01, 2).
     '''
 
     def __init__(self, alpha=1.):
@@ -746,7 +746,7 @@ def __call__(self, p):
         g(p) = log(p/(p + 1/alpha))
         '''
         p = self._clean(p)
-        return np.log(p/(p+1/self.alpha))
+        return np.log(p/(p + 1/self.alpha))
 
     def inverse(self, z):
         '''
@@ -765,9 +765,9 @@ def inverse(self, z):
         -----
         g^(-1)(z) = exp(z)/(alpha*(1-exp(z)))
         '''
-        return np.exp(z)/(self.alpha*(1-np.exp(z)))
+        return -1/(self.alpha * (1 - np.exp(-z)))
 
-    def deriv(self,p):
+    def deriv(self, p):
         '''
         Derivative of the negative binomial transform
 
@@ -785,7 +785,7 @@ def deriv(self,p):
         -----
         g'(x) = 1/(x+alpha*x^2)
         '''
-        return 1/(p+self.alpha*p**2)
+        return 1/(p + self.alpha * p**2)
 
     def inverse_deriv(self, z):
         '''
@@ -798,11 +798,13 @@ def inverse_deriv(self, z):
 
         Returns
         -------
-        The value of the inverse of the derivative of the negative binomial link
+        The value of the inverse of the derivative of the negative binomial
+        link
         '''
         t = np.exp(z)
         return t / (self.alpha * (1-t)**2)
 
+
 class nbinom(NegativeBinomial):
     """
     The negative binomial link function.


Test patch:
diff --git a/statsmodels/genmod/families/tests/test_link.py b/statsmodels/genmod/families/tests/test_link.py
--- a/statsmodels/genmod/families/tests/test_link.py
+++ b/statsmodels/genmod/families/tests/test_link.py
@@ -3,7 +3,7 @@
 """
 from statsmodels.compat.python import range
 import numpy as np
-from numpy.testing import assert_allclose
+from numpy.testing import assert_allclose, assert_equal
 import statsmodels.genmod.families as families
 
 # Family instances
@@ -44,6 +44,23 @@ def test_inverse_deriv():
             assert_allclose(d, f, rtol=1e-8, atol=1e-10)
 
 
+def test_invlogit_stability():
+    z = [1123.4910007309222, 1483.952316802719, 1344.86033748641,
+         706.339159002542, 1167.9986375146532, 663.8345826933115,
+         1496.3691686913917, 1563.0763842182257, 1587.4309332296314,
+         697.1173174974248, 1333.7256198289665, 1388.7667560586933,
+         819.7605431778434, 1479.9204150555015, 1078.5642245164856,
+         480.10338454985896, 1112.691659145772, 534.1061908007274,
+         918.2011296406588, 1280.8808515887802, 758.3890788775948,
+         673.503699841035, 1556.7043357878208, 819.5269028006679,
+         1262.5711060356423, 1098.7271535253608, 1482.811928490097,
+         796.198809756532, 893.7946963941745, 470.3304989319786,
+         1427.77079226037, 1365.2050226373822, 1492.4193201661922,
+         871.9922191949931, 768.4735925445908, 732.9222777654679,
+         812.2382651982667, 495.06449978924525]
+    zinv = logit.inverse(z)
+    assert_equal(zinv, np.ones_like(z))
+
 if __name__=="__main__":
     #run_module_suite()
     #taken from Fernando Perez: