Repository: statsmodels/statsmodels

Issue:
BUG: GLM fit_gradient doesn't use max_start_irls, if > 0
https://github.com/statsmodels/statsmodels/pull/2805#issuecomment-185932262

I'm adding a fix as part of #2805, which should be merged soon, otherwise we make a new PR with it.

consequence of fixing this:

one precision issue in unit tests
two family link combinations fail with only max_start_irls=1
skipping these two cases still leaves 4 convergence warnings that don't cause test failures.

edit:
more detailed result while investigating in #2805 
- on my computer the tol to pass should be rtol=6e-4 **or** atol=5e-6 
- after increasing to atol=5e-6 tests that converge pass
- The next fail is serious because it has a convergence failure for Gaussian with inverse_power link
- The second convergence failure is InverseGaussian with log link
  both are for the case `max_start_irls=1, start_params=None`
- more experimenting:  increasing max_start_irls=3 ( current default) 
  - Gaussian with inverse_power still fails
  - all other finish clean, including InverseGaussian loglink, no convergence warnings for any case
- there are already two skips that fail even with start_params provided
  - NegativeBinomial with link inverse_squared
  - InverseGaussian with link inverse_squared

I'm going to change max_start_irls to 3 in the unit tests

cross-link to our examples with convergence failures or problems #1649



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
@@ -76,7 +76,7 @@ def __init__(self, link, variance):
         self.variance = variance
 
     def starting_mu(self, y):
-        """
+        r"""
         Starting value for mu in the IRLS algorithm.
 
         Parameters
@@ -91,7 +91,9 @@ def starting_mu(self, y):
 
         Notes
         -----
-        mu_0 = (endog + mean(endog))/2.
+        .. math::
+
+           \mu_0 = (Y + \overline{Y})/2
 
         Notes
         -----
@@ -100,7 +102,7 @@ def starting_mu(self, y):
         return (y + y.mean())/2.
 
     def weights(self, mu):
-        """
+        r"""
         Weights for IRLS steps
 
         Parameters
@@ -115,13 +117,15 @@ def weights(self, mu):
 
         Notes
         -----
-        `w` = 1 / (link'(`mu`)**2 * variance(`mu`))
+        .. math::
+
+           w = 1 / (g'(\mu)^2  * Var(\mu))
         """
         return 1. / (self.link.deriv(mu)**2 * self.variance(mu))
 
-    def deviance(self, endog, mu, scale=1.):
-        """
-        Deviance of (endog,mu) pair.
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
+        The deviance function evaluated at (endog,mu,freq_weights,mu).
 
         Deviance is usually defined as twice the loglikelihood ratio.
 
@@ -131,8 +135,10 @@ def deviance(self, endog, mu, scale=1.):
             The endogenous response variable
         mu : array-like
             The inverse of the link function at the linear predicted values.
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
@@ -145,14 +151,15 @@ def deviance(self, endog, mu, scale=1.):
 
         .. math::
 
-           \sum_i(2 loglike(y_i, y_i) - 2 * loglike(y_i, mu_i)) / scale
+           D = \sum_i (2 * freq\_weights_i * llf(Y_i, Y_i) - 2 *
+               llf(Y_i, \mu_i)) / scale
 
         where y is the endogenous variable. The deviance functions are
         analytically defined for each family.
         """
         raise NotImplementedError
 
-    def resid_dev(self, endog, mu, scale=1.):
+    def resid_dev(self, endog, mu, freq_weights=1., scale=1.):
         """
         The deviance residuals
 
@@ -162,8 +169,11 @@ def resid_dev(self, endog, mu, scale=1.):
             The endogenous response variable
         mu : array
             The inverse of the link function at the linear predicted values.
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -211,7 +221,7 @@ def predict(self, mu):
         """
         return self.link(mu)
 
-    def loglike(self, endog, mu, scale=1.):
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
         """
         The log-likelihood function in terms of the fitted mean response.
 
@@ -221,13 +231,16 @@ def loglike(self, endog, mu, scale=1.):
             Usually the endogenous response variable.
         `mu` : array
             Usually but not always the fitted mean response variable.
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float
-            The scale parameter
+            The scale parameter. The default is 1.
 
         Returns
         -------
         llf : float
-            The value of the loglikelihood evaluated at (endog,mu).
+            The value of the loglikelihood evaluated at
+            (endog,mu,freq_weights,scale) as defined below.
         Notes
         -----
         This is defined for each family.  endog and mu are not restricted to
@@ -297,8 +310,8 @@ def _clean(self, x):
         """
         return np.clip(x, FLOAT_EPS, np.inf)
 
-    def resid_dev(self, endog, mu, scale=1.):
-        """Poisson deviance residual
+    def resid_dev(self, endog, mu, freq_weights=1., scale=1.):
+        r"""Poisson deviance residual
 
         Parameters
         ----------
@@ -306,8 +319,11 @@ def resid_dev(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -316,15 +332,18 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        resid_dev = sign(endog-mu)*sqrt(2*endog*log(endog/mu)-2*(endog-mu))
+        .. math::
+
+           resid\_dev_i = sign(Y_i - \mu_i) * \sqrt{2 * freq\_weights_i *
+                          (Y_i * \log(Y_i / \mu_i) - (Y_i - \mu_i))} / scale
         """
-        endog_mu = self._clean(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 * freq_weights * (endog * np.log(endog_mu) -
+                                            (endog - mu))) / scale)
 
-    def deviance(self, endog, mu, scale=1.):
-        '''
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
+        r'''
         Poisson deviance function
 
         Parameters
@@ -333,25 +352,30 @@ def deviance(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
         deviance : float
-            The deviance function at (endog,mu) as defined below.
+            The deviance function at (endog,mu,freq_weights,scale) as defined
+            below.
 
         Notes
         -----
         If a constant term is included it is defined as
 
-        :math:`deviance = 2*\\sum_{i}(Y*\\log(Y/\\mu))`
+        .. math::
+
+           D = 2 * \sum_i (freq\_weights_i * Y_i * \log(Y_i / \mu_i))/ scale
         '''
-        endog_mu = self._clean(endog/mu)
-        return 2*np.sum(endog*np.log(endog_mu))/scale
+        endog_mu = self._clean(endog / mu)
+        return 2 * np.sum(endog * freq_weights * np.log(endog_mu)) / scale
 
-    def loglike(self, endog, mu, scale=1.):
-        """
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         The log-likelihood function in terms of the fitted mean response.
 
         Parameters
@@ -360,6 +384,8 @@ def loglike(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
             The scale parameter, defaults to 1.
 
@@ -367,17 +393,21 @@ def loglike(self, endog, mu, scale=1.):
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         -----
-        llf = scale * sum(-mu + endog*log(mu) - gammaln(endog+1))
-        where gammaln is the log gamma function
+        .. math::
+
+           llf = scale * \sum_i freq\_weights_i * (Y_i * \log(\mu_i) - \mu_i -
+                 \ln \Gamma(Y_i + 1))
         """
-        return scale * np.sum(-mu + endog*np.log(mu)-special.gammaln(endog+1))
+        loglike = np.sum(freq_weights * (endog * np.log(mu) - mu -
+                         special.gammaln(endog + 1)))
+        return scale * loglike
 
     def resid_anscombe(self, endog, mu):
-        """
+        r"""
         Anscombe residuals for the Poisson exponential family distribution
 
         Parameters
@@ -394,13 +424,11 @@ def resid_anscombe(self, endog, mu):
 
         Notes
         -----
-        resid_anscombe is defined
-
-        .. math:
+        .. math::
 
-           (3/2.)*(endog^{2/3.} - \\mu**(2/3.))/\\mu^{1/6.}
+           resid\_anscombe_i = (3/2) * (Y_i^{2/3} - \mu_i^{2/3}) / \mu_i^{1/6}
         """
-        return (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):
@@ -436,8 +464,8 @@ def __init__(self, link=L.identity):
         self.variance = Gaussian.variance
         self.link = link()
 
-    def resid_dev(self, endog, mu, scale=1.):
-        """
+    def resid_dev(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         Gaussian deviance residuals
 
         Parameters
@@ -446,8 +474,11 @@ def resid_dev(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -456,13 +487,17 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         --------
-        `resid_dev` = (`endog` - `mu`)/sqrt(variance(`mu`))
+        .. math::
+
+           resid\_dev_i = freq\_weights_i * (Y_i - \mu_i) / \sqrt{Var(\mu_i)} /
+                          scale
         """
 
-        return (endog - mu) / np.sqrt(self.variance(mu))/scale
+        return (np.sqrt(freq_weights) * (endog - mu) /
+                np.sqrt(self.variance(mu)) / scale)
 
-    def deviance(self, endog, mu, scale=1.):
-        """
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         Gaussian deviance function
 
         Parameters
@@ -471,22 +506,27 @@ def deviance(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
         deviance : float
-            The deviance function at (endog,mu) as defined below.
+            The deviance function at (endog,mu,freq_weights,scale)
+            as defined below.
 
         Notes
         --------
-        `deviance` = sum((endog-mu)**2)
-        """
-        return np.sum((endog-mu)**2)/scale
+        .. math::
 
-    def loglike(self, endog, mu, scale=1.):
+           D = \sum_i freq\_weights_i * (Y_i - \mu_i)^2 / scale
         """
+        return np.sum((freq_weights * (endog - mu)**2)) / scale
+
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         The log-likelihood in terms of the fitted mean response.
 
         Parameters
@@ -495,6 +535,8 @@ def loglike(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
             Scales the loglikelihood function. The default is 1.
 
@@ -502,34 +544,43 @@ def loglike(self, endog, mu, scale=1.):
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         -----
         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((endog-link^(-1)(mu))**2)
+
+        .. math::
+
+           llf = -nobs / 2 * (\log(SSR) + (1 + \log(2 \pi / nobs)))
+
+        where
+
+        .. math::
+           SSR = \sum_i (Y_i - g^{-1}(\mu_i))^2
 
         If the links is not the identity link then the loglikelihood
         function is defined as
-        llf = sum((`endog`*`mu`-`mu`**2/2)/`scale` - `endog`**2/(2*`scale`) - \
-            (1/2.)*log(2*pi*`scale`))
+
+        .. math::
+
+           llf = \sum_i freq\_weights_i * ((Y_i * \mu_i - \mu_i^2 / 2) / scale-
+                 Y^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 = endog.shape[0]/2.
+            nobs2 = np.sum(freq_weights, axis=0) / 2.
             SSR = np.sum((endog-self.fitted(mu))**2, axis=0)
             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 np.sum(freq_weights * ((endog * mu - mu**2/2)/scale -
+                          endog**2/(2 * scale) - .5*np.log(2 * np.pi * scale)))
 
     def resid_anscombe(self, endog, mu):
-        """
+        r"""
         The Anscombe residuals for the Gaussian exponential family distribution
 
         Parameters
@@ -546,9 +597,11 @@ def resid_anscombe(self, endog, mu):
 
         Notes
         --------
-        `resid_anscombe` = `endog` - `mu`
+        .. math::
+
+           resid\_anscombe_i = Y_i - \mu_i
         """
-        return endog-mu
+        return endog - mu
 
 
 class Gamma(Family):
@@ -596,8 +649,8 @@ def _clean(self, x):
         """
         return np.clip(x, FLOAT_EPS, np.inf)
 
-    def deviance(self, endog, mu, scale=1.):
-        """
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         Gamma deviance function
 
         Parameters
@@ -606,8 +659,10 @@ def deviance(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
@@ -616,12 +671,15 @@ def deviance(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `deviance` = 2*sum((endog - mu)/mu - log(endog/mu))
+        .. math::
+
+           D = 2 * \sum_i freq\_weights_i * ((Y_i - \mu_i)/\mu_i - \log(Y_i /
+               \mu_i))
         """
         endog_mu = self._clean(endog/mu)
-        return 2 * np.sum((endog - mu)/mu - np.log(endog_mu))
+        return 2*np.sum(freq_weights*((endog-mu)/mu-np.log(endog_mu)))
 
-    def resid_dev(self, endog, mu, scale=1.):
+    def resid_dev(self, endog, mu, freq_weights=1., scale=1.):
         r"""
         Gamma deviance residuals
 
@@ -631,8 +689,11 @@ def resid_dev(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -641,18 +702,18 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `resid_dev` is defined
-
-        .. math:
+        .. math::
 
-           sign(endog - \mu) * \sqrt{-2*(-(endog-\mu)/\mu + \log(endog/\mu))}
+           resid\_dev_i = sign(Y_i - \mu_i) \sqrt{-2 * freq\_weights_i *
+                          (-(Y_i - \mu_i) / \mu_i + \log(Y_i / \mu_i))}
         """
-        endog_mu = self._clean(endog/mu)
-        return np.sign(endog - mu) * np.sqrt(-2 * (-(endog - mu)/mu +
-                                                   np.log(endog_mu)))
+        endog_mu = self._clean(endog / mu)
+        return np.sign(endog - mu) * np.sqrt(-2 * freq_weights *
+                                             (-(endog - mu)/mu +
+                                              np.log(endog_mu)))
 
-    def loglike(self, endog, mu, scale=1.):
-        """
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         The log-likelihood function in terms of the fitted mean response.
 
         Parameters
@@ -661,6 +722,8 @@ def loglike(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
             The default is 1.
 
@@ -668,23 +731,26 @@ def loglike(self, endog, mu, scale=1.):
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         --------
-        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.
+        .. math::
+
+           llf = -1 / scale * \sum_i freq\_weights_i * (Y_i / \mu_i+\log(\mu_i)+
+                 (scale -1) * \log(Y) + \log(scale) + scale *
+                 \ln \Gamma(1 / scale))
         """
-        return - 1./scale * np.sum(endog/mu + np.log(mu) + (scale - 1) *
-                                   np.log(endog) + np.log(scale) + scale *
-                                   special.gammaln(1./scale))
+        return - 1./scale * np.sum((endog/mu + np.log(mu) + (scale - 1) *
+                                    np.log(endog) + np.log(scale) + scale *
+                                   special.gammaln(1./scale)) * freq_weights)
+
         # 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):
-        """
+        r"""
         The Anscombe residuals for Gamma exponential family distribution
 
         Parameters
@@ -701,9 +767,11 @@ def resid_anscombe(self, endog, mu):
 
         Notes
         -----
-        resid_anscombe = 3*(endog**(1/3.)-mu**(1/3.))/mu**(1/3.)
+        .. math::
+
+           resid\_anscombe_i = 3 * (Y_i^{1/3} - \mu_i^{1/3}) / \mu_i^{1/3}
         """
-        return 3*(endog**(1/3.)-mu**(1/3.))/mu**(1/3.)
+        return 3 * (endog**(1/3.) - mu**(1/3.)) / mu**(1/3.)
 
 
 class Binomial(Family):
@@ -742,7 +810,7 @@ class Binomial(Family):
     safe_links = [L.Logit, L.CDFLink]
 
     def __init__(self, link=L.logit):  # , n=1.):
-        # TODO: it *should* work for a constant n>1 actually, if data_weights
+        # TODO: it *should* work for a constant n>1 actually, if freq_weights
         # is equal to n
         self.n = 1
         # overwritten by initialize if needed but always used to initialize
@@ -753,14 +821,11 @@ def __init__(self, link=L.logit):  # , n=1.):
     def starting_mu(self, y):
         """
         The starting values for the IRLS algorithm for the Binomial family.
-
-        A good choice for the binomial family is
-
-        starting_mu = (y + .5)/2
+        A good choice for the binomial family is :math:`\mu_0 = (Y_i + 0.5)/2`
         """
         return (y + .5)/2
 
-    def initialize(self, endog):
+    def initialize(self, endog, freq_weights):
         '''
         Initialize the response variable.
 
@@ -778,15 +843,18 @@ def initialize(self, endog):
         successes/(success + failures) is returned.  And n is set to
         successes + failures.
         '''
+        # if not np.all(np.asarray(freq_weights) == 1):
+        #     self.variance = V.Binomial(n=freq_weights)
         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
+            # overwrite self.freq_weights for deviance below
+            self.n = endog.sum(1)
+            return y*1./self.n, self.n
         else:
-            return endog
+            return endog, np.ones(endog.shape[0])
 
-    def deviance(self, endog, mu, scale=1.):
-        '''
+    def deviance(self, endog, mu, freq_weights=1, scale=1.):
+        r'''
         Deviance function for either Bernoulli or Binomial data.
 
         Parameters
@@ -796,8 +864,10 @@ def deviance(self, endog, mu, scale=1.):
             if appropriate).
         mu : array
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         --------
@@ -808,30 +878,37 @@ def deviance(self, endog, mu, scale=1.):
         -----
         If the endogenous variable is binary:
 
-        `deviance` = -2*sum(I_one * log(mu) + (I_zero)*log(1-mu))
+        .. math::
+
+           D = -2 * \sum_i freq\_weights * (I_{1,i} * \log(\mu_i) + I_{0,i} *
+               \log(1 - \mu_i))
 
-        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 :math:`I_{1,i}` is an indicator function that evalueates to 1 if
+        :math:`Y_i = 1`. and :math:`I_{0,i}` is an indicator function that
+        evaluates to 1 if :math:`Y_i = 0`.
 
         If the model is ninomial:
 
-        `deviance` = 2*sum(log(endog/mu) + (n-endog)*log((n-endog)/(n-mu)))
-        where endog and n are as defined in Binomial.initialize.
+        .. math::
+
+           D = 2 * \sum_i freq\_weights * (\log(Y_i / \mu_i) + (n_i - Y_i) *
+               \log((n_i - Y_i) / n_i - \mu_i))
+
+        where :math:`Y_i` and :math:`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))
+            return -2 * np.sum((one * np.log(mu + 1e-200) + (1-one) *
+                               np.log(1 - mu + 1e-200)) * freq_weights)
 
         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 * freq_weights *
+                              (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.):
-        """
+    def resid_dev(self, endog, mu, freq_weights=1, scale=1.):
+        r"""
         Binomial deviance residuals
 
         Parameters
@@ -840,8 +917,11 @@ def resid_dev(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -850,34 +930,43 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        If `endog` is binary:
+        If the endogenous variable is binary:
+
+        .. math::
+
+           resid\_dev_i = sign(Y_i - \mu_i) * \sqrt{-2 * freq\_weights_i *
+                          \log(I_{1,i} * \mu_i + I_{0,i} * (1 - \mu_i))}
 
-        resid_dev = sign(endog-mu)*sqrt(-2*log(I_one*mu + I_zero*(1-mu)))
+        where :math:`I_{1,i}` is an indicator function that evalueates to 1 if
+        :math:`Y_i = 1`. and :math:`I_{0,i}` is an indicator function that
+        evaluates to 1 if :math:`Y_i = 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 the endogenous variable is binomial:
 
-        If `endog` is binomial:
+        .. math::
 
-        resid_dev = sign(endog - mu) * sqrt(2 * n * (endog * log(endog/mu) +
-                    (1 - endog) * log((1 - endog)/(1 - mu))))
+           resid\_dev_i = sign(Y_i - \mu_i) \sqrt{2 * freq\_weights * n_i *
+                          (Y_i * \log(Y_i / \mu_i) + (1 - Y_i) *
+                          \log(1 - Y_i)/(1 - \mu_i))}
 
-        where endog and n are as defined in Binomial.initialize.
+        where :math:`Y_i` and :math:`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
+            return np.sign(endog-mu)*np.sqrt(-2 * freq_weights *
+                                             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)
+                    np.sqrt(2 * freq_weights * 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.):
-        """
+    def loglike(self, endog, mu, freq_weights=1, scale=1.):
+        r"""
         The log-likelihood function in terms of the fitted mean response.
 
         Parameters
@@ -886,6 +975,8 @@ def loglike(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
             Not used for the Binomial GLM.
 
@@ -893,32 +984,40 @@ def loglike(self, endog, mu, scale=1.):
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         --------
-        If `endog` is binary:
-        `llf` = scale*sum(endog*log(mu/(1-mu))+log(1-mu))
+        If the endogenous variable is binary:
+
+        .. math::
+
+         llf = scale * \sum_i (y_i * \log(\mu_i/(1-\mu_i)) + \log(1-\mu_i)) *
+               freq\_weights_i
+
+        If the endogenous variable 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)
+        .. math::
+
+           llf = scale * \sum_i freq\_weights_i * (\ln \Gamma(n+1) -
+                 \ln \Gamma(y_i + 1) - \ln \Gamma(n_i - y_i +1) + y_i *
+                 \log(\mu_i / (1 - \mu_i)) + n * \log(1 - \mu_i))
 
-        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
+        where :math:`y_i = Y_i * n_i` with :math:`Y_i` and :math:`n_i` as
+        defined in Binomial initialize.  This simply makes :math:`y_i` 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)) * freq_weights)
         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))
+            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)) * freq_weights)
 
     def resid_anscombe(self, endog, mu):
         '''
@@ -1006,8 +1105,8 @@ def __init__(self, link=L.inverse_squared):
         self.variance = InverseGaussian.variance
         self.link = link()
 
-    def resid_dev(self, endog, mu, scale=1.):
-        """
+    def resid_dev(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         Returns the deviance residuals for the inverse Gaussian family.
 
         Parameters
@@ -1016,8 +1115,11 @@ def resid_dev(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         -------
@@ -1026,12 +1128,16 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `dev_resid` = sign(endog-mu)*sqrt((endog-mu)**2/(endog*mu**2))
-        """
-        return np.sign(endog-mu) * np.sqrt((endog-mu)**2/(endog*mu**2))/scale
+        .. math::
 
-    def deviance(self, endog, mu, scale=1.):
+           resid\_dev_i = sign(Y_i - \mu_i) \sqrt {freq\_weights_i *
+                          (Y_i - \mu_i)^2 / (Y_i * \mu_i^2)} / scale
         """
+        return np.sign(endog-mu) * np.sqrt(freq_weights *
+                                           (endog-mu)**2/(endog*mu**2))/scale
+
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         Inverse Gaussian deviance function
 
         Parameters
@@ -1040,8 +1146,10 @@ def deviance(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
@@ -1050,12 +1158,15 @@ def deviance(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `deviance` = sum((endog=mu)**2/(endog*mu**2))
-        """
-        return np.sum((endog-mu)**2/(endog*mu**2))/scale
+        .. math::
 
-    def loglike(self, endog, mu, scale=1.):
+           D = \sum_i freq\_weights_i * ((Y_i - \mu_i)^2 / (Y_i *\mu_i^2)) /
+               scale
         """
+        return np.sum(freq_weights*(endog-mu)**2/(endog*mu**2))/scale
+
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
+        r"""
         The log-likelihood function in terms of the fitted mean response.
 
         Parameters
@@ -1064,6 +1175,8 @@ def loglike(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
             The default is 1.
 
@@ -1071,18 +1184,21 @@ def loglike(self, endog, mu, scale=1.):
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         -----
-        `llf` = -(1/2.)*sum((endog-mu)**2/(endog*mu**2*scale)
-                 + log(scale*endog**3) + log(2*pi))
+        .. math::
+
+           llf = -1/2 * \sum_i freq\_weights_i * ((Y_i - \mu_i)^2 / (Y_i *
+                 \mu_i * scale) + \log(scale * Y_i^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)) *
+                            freq_weights)
 
     def resid_anscombe(self, endog, mu):
-        """
+        r"""
         The Anscombe residuals for the inverse Gaussian distribution
 
         Parameters
@@ -1100,9 +1216,11 @@ def resid_anscombe(self, endog, mu):
 
         Notes
         -----
-        `resid_anscombe` =  log(endog/mu)/sqrt(mu)
+        .. math::
+
+           resid\_anscombe_i = \log(Y_i / \mu_i) / \sqrt{\mu_i}
         """
-        return np.log(endog/mu)/np.sqrt(mu)
+        return np.log(endog / mu) / np.sqrt(mu)
 
 
 class NegativeBinomial(Family):
@@ -1164,7 +1282,7 @@ def _clean(self, x):
         """
         return np.clip(x, FLOAT_EPS, np.inf)
 
-    def deviance(self, endog, mu, scale=1.):
+    def deviance(self, endog, mu, freq_weights=1., scale=1.):
         r"""
         Returns the value of the deviance function.
 
@@ -1174,8 +1292,10 @@ def deviance(self, endog, mu, scale=1.):
             Endogenous response variable
         mu : array-like
             Fitted mean response variable
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional scale argument
+            An optional scale argument. The default is 1.
 
         Returns
         -------
@@ -1184,34 +1304,29 @@ def deviance(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `deviance` = sum(piecewise)
-
-        where piecewise is defined as
-
-        If :math:`Y_{i} == 0`:
+        :math:`D = \sum_i piecewise_i` where :math:`piecewise_i` is defined as:
 
-        .. math::
+        If :math:`Y_{i} = 0`:
 
-           piecewise_i = 2\log(1+\alpha*\mu)/\alpha
+        :math:`piecewise_i = 2* \log(1 + \alpha * \mu_i) / \alpha`
 
         If :math:`Y_{i} > 0`:
 
-        .. math::
-
-           piecewise_i = math2 Y \log(Y/\mu)-2/\alpha(1+\alpha Y) * \log((1+\alpha Y)/(1+\alpha\mu))
+        :math:`piecewise_i = 2 * Y_i * \log(Y_i / \mu_i) - (2 / \alpha) *
+            (1 + \alpha * Y_i) * \ln(1 + \alpha * Y_i) / (1 + \alpha * \mu_i)`
         """
         iszero = np.equal(endog, 0)
         notzero = 1 - iszero
         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) *
+        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
+        return np.sum(freq_weights * tmp) / scale
 
-    def resid_dev(self, endog, mu, scale=1.):
-        r'''
+    def resid_dev(self, endog, mu, freq_weights=1, scale=1.):
+        r"""
         Negative Binomial Deviance Residual
 
         Parameters
@@ -1220,8 +1335,11 @@ def resid_dev(self, endog, mu, scale=1.):
             `endog` is the response variable
         mu : array-like
             `mu` is the fitted value of the model
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float, optional
-            An optional argument to divide the residuals by scale
+            An optional argument to divide the residuals by scale. The default
+            is 1.
 
         Returns
         --------
@@ -1230,34 +1348,32 @@ def resid_dev(self, endog, mu, scale=1.):
 
         Notes
         -----
-        `resid_dev` = sign(endog-mu) * sqrt(piecewise)
+        :math:`resid\_dev_i = sign(Y_i-\mu_i) * \sqrt{piecewise_i}`
 
-        where piecewise is defined as
+        where :math:`piecewise_i` is defined as
 
         If :math:`Y_i = 0`:
 
-        .. math::
-
-           piecewise_i = 2*log(1+alpha*mu)/alpha
+        :math:`piecewise_i = 2 * \log(1 + \alpha * \mu_i)/ \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_i * \log(Y_i / \mu_i) - (2 / \alpha) *
+        (1 + \alpha * Y_i) * \log((1 + \alpha * Y_i) / (1 + \alpha * \mu_i))`
+        """
         iszero = np.equal(endog, 0)
         notzero = 1 - iszero
-        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 *
+        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)))
-        return np.sign(endog - mu) * np.sqrt(tmp)/scale
+        return np.sign(endog - mu) * np.sqrt(freq_weights * tmp) / scale
 
-    def loglike(self, endog, mu, scale):
-        """
+    def loglike(self, endog, mu, freq_weights=1., scale=1.):
+        # TODO: Check weights
+        r"""
         The log-likelihood function in terms of the fitted mean response.
 
         Parameters
@@ -1266,32 +1382,42 @@ def loglike(self, endog, mu, scale):
             Endogenous response variable
         mu : array-like
             The fitted mean response values
+        freq_weights : array-like
+            1d array of frequency weights. The default is 1.
         scale : float
-            The scale parameter
+            The scale parameter. The default is 1.
 
         Returns
         -------
         llf : float
             The value of the loglikelihood function evaluated at
-            (endog,mu,scale) as defined below.
+            (endog,mu,freq_weights,scale) as defined below.
 
         Notes
         -----
-        sum(endog*log(alpha*exp(lin_pred)/(1+alpha*exp(lin_pred))) -
-                log(1+alpha*exp(lin_pred))/alpha + constant)
+        Defined as:
+
+        .. math::
+
+           llf = \sum_i freq\_weights_i * (Y_i * \log{(\alpha * e^{\eta_i} /
+                 (1 + \alpha * e^{\eta_i}))} - \log{(1 + \alpha * e^{\eta_i})}/
+                 \alpha + Constant)
 
-        where constant is defined as::
+        where :math:`Constant` is defined as:
+
+        .. math::
 
-           constant = gammaln(endog + 1/alpha) - gammaln(endog + 1) -
-                      gammaln(1/alpha)
+           Constant = \ln \Gamma{(Y_i + 1/ \alpha )} - \ln \Gamma(Y_i + 1) -
+                      \ln \Gamma{(1/ \alpha )}
         """
         lin_pred = self._link(mu)
-        constant = special.gammaln(endog + 1/self.alpha) - special.gammaln(endog+1)\
-                    -special.gammaln(1/self.alpha)
+        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 /
+        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))
+                      np.log(1 + self.alpha * exp_lin_pred) /
+                      self.alpha + constant) * freq_weights)
 
     def resid_anscombe(self, endog, mu):
         """
@@ -1318,7 +1444,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)
+        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.))
+                 1.5 * ( endog**(2 / 3.) - mu**(2 / 3.))) /
+                (mu + self.alpha * mu**2)**(1 / 6.))
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
@@ -66,6 +66,19 @@ class GLM(base.LikelihoodModel):
         family = sm.family.Binomial()
         Each family can take a link instance as an argument.  See
         statsmodels.family.family for more information.
+    offset : array-like or None
+        An offset to be included in the model.  If provided, must be
+        an array whose length is the number of rows in exog.
+    exposure : array-like or None
+        Log(exposure) will be added to the linear prediction in the model. Exposure
+        is only valid if the log link is used. If provided, it must be an array
+        with the same length as endog.
+    freq_weights : array-like
+        1d array of frequency weights. The default is None. If None is selected
+        or a blank value, then the algorithm will replace with an array of 1's
+        with length equal to the endog.
+        WARNING: Using weights is not verified yet for all possible options
+        and results, see Notes.
     %(extra_params)s
 
     Attributes
@@ -80,8 +93,12 @@ class GLM(base.LikelihoodModel):
         See Parameters.
     family : family class instance
         A pointer to the distribution family of the model.
+    freq_weights : array
+        See Parameters.
     mu : array
         The estimated mean response of the transformed variable.
+    n_trials : array
+        See Parameters.
     normalized_cov_params : array
         `p` x `p` normalized covariance of the design / exogenous data.
     pinv_wexog : array
@@ -93,6 +110,7 @@ class GLM(base.LikelihoodModel):
     weights : array
         The value of the weights after the last iteration of fit.
 
+
     Examples
     --------
     >>> import statsmodels.api as sm
@@ -140,6 +158,18 @@ class GLM(base.LikelihoodModel):
     Endog and exog are references so that if the data they refer to are already
     arrays and these arrays are changed, endog and exog will change.
 
+    Using frequency weights: Frequency weights produce the same results as repeating
+    observations by the frequencies (if those are integers). This is verified for all
+    basic results with nonrobust or heteroscedasticity robust ``cov_type``. Other
+    robust covariance types have not yet been verified, and at least the small sample
+    correction is currently not based on the correct total frequency count.
+    It is not yet desided whether all the different types of residuals will be
+    based on weighted residuals. Currently, deviance and pearson residuals,
+    as well as working and response residuals are weighted, while Anscombe
+    residuals are unweighted. Consequently, Pearson and deviance residuals
+    provide a correct measure for the scale and dispersion, but will be
+    proportional to the frequency weights for outlier measures.
+
 
     **Attributes**
 
@@ -151,16 +181,20 @@ class GLM(base.LikelihoodModel):
         Residual degrees of freedom is equal to the number of observation n
         minus the number of regressors p.
     endog : array
-        See above.  Note that endog is a reference to the data so that if
+        See above.  Note that `endog` is a reference to the data so that if
         data is already an array and it is changed, then `endog` changes
         as well.
     exposure : array-like
         Include ln(exposure) in model with coefficient constrained to 1. Can
         only be used if the link is the logarithm function.
     exog : array
-        See above.  Note that endog is a reference to the data so that if
-        data is already an array and it is changed, then `endog` changes
+        See above.  Note that `exog` is a reference to the data so that if
+        data is already an array and it is changed, then `exog` changes
         as well.
+    freq_weights : array
+        See above. Note that `freq_weights` is a reference to the data so that
+        if data i already an array and it is changed, then `freq_weights`
+        changes as well.
     iteration : int
         The number of iterations that fit has run.  Initialized at 0.
     family : family class instance
@@ -168,11 +202,17 @@ 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 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
+        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
         information.
+    n_trials : array
+        See above. Note that `n_trials` is a reference to the data so that if
+        data is already an array and it is changed, then `n_trials` changes
+        as well. `n_trials` is the number of binomial trials and only available
+        with that distribution. See statsmodels.families.Binomial for more
+        information.
     normalized_cov_params : array
         The p x p normalized covariance of the design / exogenous data.
         This is approximately equal to (X.T X)^(-1)
@@ -196,8 +236,8 @@ class GLM(base.LikelihoodModel):
         the specific distribution weighting functions.
     """ % {'extra_params' : base._missing_param_doc}
 
-    def __init__(self, endog, exog, family=None, offset=None, exposure=None,
-                 missing='none', **kwargs):
+    def __init__(self, endog, exog, family=None, offset=None,
+                 exposure=None, freq_weights=None, missing='none', **kwargs):
 
         if (family is not None) and not isinstance(family.link, tuple(family.safe_links)):
             import warnings
@@ -208,17 +248,24 @@ def __init__(self, endog, exog, family=None, offset=None, exposure=None,
             exposure = np.log(exposure)
         if offset is not None:  # this should probably be done upstream
             offset = np.asarray(offset)
+
+        self.freq_weights = freq_weights
+
         super(GLM, self).__init__(endog, exog, missing=missing,
                                   offset=offset, exposure=exposure,
-                                  **kwargs)
-        self._check_inputs(family, self.offset, self.exposure, self.endog)
+                                  freq_weights=freq_weights, **kwargs)
+        self._check_inputs(family, self.offset, self.exposure, self.endog,
+                           self.freq_weights)
         if offset is None:
             delattr(self, 'offset')
         if exposure is None:
             delattr(self, 'exposure')
+
+        self.nobs = self.endog.shape[0]
+
         #things to remove_data
-        self._data_attr.extend(['weights', 'pinv_wexog', 'mu', 'data_weights',
-                                '_offset_exposure'])
+        self._data_attr.extend(['weights', 'pinv_wexog', 'mu', 'freq_weights',
+                                '_offset_exposure', 'n_trials'])
         # register kwds for __init__, offset and exposure are added by super
         self._init_keys.append('family')
 
@@ -236,10 +283,18 @@ def initialize(self):
         self.normalized_cov_params = np.dot(self.pinv_wexog,
                                             np.transpose(self.pinv_wexog))
 
-        self.df_model = np_matrix_rank(self.exog)-1
-        self.df_resid = self.exog.shape[0] - np_matrix_rank(self.exog)
+        self.df_model = np_matrix_rank(self.exog) - 1
+
+
+        if (self.freq_weights is not None) and \
+           (self.freq_weights.shape[0] == self.endog.shape[0]):
+            self.wnobs = self.freq_weights.sum()
+            self.df_resid = self.wnobs - self.df_model - 1
+        else:
+            self.wnobs = self.exog.shape[0]
+            self.df_resid = self.exog.shape[0] - self.df_model - 1
 
-    def _check_inputs(self, family, offset, exposure, endog):
+    def _check_inputs(self, family, offset, exposure, endog, freq_weights):
 
         # Default family is Gaussian
         if family is None:
@@ -257,6 +312,17 @@ def _check_inputs(self, family, offset, exposure, endog):
             if offset.shape[0] != endog.shape[0]:
                 raise ValueError("offset is not the same length as endog")
 
+        if freq_weights is not None:
+            if freq_weights.shape[0] != endog.shape[0]:
+                raise ValueError("freq weights not the same length as endog")
+            if len(freq_weights.shape) > 1:
+                raise ValueError("freq weights has too many dimensions")
+
+        if self.freq_weights is None:
+            self.freq_weights = np.ones((endog.shape[0]))
+        if np.shape(self.freq_weights) == () and self.freq_weights > 1:
+            self.freq_weights = (self.freq_weights *
+                                 np.ones((endog.shape[0])))
 
     def _get_init_kwds(self):
         # this is a temporary fixup because exposure has been transformed
@@ -266,12 +332,12 @@ def _get_init_kwds(self):
             kwds['exposure'] = np.exp(kwds['exposure'])
         return kwds
 
-
     def loglike_mu(self, mu, scale=1.):
         """
         Evaluate the log-likelihood for a generalized linear model.
         """
-        return self.family.loglike(mu, self.endog, self.exog, scale)
+        return self.family.loglike(mu, self.endog, self.exog,
+                                   self.freq_weights, scale)
 
     def loglike(self, params, scale=None):
         """
@@ -281,7 +347,8 @@ def loglike(self, params, scale=None):
         expval = self.family.link.inverse(lin_pred)
         if scale is None:
             scale = self.estimate_scale(expval)
-        return self.family.loglike(expval, self.endog, scale)
+        return self.family.loglike(expval, self.endog, self.freq_weights,
+                                   scale)
 
     def score_obs(self, params, scale=None):
         """score first derivative of the loglikelihood for each observation.
@@ -356,7 +423,7 @@ def score_factor(self, params, scale=None):
 
         score_factor = (self.endog - mu) / self.family.link.deriv(mu)
         score_factor /= self.family.variance(mu)
-        score_factor *= self.data_weights
+        score_factor *= self.freq_weights
 
         if not scale == 1:
             score_factor /= scale
@@ -393,7 +460,7 @@ def hessian_factor(self, params, scale=None, observed=True):
 
         eim_factor = 1 / (self.family.link.deriv(mu)**2 *
                             self.family.variance(mu))
-        eim_factor *= self.data_weights
+        eim_factor *= self.freq_weights * self.n_trials
 
         if not observed:
             if not scale == 1:
@@ -410,8 +477,8 @@ def hessian_factor(self, params, scale=None, observed=True):
         tmp += self.family.variance.deriv(mu) * self.family.link.deriv(mu)
 
         tmp = score_factor * eim_factor * tmp
-        # correct for duplicatee data_weights in oim_factor and score_factor
-        tmp /= self.data_weights
+        # correct for duplicatee freq_weights in oim_factor and score_factor
+        tmp /= self.freq_weights
         oim_factor = eim_factor * (1 + tmp)
 
         if tmp.ndim > 1:
@@ -533,7 +600,8 @@ def _update_history(self, tmp_result, mu, history):
         Helper method to update history during iterative fit.
         """
         history['params'].append(tmp_result.params)
-        history['deviance'].append(self.family.deviance(self.endog, mu))
+        history['deviance'].append(self.family.deviance(self.endog, mu,
+                                                        self.freq_weights))
         return history
 
     def estimate_scale(self, mu):
@@ -565,8 +633,9 @@ def estimate_scale(self, mu):
                 return 1.
             else:
                 resid = self.endog - mu
-                return ((np.power(resid, 2) / self.family.variance(mu)).sum()
-                        / self.df_resid)
+                return ((self.freq_weights * (np.power(resid, 2) /
+                         self.family.variance(mu))).sum() /
+                        (self.df_resid))
 
         if isinstance(self.scaletype, float):
             return np.array(self.scaletype)
@@ -574,10 +643,13 @@ def estimate_scale(self, mu):
         if isinstance(self.scaletype, str):
             if self.scaletype.lower() == 'x2':
                 resid = self.endog - mu
-                return ((np.power(resid, 2) / self.family.variance(mu)).sum()
-                        / self.df_resid)
+                return ((self.freq_weights * (np.power(resid, 2) /
+                         self.family.variance(mu))).sum() /
+                        (self.df_resid))
             elif self.scaletype.lower() == 'dev':
-                return self.family.deviance(self.endog, mu)/self.df_resid
+                return (self.family.deviance(self.endog, mu,
+                                             self.freq_weights) /
+                        (self.df_resid))
             else:
                 raise ValueError("Scale %s with type %s not understood" %
                                  (self.scaletype, type(self.scaletype)))
@@ -749,21 +821,16 @@ def fit(self, start_params=None, maxiter=100, method='IRLS', tol=1e-8,
         -----
         This method does not take any extra undocumented ``kwargs``.
         """
-
-        endog = self.endog
-        if endog.ndim > 1 and endog.shape[1] == 2:
-            data_weights = endog.sum(1)  # weights are total trials
-        else:
-            data_weights = np.ones((endog.shape[0]))
-        self.data_weights = data_weights
-        if np.shape(self.data_weights) == () and self.data_weights > 1:
-            self.data_weights = self.data_weights * np.ones((endog.shape[0]))
-        self.scaletype = scale
-        if isinstance(self.family, families.Binomial):
         # this checks what kind of data is given for Binomial.
         # family will need a reference to endog if this is to be removed from
         # preprocessing
-            self.endog = self.family.initialize(self.endog)
+        self.n_trials = np.ones((self.endog.shape[0]))  # For binomial
+        if isinstance(self.family, families.Binomial):
+            tmp = self.family.initialize(self.endog, self.freq_weights)
+            self.endog = tmp[0]
+            self.n_trials = tmp[1]
+
+        self.scaletype = scale
 
         # Construct a combined offset/exposure term.  Note that
         # exposure has already been logged if present.
@@ -800,7 +867,7 @@ def _fit_gradient(self, start_params=None, method="newton",
         """
 
         if (max_start_irls > 0) and (start_params is None):
-            irls_rslt = self._fit_irls(start_params=start_params, maxiter=maxiter,
+            irls_rslt = self._fit_irls(start_params=start_params, maxiter=max_start_irls,
                                        tol=tol, scale=scale, cov_type=cov_type,
                                        cov_kwds=cov_kwds, use_t=use_t, **kwargs)
             start_params = irls_rslt.params
@@ -846,7 +913,7 @@ def _fit_irls(self, start_params=None, maxiter=100, tol=1e-8,
         else:
             lin_pred = np.dot(wlsexog, start_params) + self._offset_exposure
             mu = self.family.fitted(lin_pred)
-        dev = self.family.deviance(self.endog, mu)
+        dev = self.family.deviance(self.endog, mu, self.freq_weights)
         if np.isnan(dev):
             raise ValueError("The first guess on the deviance function "
                              "returned a nan.  This could be a boundary "
@@ -865,7 +932,8 @@ def _fit_irls(self, start_params=None, maxiter=100, tol=1e-8,
             wls_results = lm.RegressionResults(self, start_params, None)
             iteration = 0
         for iteration in range(maxiter):
-            self.weights = self.data_weights*self.family.weights(mu)
+            self.weights = (self.freq_weights * self.n_trials *
+                            self.family.weights(mu))
             wlsendog = (lin_pred + self.family.link.deriv(mu) * (self.endog-mu)
                         - self._offset_exposure)
             wls_results = lm.WLS(wlsendog, wlsexog, self.weights).fit()
@@ -1051,7 +1119,11 @@ def __init__(self, model, params, normalized_cov_params, scale,
         self._endog = model.endog
         self.nobs = model.endog.shape[0]
         self.mu = model.mu
-        self._data_weights = model.data_weights
+        self._freq_weights = model.freq_weights
+        if isinstance(self.family, families.Binomial):
+            self._n_trials = self.model.n_trials
+        else:
+            self._n_trials = 1
         self.df_resid = model.df_resid
         self.df_model = model.df_model
         self.pinv_wexog = model.pinv_wexog
@@ -1060,7 +1132,7 @@ def __init__(self, model, params, normalized_cov_params, scale,
         # call the model's attributes?
 
         # for remove data and pickle without large arrays
-        self._data_attr.extend(['results_constrained'])
+        self._data_attr.extend(['results_constrained', '_freq_weights'])
         self.data_in_cache = getattr(self, 'data_in_cache', [])
         self.data_in_cache.extend(['null'])
 
@@ -1084,31 +1156,33 @@ def __init__(self, model, params, normalized_cov_params, scale,
 
     @cache_readonly
     def resid_response(self):
-        return self._data_weights * (self._endog-self.mu)
+        return self._freq_weights * self._n_trials * (self._endog-self.mu)
 
     @cache_readonly
     def resid_pearson(self):
-        return (np.sqrt(self._data_weights) * (self._endog-self.mu) /
+        return (np.sqrt(self._freq_weights * self._n_trials) * (self._endog-self.mu) /
                 np.sqrt(self.family.variance(self.mu)))
 
     @cache_readonly
     def resid_working(self):
         val = (self.resid_response / self.family.link.deriv(self.mu))
-        val *= self._data_weights
+        val *= self._freq_weights * self._n_trials
         return val
 
     @cache_readonly
     def resid_anscombe(self):
+        # TODO: Data weights?
         return self.family.resid_anscombe(self._endog, self.mu)
 
     @cache_readonly
     def resid_deviance(self):
-        return self.family.resid_dev(self._endog, self.mu)
+        # TODO: Data weights?
+        return self.family.resid_dev(self._endog, self.mu, self._freq_weights)
 
     @cache_readonly
     def pearson_chi2(self):
         chisq = (self._endog - self.mu)**2 / self.family.variance(self.mu)
-        chisq *= self._data_weights
+        chisq *= self._freq_weights
         chisqsum = np.sum(chisq)
         return chisqsum
 
@@ -1129,25 +1203,28 @@ def null(self):
         if len(kwargs) > 0:
             return GLM(endog, exog, family=self.family, **kwargs).fit().mu
         else:
-            wls_model = lm.WLS(endog, exog, weights=self._data_weights)
+            wls_model = lm.WLS(endog, exog,
+                               weights=self._freq_weights * self._n_trials)
             return wls_model.fit().fittedvalues
 
     @cache_readonly
     def deviance(self):
-        return self.family.deviance(self._endog, self.mu)
+        return self.family.deviance(self._endog, self.mu, self._freq_weights)
 
     @cache_readonly
     def null_deviance(self):
-        return self.family.deviance(self._endog, self.null)
+        return self.family.deviance(self._endog, self.null, self._freq_weights)
 
     @cache_readonly
     def llnull(self):
-        return self.family.loglike(self._endog, self.null, scale=self.scale)
+        return self.family.loglike(self._endog, self.null,
+                                   self._freq_weights, scale=self.scale)
 
     @cache_readonly
     def llf(self):
         _modelfamily = self.family
-        val = _modelfamily.loglike(self._endog, self.mu, scale=self.scale)
+        val = _modelfamily.loglike(self._endog, self.mu,
+                                   self._freq_weights, scale=self.scale)
         return val
 
     @cache_readonly
@@ -1156,7 +1233,9 @@ def aic(self):
 
     @cache_readonly
     def bic(self):
-        return self.deviance - self.df_resid*np.log(self.nobs)
+        return (self.deviance -
+                (self.model.wnobs - self.df_model - 1) *
+                np.log(self.model.wnobs))
 
 
     def get_prediction(self, exog=None, exposure=None, offset=None,
@@ -1192,7 +1271,8 @@ def remove_data(self):
 
         #TODO: what are these in results?
         self._endog = None
-        self._data_weights = None
+        self._freq_weights = None
+        self._n_trials = None
 
     remove_data.__doc__ = base.LikelihoodModelResults.remove_data.__doc__
 
diff --git a/statsmodels/stats/sandwich_covariance.py b/statsmodels/stats/sandwich_covariance.py
--- a/statsmodels/stats/sandwich_covariance.py
+++ b/statsmodels/stats/sandwich_covariance.py
@@ -244,6 +244,15 @@ def _get_sandwich_arrays(results):
             xu = results.model.wexog * results.wresid[:, None]
 
             hessian_inv = np.asarray(results.normalized_cov_params)
+
+        # experimental support for freq_weights
+        if hasattr(results.model, 'freq_weights'):
+            # we don't want to square the weights in the covariance calculations
+            # assumes that freq_weights are incorporated in score_obs or equivalent
+            # assumes xu/score_obs is 2D
+            # temporary asarray
+            xu /= np.sqrt(np.asarray(results.model.freq_weights)[:, None])
+
     else:
         raise ValueError('need either tuple of (jac, hessian_inv) or results' +
                          'instance')


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
@@ -18,7 +18,9 @@
 
 
 iswin = platform.system() == 'Windows'
-npversionless15 = np.__version__ < '1.5'
+npversion = np.__version__.split('.')
+npversionless15 = not ((int(npversion[0]) > 1) or
+                       (int(npversion[0]) == 1 and (int(npversion[1]) >= 5)))
 winoldnp = iswin & npversionless15
 
 
diff --git a/statsmodels/discrete/tests/test_sandwich_cov.py b/statsmodels/discrete/tests/test_sandwich_cov.py
--- a/statsmodels/discrete/tests/test_sandwich_cov.py
+++ b/statsmodels/discrete/tests/test_sandwich_cov.py
@@ -217,6 +217,22 @@ def setup_class(cls):
         cls.corr_fact = np.sqrt(1./corr_fact)
 
 
+class TestPoissonHC1FitExposure(CheckCountRobustMixin):
+
+    @classmethod
+    def setup_class(cls):
+        cls.res2 = results_st.results_poisson_exposure_hc1
+        mod = smd.Poisson(endog, exog, exposure=exposure)
+        cls.res1 = mod.fit(disp=False, cov_type='HC1')
+
+        cls.bse_rob = cls.res1.bse
+        nobs, k_vars = mod.exog.shape
+        corr_fact = (nobs) / float(nobs - 1.)
+        # for bse we need sqrt of correction factor
+        cls.corr_fact = np.sqrt(1./corr_fact)
+
+
+
 class TestPoissonCluExposure(CheckCountRobustMixin):
 
     @classmethod
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
@@ -88,7 +88,7 @@ def test_aic_Stata(self):
         if isinstance(self.res1.model.family, (sm.families.Gamma,
             sm.families.InverseGaussian)):
             llf = self.res1.model.family.loglike(self.res1.model.endog,
-                    self.res1.mu, scale=1)
+                    self.res1.mu, self.res1.model.freq_weights, scale=1)
             aic = (-2*llf+2*(self.res1.df_model+1))/self.res1.nobs
         else:
             aic = self.res1.aic/self.res1.nobs
@@ -111,7 +111,7 @@ def test_loglike(self):
         if isinstance(self.res1.model.family, (sm.families.Gamma,
             sm.families.InverseGaussian)):
             llf = self.res1.model.family.loglike(self.res1.model.endog,
-                    self.res1.mu, scale=1)
+                    self.res1.mu, self.res1.model.freq_weights, scale=1)
         else:
             llf = self.res1.llf
         assert_almost_equal(llf, self.res2.llf, self.decimal_loglike)
@@ -714,12 +714,12 @@ def test_predict(self):
         assert_almost_equal(pred2, pred1+offset)
 
 
-def test_prefect_pred():
+def test_perfect_pred():
     cur_dir = os.path.dirname(os.path.abspath(__file__))
     iris = np.genfromtxt(os.path.join(cur_dir, 'results', 'iris.csv'),
-                    delimiter=",", skip_header=1)
-    y = iris[:,-1]
-    X = iris[:,:-1]
+                         delimiter=",", skip_header=1)
+    y = iris[:, -1]
+    X = iris[:, :-1]
     X = X[y != 2]
     y = y[y != 2]
     X = add_constant(X, prepend=True)
@@ -931,9 +931,7 @@ def test_summary():
         s = rslt.summary()
 
 def test_gradient_irls():
-    """
-    Compare the results when using gradient optimization and IRLS.
-    """
+    # Compare the results when using gradient optimization and IRLS.
 
     # TODO: Find working examples for inverse_squared link
 
@@ -953,6 +951,7 @@ def test_gradient_irls():
     exog = np.random.normal(size=(n, p))
     exog[:, 0] = 1
 
+    skip_one = False
     for family_class, family_links in families:
        for link in family_links:
            for binom_version in 0,1:
@@ -967,6 +966,7 @@ def test_gradient_irls():
                elif (family_class, link) == (fam.Poisson, lnk.sqrt):
                    lin_pred = 2 + exog.sum(1)
                elif (family_class, link) == (fam.InverseGaussian, lnk.log):
+                   #skip_zero = True
                    lin_pred = -1 + exog.sum(1)
                elif (family_class, link) == (fam.InverseGaussian, lnk.identity):
                    lin_pred = 20 + 5*exog.sum(1)
@@ -984,6 +984,10 @@ def test_gradient_irls():
                    continue # skip due to non-convergence
                elif (family_class, link) == (fam.NegativeBinomial, lnk.inverse_power):
                    lin_pred = 1 + exog.sum(1) / 5
+
+               elif (family_class, link) == (fam.Gaussian, lnk.inverse_power):
+                   # adding skip because of convergence failure
+                   skip_one = True
                else:
                    lin_pred = np.random.uniform(size=exog.shape[0])
 
@@ -995,8 +999,10 @@ def test_gradient_irls():
                rslt_irls = mod_irls.fit(method="IRLS")
 
                # Try with and without starting values.
-               for max_start_irls, start_params in (0, rslt_irls.params), (1, None):
-
+               for max_start_irls, start_params in (0, rslt_irls.params), (3, None):
+                   # TODO: skip convergence failures for now
+                   if max_start_irls > 0 and skip_one:
+                       continue
                    with warnings.catch_warnings():
                        warnings.simplefilter("ignore")
                        mod_gradient = sm.GLM(endog, exog, family=family_class(link=link))
@@ -1005,7 +1011,7 @@ def test_gradient_irls():
                                                     method="newton")
 
                    assert_allclose(rslt_gradient.params,
-                                   rslt_irls.params, rtol=1e-6, atol=1e-6)
+                                   rslt_irls.params, rtol=1e-6, atol=5e-6)
 
                    assert_allclose(rslt_gradient.llf, rslt_irls.llf,
                                    rtol=1e-6, atol=1e-6)
@@ -1021,6 +1027,307 @@ def test_gradient_irls():
                    assert_allclose(gradient_bse, rslt_irls.bse, rtol=1e-6, atol=1e-6)
 
 
+class CheckWtdDuplicationMixin(object):
+    decimal_params = DECIMAL_4
+
+    def __init__(self):
+        from statsmodels.datasets.cpunish import load
+        self.data = load()
+        self.endog = self.data.endog
+        self.exog = self.data.exog
+        np.random.seed(1234)
+        self.weight = np.random.randint(5, 100, len(self.endog))
+        self.endog_big = np.repeat(self.endog, self.weight)
+        self.exog_big = np.repeat(self.exog, self.weight, axis=0)
+
+    def test_params(self):
+        assert_allclose(self.res1.params, self.res2.params,  atol=1e-6,
+                        rtol=1e-6)
+
+    decimal_bse = DECIMAL_4
+
+    def test_standard_errors(self):
+        assert_allclose(self.res1.bse, self.res2.bse, rtol=1e-5, atol=1e-6)
+
+    decimal_resids = DECIMAL_4
+
+    # TODO: This doesn't work... Arrays are of different shape.
+    # Perhaps we use self.res1.model.family.resid_XXX()?
+    """
+    def test_residuals(self):
+        resids1 = np.column_stack((self.res1.resid_pearson,
+                                   self.res1.resid_deviance,
+                                   self.res1.resid_working,
+                                   self.res1.resid_anscombe,
+                                   self.res1.resid_response))
+        resids2 = np.column_stack((self.res1.resid_pearson,
+                                   self.res2.resid_deviance,
+                                   self.res2.resid_working,
+                                   self.res2.resid_anscombe,
+                                   self.res2.resid_response))
+        assert_allclose(resids1, resids2, self.decimal_resids)
+    """
+
+    def test_aic(self):
+        # R includes the estimation of the scale as a lost dof
+        # Doesn't with Gamma though
+        assert_allclose(self.res1.aic, self.res2.aic,  atol=1e-6, rtol=1e-6)
+
+    def test_deviance(self):
+        assert_allclose(self.res1.deviance, self.res2.deviance,  atol=1e-6,
+                        rtol=1e-6)
+
+    def test_scale(self):
+        assert_allclose(self.res1.scale, self.res2.scale, atol=1e-6, rtol=1e-6)
+
+    def test_loglike(self):
+        # Stata uses the below llf for these families
+        # We differ with R for them
+        assert_allclose(self.res1.llf, self.res2.llf, 1e-6)
+
+    decimal_null_deviance = DECIMAL_4
+
+    def test_null_deviance(self):
+        assert_allclose(self.res1.null_deviance, self.res2.null_deviance,
+                        atol=1e-6, rtol=1e-6)
+
+    decimal_bic = DECIMAL_4
+
+    def test_bic(self):
+        assert_allclose(self.res1.bic, self.res2.bic,  atol=1e-6, rtol=1e-6)
+
+    decimal_fittedvalues = DECIMAL_4
+
+    def test_fittedvalues(self):
+        res2_fitted = self.res2.predict(self.res1.model.exog)
+        assert_allclose(self.res1.fittedvalues, res2_fitted, atol=1e-5,
+                        rtol=1e-5)
+
+    decimal_tpvalues = DECIMAL_4
+
+    def test_tpvalues(self):
+        # test comparing tvalues and pvalues with normal implementation
+        # make sure they use normal distribution (inherited in results class)
+        assert_allclose(self.res1.tvalues, self.res2.tvalues, atol=1e-6,
+                        rtol=2e-4)
+        assert_allclose(self.res1.pvalues, self.res2.pvalues, atol=1e-6,
+                        rtol=1e-6)
+        assert_allclose(self.res1.conf_int(), self.res2.conf_int(), atol=1e-6,
+                        rtol=1e-6)
+
+
+
+
+class TestWtdGlmPoisson(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Poisson family with canonical log link.
+        '''
+        super(TestWtdGlmPoisson, self).__init__()
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=sm.families.Poisson()).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=sm.families.Poisson()).fit()
+
+
+class TestWtdGlmPoissonNewton(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Poisson family with canonical log link.
+        '''
+        super(TestWtdGlmPoissonNewton, self).__init__()
+
+        start_params = np.array([1.82794424e-04, -4.76785037e-02,
+                                 -9.48249717e-02, -2.92293226e-04,
+                                 2.63728909e+00, -2.05934384e+01])
+
+        fit_kwds = dict(method='newton')
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=sm.families.Poisson()).fit(**fit_kwds)
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=sm.families.Poisson()).fit(start_params=start_params,
+                                                          **fit_kwds)
+
+
+class TestWtdGlmPoissonHC0(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Poisson family with canonical log link.
+        '''
+        super(TestWtdGlmPoissonHC0, self).__init__()
+
+        start_params = np.array([1.82794424e-04, -4.76785037e-02,
+                                 -9.48249717e-02, -2.92293226e-04,
+                                 2.63728909e+00, -2.05934384e+01])
+
+        fit_kwds = dict(cov_type='HC0')
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=sm.families.Poisson()).fit(**fit_kwds)
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=sm.families.Poisson()).fit(start_params=start_params,
+                                                          **fit_kwds)
+
+
+class TestWtdGlmBinomial(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Binomial family with canonical logit link.
+        '''
+        super(TestWtdGlmBinomial, self).__init__()
+        self.endog = self.endog / 100
+        self.endog_big = self.endog_big / 100
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=sm.families.Binomial()).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=sm.families.Binomial()).fit()
+
+
+class TestWtdGlmNegativeBinomial(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Negative Binomial family with canonical link
+        g(u) = log(u/k * (1 - u/k))
+        '''
+        super(TestWtdGlmNegativeBinomial, self).__init__()
+        family_link = sm.families.NegativeBinomial()
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link).fit()
+
+
+class TestWtdGlmGamma(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Gamma family with log link.
+        '''
+        super(TestWtdGlmGamma, self).__init__()
+        family_link = sm.families.Gamma(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link).fit()
+
+
+class TestWtdGlmGaussian(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Gaussian family with canonical identity link.
+        '''
+        super(TestWtdGlmGaussian, self).__init__()
+        family_link = sm.families.Gaussian(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link).fit()
+
+
+class TestWtdGlmInverseGaussian(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests InverseGuassian family with log link.
+        '''
+        super(TestWtdGlmInverseGaussian, self).__init__()
+        family_link = sm.families.InverseGaussian(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link).fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link).fit()
+
+
+class TestWtdGlmGammaNewton(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Gamma family with log link.
+        '''
+        super(TestWtdGlmGammaNewton, self).__init__()
+        family_link = sm.families.Gamma(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link,
+                        method='newton').fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link,
+                        method='newton').fit()
+
+
+class TestWtdGlmGammaScale_X2(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Gamma family with log link.
+        '''
+        super(TestWtdGlmGammaScale_X2, self).__init__()
+        family_link = sm.families.Gamma(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link,
+                        scale='X2').fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link,
+                        scale='X2').fit()
+
+
+class TestWtdGlmGammaScale_dev(CheckWtdDuplicationMixin):
+    def __init__(self):
+        '''
+        Tests Gamma family with log link.
+        '''
+        super(TestWtdGlmGammaScale_dev, self).__init__()
+        family_link = sm.families.Gamma(sm.families.links.log)
+        self.res1 = GLM(self.endog, self.exog,
+                        freq_weights=self.weight,
+                        family=family_link,
+                        scale='dev').fit()
+        self.res2 = GLM(self.endog_big, self.exog_big,
+                        family=family_link,
+                        scale='dev').fit()
+
+
+    def test_missing(self):
+        endog = self.data.endog.copy()
+        exog = self.data.exog.copy()
+        exog[0, 0] = np.nan
+        endog[[2, 4, 6, 8]] = np.nan
+        freq_weights = self.weight
+        mod_misisng = GLM(endog, exog, family=self.res1.model.family,
+                          freq_weights=freq_weights, missing='drop')
+        assert_equal(mod_misisng.freq_weights.shape[0],
+                     mod_misisng.endog.shape[0])
+        assert_equal(mod_misisng.freq_weights.shape[0],
+                     mod_misisng.exog.shape[0])
+        keep_idx = np.array([ 1,  3,  5,  7,  9, 10, 11, 12, 13, 14, 15, 16])
+        assert_equal(mod_misisng.freq_weights, self.weight[keep_idx])
+
+
+def test_wtd_patsy_missing():
+    from statsmodels.datasets.cpunish import load
+    import pandas as pd
+    data = load()
+    data.exog[0, 0] = np.nan
+    data.endog[[2, 4, 6, 8]] = np.nan
+    data.pandas = pd.DataFrame(data.exog, columns=data.exog_name)
+    data.pandas['EXECUTIONS'] = data.endog
+    weights = np.arange(1, len(data.endog)+1)
+    formula = """EXECUTIONS ~ INCOME + PERPOVERTY + PERBLACK + VC100k96 +
+                 SOUTH + DEGREE"""
+    mod_misisng = GLM.from_formula(formula, data=data.pandas, freq_weights=weights)
+    assert_equal(mod_misisng.freq_weights.shape[0],
+                 mod_misisng.endog.shape[0])
+    assert_equal(mod_misisng.freq_weights.shape[0],
+                 mod_misisng.exog.shape[0])
+    assert_equal(mod_misisng.freq_weights.shape[0], 12)
+    keep_weights = np.array([ 2,  4,  6,  8, 10, 11, 12, 13, 14, 15, 16, 17])
+    assert_equal(mod_misisng.freq_weights, keep_weights)
+
+
 if __name__=="__main__":
     #run_module_suite()
     #taken from Fernando Perez: