Repository: statsmodels/statsmodels

Issue:
GEE Results doesn't have a Wrapper
Trying to compare some models and add them to a pandas DataFrame, I discovered that GEEResults doesn't return pandas.Series for params.



Discussion:

Resolving patch:
diff --git a/statsmodels/examples/try_gee.py b/statsmodels/examples/try_gee.py
--- a/statsmodels/examples/try_gee.py
+++ b/statsmodels/examples/try_gee.py
@@ -61,8 +61,8 @@
 print(marg2.summary())
 
 
-mdf_nc = md2.fit(covariance_type='naive')
-mdf_bc = md2.fit(covariance_type='bias_reduced')
+mdf_nc = md2.fit(cov_type='naive')
+mdf_bc = md2.fit(cov_type='bias_reduced')
 
 mdf_nc.use_t = False
 mdf_nc.df_resid = np.diff(mdf2.model.exog.shape)
diff --git a/statsmodels/genmod/generalized_estimating_equations.py b/statsmodels/genmod/generalized_estimating_equations.py
--- a/statsmodels/genmod/generalized_estimating_equations.py
+++ b/statsmodels/genmod/generalized_estimating_equations.py
@@ -30,6 +30,10 @@
 from statsmodels.tools.decorators import (cache_readonly,
     resettable_cache)
 import statsmodels.base.model as base
+# used for wrapper:
+import statsmodels.regression.linear_model as lm
+import statsmodels.base.wrapper as wrap
+
 from statsmodels.genmod import families
 from statsmodels.genmod import dependence_structures
 from statsmodels.genmod.dependence_structures import CovStruct
@@ -274,7 +278,7 @@ def unpack_cov(self, bcov):
     first_dep_update : integer
         No dependence structure updates occur before this
         iteration number.
-    covariance_type : string
+    cov_type : string
         One of "robust", "naive", or "bias_reduced".
 
     Returns
@@ -299,16 +303,21 @@ def unpack_cov(self, bcov):
     -------
     **Attributes**
 
-    naive_covariance : ndarray
+    cov_params_default : ndarray
+        default covariance of the parameter estimates. Is chosen among one
+        of the following three based on `cov_type`
+    cov_robust : ndarray
+        covariance of the parameter estimates that is robust
+    cov_naive : ndarray
         covariance of the parameter estimates that is not robust to
         correlation or variance misspecification
-    robust_covariance_bc : ndarray
+    cov_robust_bc : ndarray
         covariance of the parameter estimates that is robust and bias
         reduced
     converged : bool
         indicator for convergence of the optimization.
         True if the norm of the score is smaller than a threshold
-    covariance_type : string
+    cov_type : string
         string indicating whether a "robust", "naive" or "bias_
         reduced" covariance is used as default
     fit_history : dict
@@ -497,6 +506,9 @@ def __init__(self, endog, exog, groups, time=None, family=None,
         # Total sample size
         group_ns = [len(y) for y in self.endog_li]
         self.nobs = sum(group_ns)
+        # The following are column based, not on rank see #1928
+        self.df_model = self.exog.shape[1] - 1  # assumes constant
+        self.df_resid = self.nobs - self.exog.shape[1]
 
         # mean_deriv is the derivative of E[endog|exog] with respect
         # to params
@@ -678,15 +690,15 @@ def _covmat(self):
 
         Returns
         -------
-        robust_covariance : array-like
+        cov_robust : array-like
            The robust, or sandwich estimate of the covariance, which
            is meaningful even if the working covariance structure is
            incorrectly specified.
-        naive_covariance : array-like
+        cov_naive : array-like
            The model-based estimate of the covariance, which is
            meaningful if the covariance structure is correctly
            specified.
-        robust_covariance_bc : array-like
+        cov_robust_bc : array-like
            The "bias corrected" robust covariance of Mancl and
            DeRouen.
         cmat : array-like
@@ -723,11 +735,11 @@ def _covmat(self):
         scale = self.estimate_scale()
 
         bmati = np.linalg.inv(bmat)
-        naive_covariance = bmati * scale
-        robust_covariance = np.dot(bmati, np.dot(cmat, bmati))
+        cov_naive = bmati * scale
+        cov_robust = np.dot(bmati, np.dot(cmat, bmati))
 
         # Calculate the bias-corrected sandwich estimate of Mancl and
-        # DeRouen (requires naive_covariance so cannot be calculated
+        # DeRouen (requires cov_naive so cannot be calculated
         # in the previous loop).
         bcm = 0
         for i in range(self.num_group):
@@ -744,7 +756,7 @@ def _covmat(self):
             vinv_d = rslt[0]
             vinv_d /= scale
 
-            hmat = np.dot(vinv_d, naive_covariance)
+            hmat = np.dot(vinv_d, cov_naive)
             hmat = np.dot(hmat, dmat.T).T
 
             aresid = np.linalg.solve(np.eye(len(resid)) - hmat, resid)
@@ -756,11 +768,9 @@ def _covmat(self):
             srt = np.dot(dmat.T, srt) / scale
             bcm += np.outer(srt, srt)
 
-        robust_covariance_bc = np.dot(naive_covariance,
-                                      np.dot(bcm, naive_covariance))
+        cov_robust_bc = np.dot(cov_naive, np.dot(bcm, cov_naive))
 
-        return (robust_covariance, naive_covariance,
-            robust_covariance_bc, cmat)
+        return (cov_robust, cov_naive, cov_robust_bc, cmat)
 
     def predict(self, params, exog=None, offset=None, linear=False):
         """
@@ -796,7 +806,7 @@ def predict(self, params, exog=None, offset=None, linear=False):
         fitted = offset + np.dot(exog, params)
 
         if not linear:
-            fitted = self.family.link(fitted)
+            fitted = self.family.link.inverse(fitted)
 
         return fitted
 
@@ -825,7 +835,7 @@ def _starting_params(self):
 
     def fit(self, maxiter=60, ctol=1e-6, start_params=None,
             params_niter=1, first_dep_update=0,
-            covariance_type='robust'):
+            cov_type='robust'):
 
         self._fit_history = {'params': [],
                              'score': [],
@@ -896,16 +906,22 @@ def fit(self, maxiter=60, ctol=1e-6, start_params=None,
 
         scale = self.estimate_scale()
 
+        #kwargs to add to results instance, need to be available in __init__
+        res_kwds = dict(cov_type = cov_type,
+                        cov_robust = bcov,
+                        cov_naive = ncov,
+                        cov_robust_bc = bc_cov)
+
         # The superclass constructor will multiply the covariance
         # matrix argument bcov by scale, which we don't want, so we
         # divide bcov by the scale parameter here
-        results = GEEResults(self, mean_params, bcov / scale, scale)
+        results = GEEResults(self, mean_params, bcov / scale, scale,
+                             cov_type=cov_type, use_t=False,
+                             attr_kwds=res_kwds)
 
-        results.covariance_type = covariance_type
+        # attributes not needed during results__init__
         results.fit_history = self._fit_history
         delattr(self, "_fit_history")
-        results.naive_covariance = ncov
-        results.robust_covariance_bc = bc_cov
         results.score_norm = del_params
         results.converged = (del_params < ctol)
         results.cov_struct = self.cov_struct
@@ -915,13 +931,15 @@ def fit(self, maxiter=60, ctol=1e-6, start_params=None,
         results.maxiter = maxiter
 
         # These will be copied over to subclasses when upgrading.
-        results._props = ["covariance_type", "fit_history",
-                          "naive_covariance", "robust_covariance_bc",
+        results._props = ["cov_type", "use_t",
+                          "cov_params_default", "cov_robust",
+                          "cov_naive", "cov_robust_bc",
+                           "fit_history",
                           "score_norm", "converged", "cov_struct",
                           "params_niter", "first_dep_update", "ctol",
                           "maxiter"]
 
-        return results
+        return GEEResultsWrapper(results)
 
     fit.__doc__ = _gee_fit_doc
 
@@ -1055,15 +1073,47 @@ class GEEResults(base.LikelihoodModelResults):
         "This class summarizes the fit of a marginal regression model using GEE.\n"
         + _gee_results_doc)
 
-    # Default covariance type
-    covariance_type = "robust"
 
-    def __init__(self, model, params, cov_params, scale):
+    def __init__(self, model, params, cov_params, scale,
+                 cov_type='robust', use_t=False, **kwds):
 
         super(GEEResults, self).__init__(model, params,
                 normalized_cov_params=cov_params, scale=scale)
 
-    def standard_errors(self, covariance_type="robust"):
+        # not added by super
+        self.df_resid = model.df_resid
+        self.df_model = model.df_model
+
+        attr_kwds = kwds.pop('attr_kwds', {})
+        self.__dict__.update(attr_kwds)
+
+        # we don't do this if the cov_type has already been set
+        # subclasses can set it through attr_kwds
+        if not (hasattr(self, 'cov_type') and
+                hasattr(self, 'cov_params_default')):
+            self.cov_type = cov_type # keep alias
+            covariance_type = self.cov_type.lower()
+            allowed_covariances = ["robust", "naive", "bias_reduced"]
+            if covariance_type not in allowed_covariances:
+                msg = "GEE: `cov_type` must be one of " +\
+                    ", ".join(allowed_covariances)
+                raise ValueError(msg)
+
+            if cov_type == "robust":
+                cov = self.cov_robust
+            elif cov_type == "naive":
+                cov = self.cov_naive
+            elif cov_type == "bias_reduced":
+                cov = self.cov_robust_bc
+
+            self.cov_params_default = cov
+        else:
+            if self.cov_type != cov_type:
+                raise ValueError('cov_type in argument is different from '
+                                 'already attached cov_type')
+
+
+    def standard_errors(self, cov_type="robust"):
         """
         This is a convenience function that returns the standard
         errors for any covariance type.  The value of `bse` is the
@@ -1072,14 +1122,14 @@ def standard_errors(self, covariance_type="robust"):
 
         Arguments:
         ----------
-        covariance_type : string
+        cov_type : string
             One of "robust", "naive", or "bias_reduced".  Determines
             the covariance used to compute standard errors.  Defaults
             to "robust".
         """
 
         # Check covariance_type
-        covariance_type = covariance_type.lower()
+        covariance_type = cov_type.lower()
         allowed_covariances = ["robust", "naive", "bias_reduced"]
         if covariance_type not in allowed_covariances:
             msg = "GEE: `covariance_type` must be one of " +\
@@ -1087,16 +1137,16 @@ def standard_errors(self, covariance_type="robust"):
             raise ValueError(msg)
 
         if covariance_type == "robust":
-            return np.sqrt(np.diag(self.cov_params()))
+            return np.sqrt(np.diag(self.cov_robust))
         elif covariance_type == "naive":
-            return np.sqrt(np.diag(self.naive_covariance))
+            return np.sqrt(np.diag(self.cov_naive))
         elif covariance_type == "bias_reduced":
-            return np.sqrt(np.diag(self.robust_covariance_bc))
+            return np.sqrt(np.diag(self.cov_robust_bc))
 
     # Need to override to allow for different covariance types.
     @cache_readonly
     def bse(self):
-        return self.standard_errors(self.covariance_type)
+        return self.standard_errors(self.cov_type)
 
     @cache_readonly
     def resid(self):
@@ -1107,7 +1157,7 @@ def resid(self):
         return self.model.endog - self.fittedvalues
 
     @cache_readonly
-    def split_resid(self):
+    def resid_split(self):
         """
         Returns the residuals, the endogeneous data minus the fitted
         values from the model.  The residuals are returned as a list
@@ -1120,7 +1170,7 @@ def split_resid(self):
         return sresid
 
     @cache_readonly
-    def centered_resid(self):
+    def resid_centered(self):
         """
         Returns the residuals centered within each group.
         """
@@ -1131,7 +1181,7 @@ def centered_resid(self):
         return cresid
 
     @cache_readonly
-    def split_centered_resid(self):
+    def resid_centered_split(self):
         """
         Returns the residuals centered within each group.  The
         residuals are returned as a list of arrays containing the
@@ -1143,6 +1193,13 @@ def split_centered_resid(self):
             sresid.append(self.centered_resid[ii])
         return sresid
 
+
+    # FIXME: alias to be removed, temporary backwards compatibility
+    split_resid = resid_split
+    centered_resid = resid_centered
+    split_centered_resid = resid_centered_split
+
+
     @cache_readonly
     def fittedvalues(self):
         """
@@ -1151,8 +1208,7 @@ def fittedvalues(self):
         return self.model.family.link.inverse(np.dot(self.model.exog,
                                                      self.params))
 
-    def conf_int(self, alpha=.05, cols=None,
-                 covariance_type="robust"):
+    def conf_int(self, alpha=.05, cols=None, cov_type=None):
         """
         Returns confidence intervals for the fitted parameters.
 
@@ -1163,7 +1219,7 @@ def conf_int(self, alpha=.05, cols=None,
              default `alpha` = .05 returns a 95% confidence interval.
         cols : array-like, optional
              `cols` specifies which confidence intervals to return
-        covariance_type : string
+        cov_type : string
              The covariance type used for computing standard errors;
              must be one of 'robust', 'naive', and 'bias reduced'.
              See `GEE` for details.
@@ -1172,7 +1228,13 @@ def conf_int(self, alpha=.05, cols=None,
         -----
         The confidence interval is based on the Gaussian distribution.
         """
-        bse = self.standard_errors(covariance_type=covariance_type)
+        # super doesn't allow to specify cov_type and method is not
+        # implemented,
+        # FIXME: remove this method here
+        if cov_type is None:
+            bse = self.bse
+        else:
+            bse = self.standard_errors(cov_type=cov_type)
         params = self.params
         dist = stats.norm
         q = dist.ppf(1 - alpha / 2)
@@ -1186,8 +1248,7 @@ def conf_int(self, alpha=.05, cols=None,
             upper = params[cols] + q * bse[cols]
         return np.asarray(lzip(lower, upper))
 
-    def summary(self, yname=None, xname=None, title=None, alpha=.05,
-                covariance_type="robust"):
+    def summary(self, yname=None, xname=None, title=None, alpha=.05):
         """
         Summarize the GEE regression results
 
@@ -1202,7 +1263,7 @@ def summary(self, yname=None, xname=None, title=None, alpha=.05,
             the default title
         alpha : float
             significance level for the confidence intervals
-        covariance_type : string
+        cov_type : string
             The covariance type used to compute the standard errors;
             one of 'robust' (the usual robust sandwich-type covariance
             estimate), 'naive' (ignores dependence), and 'bias
@@ -1221,8 +1282,6 @@ def summary(self, yname=None, xname=None, title=None, alpha=.05,
 
         """
 
-        self.covariance_type = covariance_type
-
         top_left = [('Dep. Variable:', None),
                     ('Model:', None),
                     ('Method:', ['Generalized']),
@@ -1231,7 +1290,7 @@ def summary(self, yname=None, xname=None, title=None, alpha=.05,
                     ('Dependence structure:',
                      [self.model.cov_struct.__class__.__name__]),
                     ('Date:', None),
-                    ('Covariance type: ', [covariance_type,])
+                    ('Covariance type: ', [self.cov_type,])
                    ]
 
         NY = [len(y) for y in self.model.endog_li]
@@ -1355,7 +1414,7 @@ def plot_isotropic_dependence(self, ax=None, xpoints=10,
 
         return fig
 
-    def params_sensitivity(self, dep_params_first,
+    def sensitivity_params(self, dep_params_first,
                            dep_params_last, num_steps):
         """
         Refits the GEE model using a sequence of values for the
@@ -1398,13 +1457,24 @@ def params_sensitivity(self, dep_params_first,
                              ctol=self.ctol,
                              params_niter=self.params_niter,
                              first_dep_update=self.first_dep_update,
-                             covariance_type=self.covariance_type)
+                             cov_type=self.cov_type)
             results.append(rslt)
 
         model.update_dep = update_dep
 
         return results
 
+    # FIXME: alias to be removed, temporary backwards compatibility
+    params_sensitivity = sensitivity_params
+
+
+class GEEResultsWrapper(lm.RegressionResultsWrapper):
+    _attrs = {
+              'centered_resid' : 'rows',
+              }
+    _wrap_attrs = wrap.union_dicts(lm.RegressionResultsWrapper._wrap_attrs,
+                                   _attrs)
+wrap.populate_wrapper(GEEResultsWrapper, GEEResults)
 
 
 class OrdinalGEE(GEE):
@@ -1501,23 +1571,28 @@ def setup_ordinal(self, endog, exog, groups, time, offset):
 
     def fit(self, maxiter=60, ctol=1e-6, start_params=None,
             params_niter=1, first_dep_update=0,
-            covariance_type='robust'):
+            cov_type='robust'):
 
         rslt = super(OrdinalGEE, self).fit(maxiter, ctol, start_params,
                                            params_niter, first_dep_update,
-                                           covariance_type)
+                                           cov_type=cov_type)
 
+        rslt = rslt._results   # use unwrapped instance
+        res_kwds = dict(((k, getattr(rslt, k)) for k in rslt._props))
         # Convert the GEEResults to an OrdinalGEEResults
         ord_rslt = OrdinalGEEResults(self, rslt.params,
                                      rslt.cov_params() / rslt.scale,
-                                     rslt.scale)
-        for k in rslt._props:
-            setattr(ord_rslt, k, getattr(rslt, k))
+                                     rslt.scale,
+                                     cov_type=cov_type,
+                                     attr_kwds=res_kwds)
+        #for k in rslt._props:
+        #    setattr(ord_rslt, k, getattr(rslt, k))
 
-        return ord_rslt
+        return OrdinalGEEResultsWrapper(ord_rslt)
 
     fit.__doc__ = _gee_fit_doc
 
+
 class OrdinalGEEResults(GEEResults):
 
     __doc__ = (
@@ -1525,11 +1600,6 @@ class OrdinalGEEResults(GEEResults):
         "for an ordinal response using GEE.\n"
         + _gee_results_doc)
 
-    def __init__(self, model, params, cov_params, scale):
-
-        super(OrdinalGEEResults, self).__init__(model, params,
-                                      cov_params, scale)
-
 
     def plot_distribution(self, ax=None, exog_values=None):
         """
@@ -1617,6 +1687,10 @@ def plot_distribution(self, ax=None, exog_values=None):
 
         return fig
 
+class OrdinalGEEResultsWrapper(GEEResultsWrapper):
+    pass
+wrap.populate_wrapper(OrdinalGEEResultsWrapper, OrdinalGEEResults)
+
 
 class NominalGEE(GEE):
 
@@ -1657,6 +1731,12 @@ def setup_nominal(self, endog, exog, groups, time, offset):
             self.time_orig = None
             time = np.zeros((len(endog),1))
 
+        exog = np.asarray(exog)
+        endog = np.asarray(endog)
+        groups = np.asarray(groups)
+        time = np.asarray(time)
+        offset = np.asarray(offset)
+
         # The unique outcomes, except the greatest one.
         self.endog_values = np.unique(endog)
         endog_cuts = self.endog_values[0:-1]
@@ -1701,30 +1781,34 @@ def setup_nominal(self, endog, exog, groups, time, offset):
 
         # Preserve endog name if there is one
         if type(self.endog_orig) == pd.Series:
-            endog_out = pd.Series(endog_out, name=self.endog_orig.names)
+            endog_out = pd.Series(endog_out, name=self.endog_orig.name)
 
         return endog_out, exog_out, groups_out, time_out, offset_out
 
     def fit(self, maxiter=60, ctol=1e-6, start_params=None,
             params_niter=1, first_dep_update=0,
-            covariance_type='robust'):
+            cov_type='robust'):
 
         rslt = super(NominalGEE, self).fit(maxiter, ctol, start_params,
                                            params_niter, first_dep_update,
-                                           covariance_type)
+                                           cov_type=cov_type)
         if rslt is None:
             warnings.warn("GEE updates did not converge",
                           ConvergenceWarning)
             return None
 
+        rslt = rslt._results   # use unwrapped instance
+        res_kwds = dict(((k, getattr(rslt, k)) for k in rslt._props))
         # Convert the GEEResults to a NominalGEEResults
         nom_rslt = NominalGEEResults(self, rslt.params,
                                      rslt.cov_params() / rslt.scale,
-                                     rslt.scale)
-        for k in rslt._props:
-            setattr(nom_rslt, k, getattr(rslt, k))
+                                     rslt.scale,
+                                     cov_type=cov_type,
+                                     attr_kwds=res_kwds)
+        #for k in rslt._props:
+        #    setattr(nom_rslt, k, getattr(rslt, k))
 
-        return nom_rslt
+        return NominalGEEResultsWrapper(nom_rslt)
 
     fit.__doc__ = _gee_fit_doc
 
@@ -1735,10 +1819,6 @@ class NominalGEEResults(GEEResults):
         "for a nominal response using GEE.\n"
         + _gee_results_doc)
 
-    def __init__(self, model, params, cov_params, scale):
-
-        super(NominalGEEResults, self).__init__(model, params,
-                                      cov_params, scale)
 
     def plot_distribution(self, ax=None, exog_values=None):
         """
@@ -1819,6 +1899,9 @@ def plot_distribution(self, ax=None, exog_values=None):
 
         return fig
 
+class NominalGEEResultsWrapper(GEEResultsWrapper):
+    pass
+wrap.populate_wrapper(NominalGEEResultsWrapper, NominalGEEResults)
 
 
 class MultinomialLogit(Link):


Test patch:
diff --git a/statsmodels/base/tests/test_generic_methods.py b/statsmodels/base/tests/test_generic_methods.py
--- a/statsmodels/base/tests/test_generic_methods.py
+++ b/statsmodels/base/tests/test_generic_methods.py
@@ -89,8 +89,14 @@ def test_ftest_pvalues(self):
         string_use_t = 'P>|z|' if use_t is False else 'P>|t|'
         summ = str(res.summary())
         assert_(string_use_t in summ)
-        summ = str(res.summary2())
-        assert_(string_use_t in summ)
+
+        # try except for models that don't have summary2
+        try:
+            summ2 = str(res.summary2())
+        except AttributeError:
+            summ2 = None
+        if summ2 is not None:
+            assert_(string_use_t in summ2)
 
 
     # TODO The following is not (yet) guaranteed across models
@@ -99,7 +105,11 @@ def test_fitted(self):
         # ignore wrapper for isinstance check
         from statsmodels.genmod.generalized_linear_model import GLMResults
         from statsmodels.discrete.discrete_model import DiscreteResults
-        results = self.results._results
+        # FIXME: work around GEE has no wrapper
+        if hasattr(self.results, '_results'):
+            results = self.results._results
+        else:
+            results = self.results
         if (isinstance(results, GLMResults) or
             isinstance(results, DiscreteResults)):
             raise SkipTest
@@ -117,7 +127,13 @@ def test_predict_types(self):
         # ignore wrapper for isinstance check
         from statsmodels.genmod.generalized_linear_model import GLMResults
         from statsmodels.discrete.discrete_model import DiscreteResults
-        results = self.results._results
+
+        # FIXME: work around GEE has no wrapper
+        if hasattr(self.results, '_results'):
+            results = self.results._results
+        else:
+            results = self.results
+
         if (isinstance(results, GLMResults) or
             isinstance(results, DiscreteResults)):
             # SMOKE test only  TODO
@@ -241,5 +257,71 @@ def setup(self):
         y = x.sum(1) + np.random.randn(x.shape[0])
         self.results = sm.GLM(y, self.exog).fit()
 
+
+class TestGenericGEEPoisson(CheckGenericMixin):
+
+    def setup(self):
+        #fit for each test, because results will be changed by test
+        x = self.exog
+        np.random.seed(987689)
+        y_count = np.random.poisson(np.exp(x.sum(1) - x.mean()))
+        groups = np.random.randint(0, 4, size=x.shape[0])
+        # use start_params to speed up test, difficult convergence not tested
+        start_params = np.array([0., 1., 1., 1.])
+
+        # no sm. import
+        # vi = sm.dependence_structures.Independence()
+        from statsmodels.genmod.dependence_structures import Independence
+        vi = Independence()
+        family = sm.families.Poisson()
+        self.results = sm.GEE(y_count, self.exog, groups, family=family,
+                                cov_struct=vi).fit(start_params=start_params)
+
+
+class TestGenericGEEPoissonNaive(CheckGenericMixin):
+
+    def setup(self):
+        #fit for each test, because results will be changed by test
+        x = self.exog
+        np.random.seed(987689)
+        #y_count = np.random.poisson(np.exp(x.sum(1) - x.mean()))
+        y_count = np.random.poisson(np.exp(x.sum(1) - x.sum(1).mean(0)))
+        groups = np.random.randint(0, 4, size=x.shape[0])
+        # use start_params to speed up test, difficult convergence not tested
+        start_params = np.array([0., 1., 1., 1.])
+
+        # no sm. import
+        # vi = sm.dependence_structures.Independence()
+        from statsmodels.genmod.dependence_structures import Independence
+        vi = Independence()
+        family = sm.families.Poisson()
+        self.results = sm.GEE(y_count, self.exog, groups, family=family,
+                                cov_struct=vi).fit(start_params=start_params,
+                                                   cov_type='naive')
+
+
+class TestGenericGEEPoissonBC(CheckGenericMixin):
+
+    def setup(self):
+        #fit for each test, because results will be changed by test
+        x = self.exog
+        np.random.seed(987689)
+        #y_count = np.random.poisson(np.exp(x.sum(1) - x.mean()))
+        y_count = np.random.poisson(np.exp(x.sum(1) - x.sum(1).mean(0)))
+        groups = np.random.randint(0, 4, size=x.shape[0])
+        # use start_params to speed up test, difficult convergence not tested
+        start_params = np.array([0., 1., 1., 1.])
+        # params_est = np.array([-0.0063238 ,  0.99463752,  1.02790201,  0.98080081])
+
+        # no sm. import
+        # vi = sm.dependence_structures.Independence()
+        from statsmodels.genmod.dependence_structures import Independence
+        vi = Independence()
+        family = sm.families.Poisson()
+        mod = sm.GEE(y_count, self.exog, groups, family=family, cov_struct=vi)
+        self.results = mod.fit(start_params=start_params,
+                               cov_type='bias_reduced')
+
+
 if __name__ == '__main__':
     pass
diff --git a/statsmodels/genmod/tests/test_gee.py b/statsmodels/genmod/tests/test_gee.py
--- a/statsmodels/genmod/tests/test_gee.py
+++ b/statsmodels/genmod/tests/test_gee.py
@@ -11,11 +11,13 @@
 from statsmodels.compat import lrange
 import numpy as np
 import os
-from numpy.testing import (assert_almost_equal, assert_array_less,
-                           assert_equal)
+
+from numpy.testing import (assert_almost_equal, assert_equal, assert_allclose,
+                           assert_array_less, assert_raises, assert_)
 from statsmodels.genmod.generalized_estimating_equations import (GEE,
      OrdinalGEE, NominalGEE, GEEMargins, Multinomial,
-     NominalGEEResults, OrdinalGEEResults)
+     NominalGEEResults, OrdinalGEEResults,
+     NominalGEEResultsWrapper, OrdinalGEEResultsWrapper)
 from statsmodels.genmod.families import Gaussian, Binomial, Poisson
 from statsmodels.genmod.dependence_structures import (Exchangeable,
     Independence, GlobalOddsRatio, Autoregressive, Nested)
@@ -51,6 +53,19 @@ def load_data(fname, icept=True):
     return endog,exog,group
 
 
+def check_wrapper(results):
+    # check wrapper
+    assert_(isinstance(results.params, pd.Series))
+    assert_(isinstance(results.fittedvalues, pd.Series))
+    assert_(isinstance(results.resid, pd.Series))
+    assert_(isinstance(results.centered_resid, pd.Series))
+
+    assert_(isinstance(results._results.params, np.ndarray))
+    assert_(isinstance(results._results.fittedvalues, np.ndarray))
+    assert_(isinstance(results._results.resid, np.ndarray))
+    assert_(isinstance(results._results.centered_resid, np.ndarray))
+
+
 class TestGEE(object):
 
 
@@ -101,6 +116,10 @@ def test_poisson_epil(self):
                                family=families.Poisson())
         rslt2 = mod2.fit(scale="X2")
 
+        # don't use wrapper, asserts_xxx don't work
+        rslt1 = rslt1._results
+        rslt2 = rslt2._results
+
         assert_almost_equal(rslt1.params, rslt2.params, decimal=6)
         assert_almost_equal(rslt1.scale, rslt2.scale, decimal=6)
 
@@ -558,7 +577,8 @@ def test_ordinal(self):
         assert_almost_equal(rslt.bse, se, decimal=5)
 
         # Check that we get the correct results type
-        assert_equal(type(rslt), OrdinalGEEResults)
+        assert_equal(type(rslt), OrdinalGEEResultsWrapper)
+        assert_equal(type(rslt._results), OrdinalGEEResults)
 
     def test_nominal(self):
 
@@ -590,7 +610,8 @@ def test_nominal(self):
         assert_almost_equal(rslt2.standard_errors(), se2, decimal=5)
 
         # Make sure we get the correct results type
-        assert_equal(type(rslt1), NominalGEEResults)
+        assert_equal(type(rslt1), NominalGEEResultsWrapper)
+        assert_equal(type(rslt1._results), NominalGEEResults)
 
 
     def test_poisson(self):
@@ -692,13 +713,16 @@ def test_compare_OLS(self):
 
         ols = sm.ols("Y ~ X1 + X2 + X3", data=D).fit()
 
-        assert_almost_equal(ols.params.values, mdf.params, decimal=10)
+        # don't use wrapper, asserts_xxx don't work
+        ols = ols._results
+
+        assert_almost_equal(ols.params, mdf.params, decimal=10)
 
-        se = mdf.standard_errors(covariance_type="naive")
+        se = mdf.standard_errors(cov_type="naive")
         assert_almost_equal(ols.bse, se, decimal=10)
 
         naive_tvalues = mdf.params / \
-            np.sqrt(np.diag(mdf.naive_covariance))
+            np.sqrt(np.diag(mdf.cov_naive))
         assert_almost_equal(naive_tvalues, ols.tvalues, decimal=10)
 
     def test_formulas(self):
@@ -744,6 +768,9 @@ def test_formulas(self):
         assert_almost_equal(rslt1.params, rslt4.params, decimal=8)
         assert_almost_equal(rslt1.params, rslt5.params, decimal=8)
 
+        check_wrapper(rslt2)
+
+
     def test_compare_logit(self):
 
         vs = Independence()
@@ -762,9 +789,10 @@ def test_compare_logit(self):
         rslt1 = mod1.fit()
 
         mod2 = sm.logit("Y ~ X1 + X2 + X3", data=D)
-        rslt2 = mod2.fit()
+        rslt2 = mod2.fit(disp=False)
 
-        assert_almost_equal(rslt1.params, rslt2.params, decimal=10)
+        assert_almost_equal(rslt1.params.values, rslt2.params.values,
+                            decimal=10)
 
 
     def test_compare_poisson(self):
@@ -787,7 +815,8 @@ def test_compare_poisson(self):
         mod2 = sm.poisson("Y ~ X1 + X2 + X3", data=D)
         rslt2 = mod2.fit(disp=False)
 
-        assert_almost_equal(rslt1.params, rslt2.params, decimal=10)
+        assert_almost_equal(rslt1.params.values, rslt2.params.values,
+                            decimal=10)
 
 
     def test_sensitivity(self):
@@ -815,6 +844,183 @@ def test_sensitivity(self):
         assert_almost_equal([x.params[0] for x in ps],
                             np.r_[-0.1256575, -0.126747036])
 
+
+class CheckConsistency(object):
+
+    start_params = None
+
+    def test_cov_type(self):
+        mod = self.mod
+        res_robust = mod.fit(start_params=self.start_params)
+        res_naive = mod.fit(start_params=self.start_params,
+                            cov_type='naive')
+        res_robust_bc = mod.fit(start_params=self.start_params,
+                                cov_type='bias_reduced')
+
+        # call summary to make sure it doesn't change cov_type
+        res_naive.summary()
+        res_robust_bc.summary()
+
+        #check cov_type
+        assert_equal(res_robust.cov_type, 'robust')
+        assert_equal(res_naive.cov_type, 'naive')
+        assert_equal(res_robust_bc.cov_type, 'bias_reduced')
+
+
+        # check bse and cov_params
+        # we are comparing different runs of the optimization
+        # bse in ordinal and multinomial have an atol around 5e-10 for two
+        # consecutive calls to fit.
+        rtol = 1e-8
+        for (res, cov_type, cov) in [
+                (res_robust, 'robust', res_robust.cov_robust),
+                (res_naive, 'naive', res_robust.cov_naive),
+                (res_robust_bc, 'bias_reduced', res_robust.cov_robust_bc)
+                ]:
+            bse = np.sqrt(np.diag(cov))
+            assert_allclose(res.bse, bse, rtol=rtol)
+            bse = res_naive.standard_errors(cov_type=cov_type)
+            assert_allclose(res.bse, bse, rtol=rtol)
+            assert_allclose(res.cov_params(), cov, rtol=rtol, atol=1e-10)
+            assert_allclose(res.cov_params_default, cov, rtol=rtol, atol=1e-10)
+
+        # assert that we don't have a copy
+        assert_(res_robust.cov_params_default is res_robust.cov_robust)
+        assert_(res_naive.cov_params_default is res_naive.cov_naive)
+        assert_(res_robust_bc.cov_params_default is
+                res_robust_bc.cov_robust_bc)
+
+        # check exception for misspelled cov_type
+        assert_raises(ValueError, mod.fit, cov_type='robust_bc')
+
+
+class TestGEEPoissonCovType(CheckConsistency):
+
+    @classmethod
+    def setup_class(cls):
+
+
+        endog, exog, group_n = load_data("gee_poisson_1.csv")
+
+        family = Poisson()
+        vi = Independence()
+
+        cls.mod = GEE(endog, exog, group_n, None, family, vi)
+
+        cls.start_params = np.array([-0.03644504, -0.05432094,  0.01566427,
+                                      0.57628591, -0.0046566,  -0.47709315])
+
+
+    def test_wrapper(self):
+
+        endog, exog, group_n = load_data("gee_poisson_1.csv",
+                                        icept=False)
+        endog = pd.Series(endog)
+        exog = pd.DataFrame(exog)
+        group_n = pd.Series(group_n)
+
+        family = Poisson()
+        vi = Independence()
+
+        mod = GEE(endog, exog, group_n, None, family, vi)
+        rslt2 = mod.fit()
+
+        check_wrapper(rslt2)
+
+
+class TestGEEPoissonFormulaCovType(CheckConsistency):
+
+    @classmethod
+    def setup_class(cls):
+
+        endog, exog, group_n = load_data("gee_poisson_1.csv")
+
+        family = Poisson()
+        vi = Independence()
+        # Test with formulas
+        D = np.concatenate((endog[:,None], group_n[:,None],
+                            exog[:,1:]), axis=1)
+        D = pd.DataFrame(D)
+        D.columns = ["Y","Id",] + ["X%d" % (k+1)
+                                   for k in range(exog.shape[1]-1)]
+
+        cls.mod = GEE.from_formula("Y ~ X1 + X2 + X3 + X4 + X5", "Id",
+                                    D, family=family, cov_struct=vi)
+
+        cls.start_params = np.array([-0.03644504, -0.05432094,  0.01566427,
+                                      0.57628591, -0.0046566,  -0.47709315])
+
+
+class TestGEEOrdinalCovType(CheckConsistency):
+
+    @classmethod
+    def setup_class(cls):
+
+
+        family = Binomial()
+
+        endog, exog, groups = load_data("gee_ordinal_1.csv",
+                                        icept=False)
+
+        va = GlobalOddsRatio("ordinal")
+
+        cls.mod = OrdinalGEE(endog, exog, groups, None, family, va)
+        cls.start_params = np.array([ 1.09250002, 0.0217443 , -0.39851092,
+                                     -0.01812116, 0.03023969, 1.18258516,
+                                      0.01803453, -1.10203381])
+
+    def test_wrapper(self):
+
+        endog, exog, groups = load_data("gee_ordinal_1.csv",
+                                        icept=False)
+
+
+        endog = pd.Series(endog, name='yendog')
+        exog = pd.DataFrame(exog)
+        groups = pd.Series(groups, name='the_group')
+
+        family = Binomial()
+        va = GlobalOddsRatio("ordinal")
+        mod = OrdinalGEE(endog, exog, groups, None, family, va)
+        rslt2 = mod.fit()
+
+        check_wrapper(rslt2)
+
+
+class TestGEEMultinomialCovType(CheckConsistency):
+
+    @classmethod
+    def setup_class(cls):
+
+        family = Multinomial(3)
+
+        endog, exog, groups = load_data("gee_nominal_1.csv",
+                                        icept=False)
+
+        # Test with independence correlation
+        va = Independence()
+        cls.mod = NominalGEE(endog, exog, groups, None, family, va)
+        cls.start_params = np.array([0.44944752,  0.45569985, -0.92007064,
+                                     -0.46766728])
+
+
+    def test_wrapper(self):
+
+        endog, exog, groups = load_data("gee_nominal_1.csv",
+                                        icept=False)
+        endog = pd.Series(endog, name='yendog')
+        exog = pd.DataFrame(exog)
+        groups = pd.Series(groups, name='the_group')
+
+        family = Multinomial(3)
+        va = Independence()
+        mod = NominalGEE(endog, exog, groups, None, family, va)
+        rslt2 = mod.fit()
+
+        check_wrapper(rslt2)
+
+
+
 if  __name__=="__main__":
 
     import nose
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
@@ -328,7 +328,7 @@ def __init__(self):
                 family=sm.families.Binomial()).fit()
 
         modd = discrete.Logit(self.res2.endog, self.res2.exog)
-        self.resd = modd.fit(start_params=self.res1.params * 0.9)
+        self.resd = modd.fit(start_params=self.res1.params * 0.9, disp=False)
 
 
     def score_test_r(self):
@@ -473,7 +473,7 @@ def __init__(self):
         self.res2 = Cpunish()
         # compare with discrete, start close to save time
         modd = discrete.Poisson(self.data.endog, self.data.exog)
-        self.resd = modd.fit(start_params=self.res1.params * 0.9)
+        self.resd = modd.fit(start_params=self.res1.params * 0.9, disp=False)
 
 #class TestGlmPoissonIdentity(CheckModelResultsMixin):
 #    pass