Repository: statsmodels/statsmodels

Issue:
GLM.fit maxiter=0  incorrect
GLM fit still does a lot of initialization calculations that are not necessary if maxiter=0.

I'm using maxiter=0 with start_params=params to get a full results instance.

I think the error is in missing offset and exposure in the initialization
`mu = self.family.fitted(np.dot(wlsexog, start_params))`  see #1486



Discussion:

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
@@ -122,15 +122,15 @@ def weights(self, mu):
         """
         return 1. / (self.link.deriv(mu)**2 * self.variance(mu))
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         """
-        Deviance of (Y,mu) pair.
+        Deviance of (endog,mu) pair.
 
         Deviance is usually defined as twice the loglikelihood ratio.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             The endogenous response variable
         mu : array-like
             The inverse of the link function at the linear predicted values.
@@ -144,19 +144,19 @@ def deviance(self, Y, mu, scale=1.):
 
         Notes
         -----
-        DEV = (sum_i(2*loglike(Y_i,Y_i) - 2*loglike(Y_i,mu_i)) / scale
+        DEV = (sum_i(2*loglike(endog_i,endog_i) - 2*loglike(endog_i,mu_i)) / scale
 
         The deviance functions are analytically defined for each family.
         """
         raise NotImplementedError
 
-    def resid_dev(self, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """
         The deviance residuals
 
         Parameters
         ----------
-        Y : array
+        endog : array
             The endogenous response variable
         mu : array
             The inverse of the link function at the linear predicted values.
@@ -173,13 +173,13 @@ def resid_dev(self, Y, mu, scale=1.):
         """
         raise NotImplementedError
 
-    def fitted(self, eta):
+    def fitted(self, lin_pred):
         """
-        Fitted values based on linear predictors eta.
+        Fitted values based on linear predictors lin_pred.
 
         Parameters
         -----------
-        eta : array
+        lin_pred : array
             Values of the linear predictor of the model.
             dot(X,beta) in a classical linear model.
 
@@ -189,7 +189,7 @@ def fitted(self, eta):
             The mean response variables given by the inverse of the link
             function.
         """
-        return self.link.inverse(eta)
+        return self.link.inverse(lin_pred)
 
     def predict(self, mu):
         """
@@ -202,19 +202,19 @@ def predict(self, mu):
 
         Returns
         -------
-        eta : array
+        lin_pred : array
             Linear predictors based on the mean response variables.  The value
             of the link function at the given mu.
         """
         return self.link(mu)
 
-    def loglike(self, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         The loglikelihood function.
 
         Parameters
         ----------
-        `Y` : array
+        `endog` : array
             Usually the endogenous response variable.
         `mu` : array
             Usually but not always the fitted mean response variable.
@@ -222,16 +222,16 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood evaluated at (Y,mu).
+            The value of the loglikelihood evaluated at (endog,mu).
         Notes
         -----
-        This is defined for each family.  Y and mu are not restricted to
-        `Y` and `mu` respectively.  For instance, the deviance function calls
-        both loglike(Y,Y) and loglike(Y,mu) to get the likelihood ratio.
+        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.
         """
         raise NotImplementedError
 
-    def resid_anscombe(self, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         The Anscome residuals.
 
@@ -276,12 +276,12 @@ def __init__(self, link=L.log):
         self.variance = Poisson.variance
         self.link = link()
 
-    def resid_dev(self, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """Poisson deviance residual
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -295,17 +295,17 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         -----
-        resid_dev = sign(Y-mu)*sqrt(2*Y*log(Y/mu)-2*(Y-mu))
+        resid_dev = sign(endog-mu)*sqrt(2*endog*log(endog/mu)-2*(endog-mu))
         """
-        return np.sign(Y-mu) * np.sqrt(2*Y*np.log(Y/mu)-2*(Y-mu))/scale
+        return np.sign(endog-mu) * np.sqrt(2*endog*np.log(endog/mu)-2*(endog-mu))/scale
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         '''
         Poisson deviance function
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -315,7 +315,7 @@ def deviance(self, Y, mu, scale=1.):
         Returns
         -------
         deviance : float
-            The deviance function at (Y,mu) as defined below.
+            The deviance function at (endog,mu) as defined below.
 
         Notes
         -----
@@ -323,24 +323,24 @@ def deviance(self, Y, mu, scale=1.):
 
         :math:`deviance = 2*\\sum_{i}(Y*\\log(Y/\\mu))`
         '''
-        if np.any(Y==0):
-            retarr = np.zeros(Y.shape)
-            Ymu = Y/mu
-            mask = Ymu != 0
-            YmuMasked = Ymu[mask]
-            Ymasked = Y[mask]
-            np.putmask(retarr, mask, Ymasked*np.log(YmuMasked)/scale)
+        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(Y*np.log(Y/mu))/scale
+            return 2*np.sum(endog*np.log(endog/mu))/scale
 
-    def loglike(self, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         Loglikelihood function for Poisson exponential family distribution.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -350,23 +350,23 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
         -----
-        llf = scale * sum(-mu + Y*log(mu) - gammaln(Y+1))
+        llf = scale * sum(-mu + endog*log(mu) - gammaln(endog+1))
         where gammaln is the log gamma function
         """
-        return scale * np.sum(-mu + Y*np.log(mu)-special.gammaln(Y+1))
+        return scale * np.sum(-mu + endog*np.log(mu)-special.gammaln(endog+1))
 
-    def resid_anscombe(self, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         Anscombe residuals for the Poisson exponential family distribution
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -378,9 +378,9 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         -----
-        resid_anscombe = :math:`(3/2.)*(Y^{2/3.} - \\mu**(2/3.))/\\mu^{1/6.}`
+        resid_anscombe = :math:`(3/2.)*(endog^{2/3.} - \\mu**(2/3.))/\\mu^{1/6.}`
         """
-        return (3/2.)*(Y**(2/3.)-mu**(2/3.))/mu**(1/6.)
+        return (3/2.)*(endog**(2/3.)-mu**(2/3.))/mu**(1/6.)
 
 class Gaussian(Family):
     """
@@ -414,13 +414,13 @@ def __init__(self, link=L.identity):
         self.variance = Gaussian.variance
         self.link = link()
 
-    def resid_dev(self, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """
         Gaussian deviance residuals
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -434,18 +434,18 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         --------
-        `resid_dev` = (`Y` - `mu`)/sqrt(variance(`mu`))
+        `resid_dev` = (`endog` - `mu`)/sqrt(variance(`mu`))
         """
 
-        return (Y - mu) / np.sqrt(self.variance(mu))/scale
+        return (endog - mu) / np.sqrt(self.variance(mu))/scale
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         """
         Gaussian deviance function
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -455,21 +455,21 @@ def deviance(self, Y, mu, scale=1.):
         Returns
         -------
         deviance : float
-            The deviance function at (Y,mu) as defined below.
+            The deviance function at (endog,mu) as defined below.
 
         Notes
         --------
-        `deviance` = sum((Y-mu)**2)
+        `deviance` = sum((endog-mu)**2)
         """
-        return np.sum((Y-mu)**2)/scale
+        return np.sum((endog-mu)**2)/scale
 
-    def loglike(self, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         Loglikelihood function for Gaussian exponential family distribution.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -479,7 +479,7 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
@@ -487,32 +487,32 @@ def loglike(self, Y, mu, scale=1.):
         If the link is the identity link function then the
         loglikelihood function is the same as the classical OLS model.
         llf = -(nobs/2)*(log(SSR) + (1 + log(2*pi/nobs)))
-        where SSR = sum((Y-link^(-1)(mu))**2)
+        where SSR = sum((endog-link^(-1)(mu))**2)
 
         If the links is not the identity link then the loglikelihood
         function is defined as
-        llf = sum((`Y`*`mu`-`mu`**2/2)/`scale` - `Y`**2/(2*`scale`) - \
+        llf = sum((`endog`*`mu`-`mu`**2/2)/`scale` - `endog`**2/(2*`scale`) - \
             (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
-            nobs2 = Y.shape[0]/2.
-            SSR = ss(Y-self.fitted(mu))
+            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((Y*mu-mu**2/2)/scale-Y**2/(2*scale)-\
+            return np.sum((endog*mu-mu**2/2)/scale-endog**2/(2*scale)-\
                     .5*np.log(2*np.pi*scale))
 
-    def resid_anscombe(self, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         The Anscombe residuals for the Gaussian exponential family distribution
 
         Parameters
         ----------
-        Y : array
+        endog : array
             Endogenous response variable
         mu : array
             Fitted mean response variable
@@ -524,9 +524,9 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         --------
-        `resid_anscombe` = `Y` - `mu`
+        `resid_anscombe` = `endog` - `mu`
         """
-        return Y-mu
+        return endog-mu
 
 class Gamma(Family):
     """
@@ -573,13 +573,13 @@ def _clean(self, x):
         """
         return np.clip(x, FLOAT_EPS, np.inf)
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         """
         Gamma deviance function
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -593,18 +593,18 @@ def deviance(self, Y, mu, scale=1.):
 
         Notes
         -----
-        `deviance` = 2*sum((Y - mu)/mu - log(Y/mu))
+        `deviance` = 2*sum((endog - mu)/mu - log(endog/mu))
         """
-        Y_mu = self._clean(Y/mu)
-        return 2 * np.sum((Y - mu)/mu - np.log(Y_mu))
+        endog_mu = self._clean(endog/mu)
+        return 2 * np.sum((endog - mu)/mu - np.log(endog_mu))
 
-    def resid_dev(self, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """
         Gamma deviance residuals
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -618,18 +618,18 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         -----
-        `resid_dev` = sign(Y - mu) * sqrt(-2*(-(Y-mu)/mu + log(Y/mu)))
+        `resid_dev` = sign(endog - mu) * sqrt(-2*(-(endog-mu)/mu + log(endog/mu)))
         """
-        Y_mu = self._clean(Y/mu)
-        return np.sign(Y-mu) * np.sqrt(-2*(-(Y-mu)/mu + np.log(Y_mu)))
+        endog_mu = self._clean(endog/mu)
+        return np.sign(endog-mu) * np.sqrt(-2*(-(endog-mu)/mu + np.log(endog_mu)))
 
-    def loglike(self, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         Loglikelihood function for Gamma exponential family distribution.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -639,28 +639,28 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
         --------
-        llf = -1/scale * sum(Y/mu + log(mu) + (scale-1)*log(Y) + log(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(Y/mu+np.log(mu)+(scale-1)*np.log(Y)\
+        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, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         The Anscombe residuals for Gamma exponential family distribution
 
         Parameters
         ----------
-        Y : array
+        endog : array
             Endogenous response variable
         mu : array
             Fitted mean response variable
@@ -672,9 +672,9 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         -----
-        resid_anscombe = 3*(Y**(1/3.)-mu**(1/3.))/mu**(1/3.)
+        resid_anscombe = 3*(endog**(1/3.)-mu**(1/3.))/mu**(1/3.)
         """
-        return 3*(Y**(1/3.)-mu**(1/3.))/mu**(1/3.)
+        return 3*(endog**(1/3.)-mu**(1/3.))/mu**(1/3.)
 
 class Binomial(Family):
     """
@@ -713,7 +713,7 @@ def __init__(self, link=L.logit):  #, n=1.):
 # equal to n
         self.n = 1 # overwritten by initialize if needed but
                    # always used to initialize variance
-                   # since Y is assumed/forced to be (0,1)
+                   # since endog is assumed/forced to be (0,1)
         self.variance = V.Binomial(n=self.n)
         self.link = link()
 
@@ -727,38 +727,38 @@ def starting_mu(self, y):
         """
         return (y + .5)/2
 
-    def initialize(self, Y):
+    def initialize(self, endog):
         '''
         Initialize the response variable.
 
         Parameters
         ----------
-        Y : array
+        endog : array
             Endogenous response variable
 
         Returns
         --------
-        If `Y` is binary, returns `Y`
+        If `endog` is binary, returns `endog`
 
-        If `Y` is a 2d array, then the input is assumed to be in the format
+        If `endog` is a 2d array, then the input is assumed to be in the format
         (successes, failures) and
         successes/(success + failures) is returned.  And n is set to
         successes + failures.
         '''
-        if (Y.ndim > 1 and Y.shape[1] > 1):
-            y = Y[:,0]
-            self.n = Y.sum(1) # overwrite self.n for deviance below
+        if (endog.ndim > 1 and endog.shape[1] > 1):
+            y = endog[:,0]
+            self.n = endog.sum(1) # overwrite self.n for deviance below
             return y*1./self.n
         else:
-            return Y
+            return endog
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         '''
         Deviance function for either Bernoulli or Binomial data.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable (already transformed to a probability
             if appropriate).
         mu : array
@@ -777,28 +777,28 @@ def deviance(self, Y, 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 Y_i == 1.
-        and I_zero is an indicator function that evaluates to 1 if Y_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:
 
-        `deviance` = 2*sum(log(Y/mu) + (n-Y)*log((n-Y)/(n-mu)))
-        where Y and n are as defined in Binomial.initialize.
+        `deviance` = 2*sum(log(endog/mu) + (n-endog)*log((n-endog)/(n-mu)))
+        where endog and n are as defined in Binomial.initialize.
         '''
         if np.shape(self.n) == () and self.n == 1:
-            one = np.equal(Y,1)
+            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*(Y*np.log(Y/mu+1e-200)+(1-Y)*np.log((1-Y)/(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, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """
         Binomial deviance residuals
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -812,36 +812,36 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         -----
-        If `Y` is binary:
+        If `endog` is binary:
 
-        resid_dev = sign(Y-mu)*sqrt(-2*log(I_one*mu + I_zero*(1-mu)))
+        resid_dev = sign(endog-mu)*sqrt(-2*log(I_one*mu + I_zero*(1-mu)))
 
-        where I_one is an indicator function that evaluates as 1 if Y == 1
-        and I_zero is an indicator function that evaluates as 1 if Y == 0.
+        where I_one is an indicator function that evaluates as 1 if endog == 1
+        and I_zero is an indicator function that evaluates as 1 if endog == 0.
 
-        If `Y` is binomial:
+        If `endog` is binomial:
 
-        resid_dev = sign(Y-mu)*sqrt(2*n*(Y*log(Y/mu)+(1-Y)*log((1-Y)/(1-mu))))
+        resid_dev = sign(endog-mu)*sqrt(2*n*(endog*log(endog/mu)+(1-endog)*log((1-endog)/(1-mu))))
 
-        where Y and n are as defined in Binomial.initialize.
+        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(Y,1)
-            return np.sign(Y-mu)*np.sqrt(-2*np.log(one*mu+(1-one)*(1-mu)))\
+            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(Y-mu) * np.sqrt(2*self.n*(Y*np.log(Y/mu+1e-200)+(1-Y)*\
-                        np.log((1-Y)/(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, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         Loglikelihood function for Binomial exponential family distribution.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -851,38 +851,38 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
         --------
-        If `Y` is binary:
-        `llf` = scale*sum(Y*log(mu/(1-mu))+log(1-mu))
+        If `endog` is binary:
+        `llf` = scale*sum(endog*log(mu/(1-mu))+log(1-mu))
 
-        If `Y` is binomial:
+        If `endog` is binomial:
         `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 = Y*n with Y and n
+        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(Y*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=Y*self.n  #convert back to successes
+            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, Y, mu):
+    def resid_anscombe(self, endog, mu):
         '''
         The Anscombe residuals
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -894,7 +894,7 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         -----
-        sqrt(n)*(cox_snell(Y)-cox_snell(mu))/(mu**(1/6.)*(1-mu)**(1/6.))
+        sqrt(n)*(cox_snell(endog)-cox_snell(mu))/(mu**(1/6.)*(1-mu)**(1/6.))
 
         where cox_snell is defined as
         cox_snell(x) = betainc(2/3., 2/3., x)*betainc(2/3.,2/3.)
@@ -919,7 +919,7 @@ def resid_anscombe(self, Y, mu):
         '''
         cox_snell = lambda x: special.betainc(2/3., 2/3., x)\
                             *special.beta(2/3.,2/3.)
-        return np.sqrt(self.n)*(cox_snell(Y)-cox_snell(mu))/\
+        return np.sqrt(self.n)*(cox_snell(endog)-cox_snell(mu))/\
                         (mu**(1/6.)*(1-mu)**(1/6.))
 
 class InverseGaussian(Family):
@@ -960,13 +960,13 @@ def __init__(self, link=L.inverse_squared):
         self.variance = InverseGaussian.variance
         self.link = link()
 
-    def resid_dev(self, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         """
         Returns the deviance residuals for the inverse Gaussian family.
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -980,17 +980,17 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         -----
-        `dev_resid` = sign(Y-mu)*sqrt((Y-mu)**2/(Y*mu**2))
+        `dev_resid` = sign(endog-mu)*sqrt((endog-mu)**2/(endog*mu**2))
         """
-        return np.sign(Y-mu) * np.sqrt((Y-mu)**2/(Y*mu**2))/scale
+        return np.sign(endog-mu) * np.sqrt((endog-mu)**2/(endog*mu**2))/scale
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         """
         Inverse Gaussian deviance function
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -1004,17 +1004,17 @@ def deviance(self, Y, mu, scale=1.):
 
         Notes
         -----
-        `deviance` = sum((Y=mu)**2/(Y*mu**2))
+        `deviance` = sum((endog=mu)**2/(endog*mu**2))
         """
-        return np.sum((Y-mu)**2/(Y*mu**2))/scale
+        return np.sum((endog-mu)**2/(endog*mu**2))/scale
 
-    def loglike(self, Y, mu, scale=1.):
+    def loglike(self, endog, mu, scale=1.):
         """
         Loglikelihood function for inverse Gaussian distribution.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -1024,24 +1024,24 @@ def loglike(self, Y, mu, scale=1.):
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
         -----
-        `llf` = -(1/2.)*sum((Y-mu)**2/(Y*mu**2*scale) + log(scale*Y**3)\
+        `llf` = -(1/2.)*sum((endog-mu)**2/(endog*mu**2*scale) + log(scale*endog**3)\
                  + log(2*pi))
         """
-        return -.5 * np.sum((Y-mu)**2/(Y*mu**2*scale)\
-                + np.log(scale*Y**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, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         The Anscombe residuals for the inverse Gaussian distribution
 
         Parameters
         ----------
-        Y : array
+        endog : array
             Endogenous response variable
         mu : array
             Fitted mean response variable
@@ -1054,9 +1054,9 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         -----
-        `resid_anscombe` =  log(Y/mu)/sqrt(mu)
+        `resid_anscombe` =  log(endog/mu)/sqrt(mu)
         """
-        return np.log(Y/mu)/np.sqrt(mu)
+        return np.log(endog/mu)/np.sqrt(mu)
 
 class NegativeBinomial(Family):
     """
@@ -1116,13 +1116,13 @@ def _clean(self, x):
         """
         return np.clip(x, FLOAT_EPS, np.inf)
 
-    def deviance(self, Y, mu, scale=1.):
+    def deviance(self, endog, mu, scale=1.):
         """
         Returns the value of the deviance function.
 
         Parameters
         -----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -1148,23 +1148,23 @@ def deviance(self, Y, mu, scale=1.):
 
         piecewise_i = :math:`2 Y \\log(Y/\\mu)-2/\\alpha(1+\\alpha Y)*\\log((1+\\alpha Y)/(1+\\alpha\\mu))`
         """
-        iszero = np.equal(Y,0)
+        iszero = np.equal(endog,0)
         notzero = 1 - iszero
-        tmp = np.zeros(len(Y))
-        Y_mu = self._clean(Y/mu)
+        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*Y*np.log(Y_mu)-2/self.alpha*(1+self.alpha*Y)*\
-                np.log((1+self.alpha*Y)/(1+self.alpha*mu)))
+        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, Y, mu, scale=1.):
+    def resid_dev(self, endog, mu, scale=1.):
         '''
         Negative Binomial Deviance Residual
 
         Parameters
         ----------
-        Y : array-like
-            `Y` is the response variable
+        endog : array-like
+            `endog` is the response variable
         mu : array-like
             `mu` is the fitted value of the model
         scale : float, optional
@@ -1177,7 +1177,7 @@ def resid_dev(self, Y, mu, scale=1.):
 
         Notes
         -----
-        `resid_dev` = sign(Y-mu) * sqrt(piecewise)
+        `resid_dev` = sign(endog-mu) * sqrt(piecewise)
 
         where piecewise is defined as
         if :math:`Y_i = 0`:
@@ -1186,58 +1186,60 @@ def resid_dev(self, Y, mu, scale=1.):
         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))`
         '''
-        iszero = np.equal(Y,0)
+        iszero = np.equal(endog,0)
         notzero = 1 - iszero
-        tmp=np.zeros(len(Y))
+        tmp=np.zeros(len(endog))
         tmp = iszero*2*np.log(1+self.alpha*mu)/self.alpha
-        tmp += notzero*(2*Y*np.log(Y/mu)-2/self.alpha*(1+self.alpha*Y)*\
-                np.log((1+self.alpha*Y)/(1+self.alpha*mu)))
-        return np.sign(Y-mu)*np.sqrt(tmp)/scale
+        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, Y, fittedvalues=None):
+    def loglike(self, endog, lin_pred=None):
         """
         The loglikelihood function for the negative binomial family.
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
-        fittedvalues : array-like
-            The linear fitted values of the model.  This is dot(exog,params).
+        lin_pred : array-like
+            The linear predictor of the model.  This is dot(exog,params),
+            plus the offset if present.
 
         Returns
         -------
         llf : float
-            The value of the loglikelihood function evaluated at (Y,mu,scale)
+            The value of the loglikelihood function evaluated at (endog,mu,scale)
             as defined below.
 
         Notes
         -----
-        sum(Y*log(alpha*exp(fittedvalues)/(1+alpha*exp(fittedvalues))) -\
-                log(1+alpha*exp(fittedvalues))/alpha + constant)
+        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(Y + 1/alpha) - gammaln(Y + 1) - gammaln(1/alpha)
+        constant = gammaln(endog + 1/alpha) - gammaln(endog + 1) - gammaln(1/alpha)
         """
         # don't need to specify mu
-        if fittedvalues is None:
+        if lin_pred is None:
             raise AttributeError('The loglikelihood for the negative binomial \
-requires that the fitted values be provided via the `fittedvalues` keyword \
+requires that the fitted values be provided via the `lin_pred` keyword \
 argument.')
-        constant = special.gammaln(Y + 1/self.alpha) - special.gammaln(Y+1)\
+        constant = special.gammaln(endog + 1/self.alpha) - special.gammaln(endog+1)\
                     -special.gammaln(1/self.alpha)
-        return np.sum(Y*np.log(self.alpha*np.exp(fittedvalues)/\
-            (1 + self.alpha*np.exp(fittedvalues))) - \
-            np.log(1+self.alpha*np.exp(fittedvalues))/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)
 
-    def resid_anscombe(self, Y, mu):
+    def resid_anscombe(self, endog, mu):
         """
         The Anscombe residuals for the negative binomial family
 
         Parameters
         ----------
-        Y : array-like
+        endog : array-like
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
@@ -1249,13 +1251,13 @@ def resid_anscombe(self, Y, mu):
 
         Notes
         -----
-        `resid_anscombe` = (hyp2f1(-alpha*Y)-hyp2f1(-alpha*mu)+\
-                1.5*(Y**(2/3.)-mu**(2/3.)))/(mu+alpha*mu**2)**(1/6.)
+        `resid_anscombe` = (hyp2f1(-alpha*endog)-hyp2f1(-alpha*mu)+\
+                1.5*(endog**(2/3.)-mu**(2/3.)))/(mu+alpha*mu**2)**(1/6.)
 
         where hyp2f1 is the hypergeometric 2f1 function parameterized as
         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*Y)-hyp2f1(-self.alpha*mu)+1.5*(Y**(2/3.)-\
+        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/varfuncs.py b/statsmodels/genmod/families/varfuncs.py
--- a/statsmodels/genmod/families/varfuncs.py
+++ b/statsmodels/genmod/families/varfuncs.py
@@ -47,7 +47,7 @@ def __call__(self, mu):
 
 constant = VarianceFunction()
 constant.__doc__ = """
-The call method of constnat returns a constant variance, ie., a vector of ones.
+The call method of constant returns a constant variance, i.e., a vector of ones.
 
 constant is an alias of VarianceFunction()
 """
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
@@ -32,9 +32,8 @@
 __all__ = ['GLM']
 
 
-def _check_convergence(criterion, iteration, tol, maxiter):
-    return not ((np.fabs(criterion[iteration] - criterion[iteration-1]) > tol)
-                and iteration <= maxiter)
+def _check_convergence(criterion, iteration, tol):
+    return not (np.fabs(criterion[iteration] - criterion[iteration-1]) > tol)
 
 
 class GLM(base.LikelihoodModel):
@@ -52,7 +51,8 @@ class GLM(base.LikelihoodModel):
     exog : array-like
         A nobs x k array where `nobs` is the number of observations and `k`
         is the number of regressors. An intercept is not included by default
-        and should be added by the user. See `statsmodels.tools.add_constant`.
+        and should be added by the user (models specified using a formula
+        include an intercept by default). See `statsmodels.tools.add_constant`.
     family : family class instance
         The default is Gaussian.  To specify the binomial distribution
         family = sm.family.Binomial()
@@ -160,7 +160,7 @@ class GLM(base.LikelihoodModel):
         statsmodels.families.  Default is Gaussian.
     mu : array
         The mean response of the transformed variable.  `mu` is the value of
-        the inverse of the link function at eta, where eta is the linear
+        the inverse of the link function at lin_pred, where lin_pred is the linear
         predicted value of the WLS fit of the transformed variable.  `mu` is
         only available after fit is called.  See
         statsmodels.families.family.fitted of the distribution family for more
@@ -199,7 +199,7 @@ def __init__(self, endog, exog, family=None, offset=None, exposure=None,
             delattr(self, 'exposure')
         #things to remove_data
         self._data_attr.extend(['weights', 'pinv_wexog', 'mu', 'data_weights',
-                                ])
+                                '_offset_exposure'])
         # register kwds for __init__, offset and exposure are added by super
         self._init_keys.append('family')
 
@@ -428,18 +428,20 @@ def fit(self, start_params=None, maxiter=100, method='IRLS', tol=1e-8,
 
         # Construct a combined offset/exposure term.  Note that
         # exposure has already been logged if present.
-        offset = 0.
+        offset_exposure = 0.
         if hasattr(self, 'offset'):
-            offset = self.offset.copy()
+            offset_exposure = self.offset
         if hasattr(self, 'exposure'):
-            offset += self.exposure
+            offset_exposure = offset_exposure + self.exposure
+        self._offset_exposure = offset_exposure
 
         wlsexog = self.exog
         if start_params is None:
             mu = self.family.starting_mu(self.endog)
+            lin_pred = self.family.predict(mu)
         else:
-            mu = self.predict(start_params)
-        eta = self.family.predict(mu)
+            lin_pred = np.dot(wlsexog, start_params) + offset_exposure
+            mu = self.family.fitted(lin_pred)
         dev = self.family.deviance(self.endog, mu)
         if np.isnan(dev):
             raise ValueError("The first guess on the deviance function "
@@ -449,28 +451,35 @@ def fit(self, start_params=None, maxiter=100, method='IRLS', tol=1e-8,
         # first guess on the deviance is assumed to be scaled by 1.
         # params are none to start, so they line up with the deviance
         history = dict(params=[None, start_params], deviance=[np.inf, dev])
-        iteration = 0
-        converged = 0
+        converged = False
         criterion = history['deviance']
-        while not converged:
+        # This special case is used to get the likelihood for a specific
+        # params vector.
+        if maxiter == 0:
+            mu = self.family.fitted(lin_pred)
+            self.scale = self.estimate_scale(mu)
+            wls_results = lm.RegressionResults(self, start_params, None)
+            iteration = 0
+        for iteration in range(maxiter):
             self.weights = data_weights*self.family.weights(mu)
-            wlsendog = (eta + self.family.link.deriv(mu) * (self.endog-mu)
-                        - offset)
+            wlsendog = (lin_pred + self.family.link.deriv(mu) * (self.endog-mu)
+                        - offset_exposure)
             wls_results = lm.WLS(wlsendog, wlsexog, self.weights).fit()
-            eta = np.dot(self.exog, wls_results.params) + offset
-            mu = self.family.fitted(eta)
+            lin_pred = np.dot(self.exog, wls_results.params) + offset_exposure
+            mu = self.family.fitted(lin_pred)
             history = self._update_history(wls_results, mu, history)
             self.scale = self.estimate_scale(mu)
-            iteration += 1
             if endog.squeeze().ndim == 1 and np.allclose(mu - endog, 0):
                 msg = "Perfect separation detected, results not available"
                 raise PerfectSeparationError(msg)
-            converged = _check_convergence(criterion, iteration, tol, maxiter)
+            converged = _check_convergence(criterion, iteration, tol)
+            if converged:
+                break
         self.mu = mu
         glm_results = GLMResults(self, wls_results.params,
                                  wls_results.normalized_cov_params,
                                  self.scale)
-        history['iteration'] = iteration
+        history['iteration'] = iteration + 1
         glm_results.fit_history = history
         return GLMResultsWrapper(glm_results)
 
@@ -710,8 +719,10 @@ def llnull(self):
     def llf(self):
         _modelfamily = self.family
         if isinstance(_modelfamily, families.NegativeBinomial):
-            XB = np.dot(self.model.exog, self.params)
-            val = _modelfamily.loglike(self.model.endog, fittedvalues=XB)
+            lin_pred = np.dot(self.model.exog, self.params) +\
+                       self.model._offset_exposure
+            val = _modelfamily.loglike(self.model.endog,
+                                       lin_pred=lin_pred)
         else:
             val = _modelfamily.loglike(self._endog, self.mu, scale=self.scale)
         return val


Test patch:
diff --git a/statsmodels/base/tests/test_shrink_pickle.py b/statsmodels/base/tests/test_shrink_pickle.py
--- a/statsmodels/base/tests/test_shrink_pickle.py
+++ b/statsmodels/base/tests/test_shrink_pickle.py
@@ -76,8 +76,9 @@ def test_remove_data_pickle(self):
         #for testing attach res
         self.res = res
 
-        #Note: 10000 is just a guess for the limit on the length of the pickle
-        assert_(l < 10000, msg='pickle length not %d < %d' % (l, 10000))
+        #Note: l_max is just a guess for the limit on the length of the pickle
+        l_max = 20000
+        assert_(l < l_max, msg='pickle length not %d < %d' % (l, l_max))
 
         pred3 = results.predict(xf, **pred_kwds)
         np.testing.assert_equal(pred3, pred1)
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
@@ -603,6 +603,24 @@ def test_glm_start_params():
     res = mod.fit(start_params=[-4, -5])
     np.testing.assert_almost_equal(res.params, [-4.60305022, -5.29634545], 6)
 
+
+def test_loglike_no_opt():
+    # see 1728
+
+    y = np.asarray([0, 1, 0, 0, 1, 1, 0, 1, 1, 1])
+    x = np.arange(10, dtype=np.float64)
+
+    def llf(params):
+        lin_pred = params[0] + params[1]*x
+        pr = 1 / (1 + np.exp(-lin_pred))
+        return np.sum(y*np.log(pr) + (1-y)*np.log(1-pr))
+
+    for params in [0,0], [0,1], [0.5,0.5]:
+        mod = sm.GLM(y, sm.add_constant(x), family=sm.families.Binomial())
+        res = mod.fit(start_params=params, maxiter=0)
+        like = llf(params)
+        assert_almost_equal(like, res.llf)
+
 if __name__=="__main__":
     #run_module_suite()
     #taken from Fernando Perez: