Repository: statsmodels/statsmodels

Issue:
De-duplicate HoldIt class
Defined in tools.dump2module but not used elsewhere within statsmodels (useful externally?).  A not-quite-identical class is defined in statsmodels/sandbox/tests/maketests_mlabwrap.py and used both there and in statsmodels/sandbox/tests/savervs.py


Discussion:
You should see how many `class Bunch` definitions there are. 
We don't have yet a proper class hierarchy for Bunch/Holder classes, so the easiest is to just define them where they are needed. For hypothesis tests they get sometimes converted to some kind of Results classes with extra methods.
Design for inheritable Bunch/Holder/TestResults classes is still open.

(My Stata export `do` file defines new Holder classes each time.)
All `class Bunch` definitions 'out there' follow one of the following two templates, (i),
```python
class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self, kw)
        self.__dict__ = self
```
And (ii),

```python
class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self, kw)
        self.__dict__ = self

        for i,att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
            self[att] = self.params_table[:,i]
```

I have some spare time and could consolidate this, i.a., into a proper class hierarchy, and then redirect current definitions to use one of the two classes. After that, I'm very willing to look at Hold(It/er) and TestResults, but that might not get done soon.

@josef-pkt @bashtage where'd you suggest to place the `Bunch` definition in the code base? My initial thoughts were to create a new module `parameter_objects` in `sm/tools` , which would then expose these classes.
The problem is that I actually don't like `Bunch` classes (subclass of dict) anymore, i.e. I never use the dictionary access, and only like attribute access as in our Model Results classes.

based on a file search:
tsa (Chad in newer code) uses them quite a bit, but imported:
`from statsmodels.tools.tools import Bunch`

there are some miscellaneous uses where a Bunch class is not always a Bunch(dict), but are just what I switched to calling Holder classes.
And there are many Bunch classes in Stata results, but those are part of the do file, i.e. autogenerated. (Those could be Holder classes because we only use attribute access AFAIK)

for test results as replacement of Bunch and Holder classes
#277 first open issue from 2012
#1182 initial discussion from 2013 
with still no general outcome, just a lot more special cases


Resolving patch:
diff --git a/statsmodels/tsa/vector_ar/vecm.py b/statsmodels/tsa/vector_ar/vecm.py
--- a/statsmodels/tsa/vector_ar/vecm.py
+++ b/statsmodels/tsa/vector_ar/vecm.py
@@ -14,6 +14,7 @@
 from statsmodels.tools.decorators import cache_readonly
 from statsmodels.tools.sm_exceptions import HypothesisTestWarning
 from statsmodels.tools.tools import chain_dot
+from statsmodels.tools.testing import Holder
 from statsmodels.tsa.tsatools import duplication_matrix, vec, lagmat
 
 import statsmodels.tsa.base.tsa_model as tsbase
@@ -608,9 +609,6 @@ def coint_johansen(endog, det_order, k_ar_diff):
 
     from statsmodels.regression.linear_model import OLS
 
-    class Holder(object):
-        pass
-
     def detrend(y, order):
         if order == -1:
             return y


Test patch:
diff --git a/statsmodels/discrete/tests/results/results_count_margins.py b/statsmodels/discrete/tests/results/results_count_margins.py
--- a/statsmodels/discrete/tests/results/results_count_margins.py
+++ b/statsmodels/discrete/tests/results/results_count_margins.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import MarginTableTestBunch
+
 est = dict(
            rank=7,
            N=17,
@@ -79,16 +81,7 @@
                         'south', 'sdegree', '_cons']
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.margins_table[:, i]
-
-
-results_poisson_margins_cont = Bunch(
+results_poisson_margins_cont = MarginTableTestBunch(
                 margins_table=margins_table,
                 margins_table_colnames=margins_table_colnames,
                 margins_table_rownames=margins_table_rownames,
@@ -188,7 +181,7 @@ def __init__(self, **kw):
                         'south', 'sdegree', '_cons', '_cons']
 
 
-results_negbin_margins_cont = Bunch(
+results_negbin_margins_cont = MarginTableTestBunch(
                 margins_table=margins_table,
                 margins_table_colnames=margins_table_colnames,
                 margins_table_rownames=margins_table_rownames,
@@ -283,7 +276,7 @@ def __init__(self, **kw):
                         '0b.south', '1.south', 'sdegree', '_cons']
 
 
-results_poisson_margins_dummy = Bunch(
+results_poisson_margins_dummy = MarginTableTestBunch(
                 margins_table=margins_table,
                 margins_table_colnames=margins_table_colnames,
                 margins_table_rownames=margins_table_rownames,
@@ -390,7 +383,7 @@ def __init__(self, **kw):
                         '0b.south', '1.south', 'sdegree', '_cons', '_cons']
 
 
-results_negbin_margins_dummy = Bunch(
+results_negbin_margins_dummy = MarginTableTestBunch(
                 margins_table=margins_table,
                 margins_table_colnames=margins_table_colnames,
                 margins_table_rownames=margins_table_rownames,
diff --git a/statsmodels/discrete/tests/results/results_count_robust_cluster.py b/statsmodels/discrete/tests/results/results_count_robust_cluster.py
--- a/statsmodels/discrete/tests/results/results_count_robust_cluster.py
+++ b/statsmodels/discrete/tests/results/results_count_robust_cluster.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
            rank=3,
            N=34,
@@ -62,16 +64,7 @@
 cov_rownames = 'yr_con op_75_79 _cons'.split()
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_poisson_clu = Bunch(
+results_poisson_clu = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -141,7 +134,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons'.split()
 
 
-results_poisson_hc1 = Bunch(
+results_poisson_hc1 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -211,7 +204,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons'.split()
 
 
-results_poisson_exposure_nonrobust = Bunch(
+results_poisson_exposure_nonrobust = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -282,7 +275,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons'.split()
 
 
-results_poisson_exposure_hc1 = Bunch(
+results_poisson_exposure_hc1 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -355,7 +348,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons'.split()
 
 
-results_poisson_exposure_clu = Bunch(
+results_poisson_exposure_clu = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -437,7 +430,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons _cons'.split()
 
 
-results_negbin_clu = Bunch(
+results_negbin_clu = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -518,7 +511,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons _cons'.split()
 
 
-results_negbin_hc1 = Bunch(
+results_negbin_hc1 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -602,7 +595,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons _cons'.split()
 
 
-results_negbin_exposure_nonrobust = Bunch(
+results_negbin_exposure_nonrobust = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -685,7 +678,7 @@ def __init__(self, **kw):
 cov_rownames = 'yr_con op_75_79 _cons _cons'.split()
 
 
-results_negbin_exposure_clu = Bunch(
+results_negbin_exposure_clu = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
diff --git a/statsmodels/discrete/tests/results/results_glm_logit_constrained.py b/statsmodels/discrete/tests/results/results_glm_logit_constrained.py
--- a/statsmodels/discrete/tests/results/results_glm_logit_constrained.py
+++ b/statsmodels/discrete/tests/results/results_glm_logit_constrained.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
            k_eq_model=0,
            phi=1,
@@ -98,16 +100,7 @@
 infocrit_rownames = '.'.split()
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_noconstraint = Bunch(
+results_noconstraint = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -218,7 +211,7 @@ def __init__(self, **kw):
 infocrit_rownames = '.'.split()
 
 
-results_noconstraint_robust = Bunch(
+results_noconstraint_robust = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -329,7 +322,7 @@ def __init__(self, **kw):
 infocrit_rownames = '.'.split()
 
 
-results_constraint1 = Bunch(
+results_constraint1 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -485,7 +478,7 @@ def __init__(self, **kw):
 predict_hat_rownames = ['r'+str(n) for n in range(1, 33)]
 
 
-results_constraint2 = Bunch(
+results_constraint2 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -605,7 +598,7 @@ def __init__(self, **kw):
 infocrit_rownames = '.'.split()
 
 
-results_constraint2_robust = Bunch(
+results_constraint2_robust = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -690,7 +683,7 @@ def __init__(self, **kw):
 infocrit_rownames = '.'.split()
 
 
-results_logit_constraint2 = Bunch(
+results_logit_constraint2 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
diff --git a/statsmodels/discrete/tests/results/results_poisson_constrained.py b/statsmodels/discrete/tests/results/results_poisson_constrained.py
--- a/statsmodels/discrete/tests/results/results_poisson_constrained.py
+++ b/statsmodels/discrete/tests/results/results_poisson_constrained.py
@@ -1,14 +1,6 @@
 import numpy as np
 
-
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
+from statsmodels.tools.testing import ParamsTableTestBunch
 
 est = dict(
            rank=7,
@@ -98,7 +90,7 @@ def __init__(self, **kw):
                 '3.agecat', '4.agecat', '5.agecat', '_cons']
 
 
-results_noexposure_noconstraint = Bunch(
+results_noexposure_noconstraint = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -190,7 +182,7 @@ def __init__(self, **kw):
                 '4.agecat', '5.agecat', '_cons']
 
 
-results_exposure_noconstraint = Bunch(
+results_exposure_noconstraint = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -287,7 +279,7 @@ def __init__(self, **kw):
                 '3.agecat', '4.agecat', '5.agecat', '_cons']
 
 
-results_noexposure_constraint = Bunch(
+results_noexposure_constraint = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -379,7 +371,7 @@ def __init__(self, **kw):
                 '4.agecat', '5.agecat', '_cons']
 
 
-results_exposure_constraint = Bunch(
+results_exposure_constraint = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -476,7 +468,7 @@ def __init__(self, **kw):
                 '3.agecat', '4.agecat', '5.agecat', '_cons']
 
 
-results_noexposure_constraint2 = Bunch(
+results_noexposure_constraint2 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -568,7 +560,7 @@ def __init__(self, **kw):
                 '4.agecat', '5.agecat', '_cons']
 
 
-results_exposure_constraint2 = Bunch(
+results_exposure_constraint2 = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
@@ -662,7 +654,7 @@ def __init__(self, **kw):
                 '4.agecat', '5.agecat', '_cons']
 
 
-results_exposure_constraint2_robust = Bunch(
+results_exposure_constraint2_robust = ParamsTableTestBunch(
                 params_table=params_table,
                 params_table_colnames=params_table_colnames,
                 params_table_rownames=params_table_rownames,
diff --git a/statsmodels/gam/tests/results/results_mpg_bs.py b/statsmodels/gam/tests/results/results_mpg_bs.py
--- a/statsmodels/gam/tests/results/results_mpg_bs.py
+++ b/statsmodels/gam/tests/results/results_mpg_bs.py
@@ -1,12 +1,5 @@
-
 import numpy as np
-
-
-class Bunch(dict):
-
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
+from statsmodels.tools.tools import Bunch
 
 
 mpg_bs = Bunch()
diff --git a/statsmodels/gam/tests/results/results_mpg_bs_poisson.py b/statsmodels/gam/tests/results/results_mpg_bs_poisson.py
--- a/statsmodels/gam/tests/results/results_mpg_bs_poisson.py
+++ b/statsmodels/gam/tests/results/results_mpg_bs_poisson.py
@@ -1,12 +1,6 @@
-
 import numpy as np
 
-
-class Bunch(dict):
-
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
+from statsmodels.tools.tools import Bunch
 
 
 mpg_bs_poisson = Bunch()
diff --git a/statsmodels/gam/tests/results/results_pls.py b/statsmodels/gam/tests/results/results_pls.py
--- a/statsmodels/gam/tests/results/results_pls.py
+++ b/statsmodels/gam/tests/results/results_pls.py
@@ -1,12 +1,6 @@
-
 import numpy as np
 
-
-class Bunch(dict):
-
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
+from statsmodels.tools.tools import Bunch
 
 
 pls5 = Bunch()
diff --git a/statsmodels/genmod/tests/results/res_R_var_weight.py b/statsmodels/genmod/tests/results/res_R_var_weight.py
--- a/statsmodels/genmod/tests/results/res_R_var_weight.py
+++ b/statsmodels/genmod/tests/results/res_R_var_weight.py
@@ -3,14 +3,11 @@
 import numpy as np
 import pandas as pd
 import os
-path = os.path.abspath(__file__)
-dir_path = os.path.dirname(path)
 
+from statsmodels.tools.tools import Bunch
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
+path = os.path.abspath(__file__)
+dir_path = os.path.dirname(path)
 
 
 res = dict()
diff --git a/statsmodels/genmod/tests/results/results_glm_poisson_weights.py b/statsmodels/genmod/tests/results/results_glm_poisson_weights.py
--- a/statsmodels/genmod/tests/results/results_glm_poisson_weights.py
+++ b/statsmodels/genmod/tests/results/results_glm_poisson_weights.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
     deviance=18.59164098607571,
     dispers=1.859164098607571,
@@ -167,16 +169,7 @@
 resids_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_poisson_none_nonrobust = Bunch(
+results_poisson_none_nonrobust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -364,7 +357,7 @@ def __init__(self, **kw):
 resids_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_fweight_nonrobust = Bunch(
+results_poisson_fweight_nonrobust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -552,7 +545,7 @@ def __init__(self, **kw):
 resids_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_aweight_nonrobust = Bunch(
+results_poisson_aweight_nonrobust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -735,7 +728,7 @@ def __init__(self, **kw):
 resids_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_pweight_nonrobust = Bunch(
+results_poisson_pweight_nonrobust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -884,7 +877,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_none_hc1 = Bunch(
+results_poisson_none_hc1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1032,7 +1025,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_fweight_hc1 = Bunch(
+results_poisson_fweight_hc1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1180,7 +1173,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_aweight_hc1 = Bunch(
+results_poisson_aweight_hc1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1328,7 +1321,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_pweight_hc1 = Bunch(
+results_poisson_pweight_hc1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1476,7 +1469,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_none_clu1 = Bunch(
+results_poisson_none_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1626,7 +1619,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_fweight_clu1 = Bunch(
+results_poisson_fweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1776,7 +1769,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_aweight_clu1 = Bunch(
+results_poisson_aweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -1926,7 +1919,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_poisson_pweight_clu1 = Bunch(
+results_poisson_pweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -2038,7 +2031,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_wls_aweight_robust = Bunch(
+results_wls_aweight_robust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -2152,7 +2145,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_wls_aweight_clu1 = Bunch(
+results_wls_aweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -2266,7 +2259,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_wls_fweight_clu1 = Bunch(
+results_wls_fweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -2380,7 +2373,7 @@ def __init__(self, **kw):
 predicted_rownames = ['r'+str(n) for n in range(1, 18)]
 
 
-results_wls_pweight_clu1 = Bunch(
+results_wls_pweight_clu1 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/genmod/tests/results/test_get_R_tweedie_var_weight.R b/statsmodels/genmod/tests/results/test_get_R_tweedie_var_weight.R
--- a/statsmodels/genmod/tests/results/test_get_R_tweedie_var_weight.R
+++ b/statsmodels/genmod/tests/results/test_get_R_tweedie_var_weight.R
@@ -7,7 +7,8 @@ cat("# This file auto-generated by test_get_R_tweedie_var_weight.R\n")
 cat(sprintf("# Using: %s\n", R.Version()$version.string))
 cat("import numpy as np\n")
 cat("import pandas as pd\n")
-cat("import os\n")
+cat("import os\n\n")
+cat("from statsmodels.tools.tools import Bunch\n\n")
 cat("path = os.path.abspath(__file__)\n")
 cat("dir_path = os.path.dirname(path)\n")
 
@@ -71,11 +72,6 @@ pyprint <- function(arr, prefix=NULL, suffix=NULL) {
 
 cat("
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
 ")
 
 out2py <- function(model, name, resid_csv = FALSE){
diff --git a/statsmodels/miscmodels/tests/results_tmodel.py b/statsmodels/miscmodels/tests/results_tmodel.py
--- a/statsmodels/miscmodels/tests/results_tmodel.py
+++ b/statsmodels/miscmodels/tests/results_tmodel.py
@@ -8,12 +8,11 @@
 
 import numpy as np
 
-class Holder():
-    pass
+from statsmodels.tools.testing import Holder
 
 res_t_dfest = Holder()
-#> tfit3 <- tlm(m.marietta ~ CRSP, data = mm, start = list(dof = 3),estDof = TRUE)
-#> cat_items(tfit3, "res_t_dfest.")
+# > tfit3 <- tlm(m.marietta~CRSP, data=mm, start=list(dof = 3), estDof=TRUE)
+# > cat_items(tfit3, "res_t_dfest.")
 res_t_dfest.random = np.array([
      0.6242843, 1.349205, 1.224172, 1.272655, 1.323455, 1.091313, 1.227218,
      0.0316284, 0.7202973, 1.038392, 1.091907, 0.7966355, 0.784222,
diff --git a/statsmodels/miscmodels/tests/test_tmodel.py b/statsmodels/miscmodels/tests/test_tmodel.py
--- a/statsmodels/miscmodels/tests/test_tmodel.py
+++ b/statsmodels/miscmodels/tests/test_tmodel.py
@@ -11,14 +11,10 @@
 from numpy.testing import assert_allclose
 
 from statsmodels.tools.tools import add_constant
+from statsmodels.tools.testing import Holder
 from statsmodels.miscmodels.tmodel import TLinearModel
 
 
-class Holder(object):
-    def __init__(self, **kwds):
-        self.__dict__.update(kwds)
-
-
 mm = Holder()
 mm.date_label = ["Apr.1982",  "Apr.1983", "Apr.1984", "Apr.1985", "Apr.1986",
                  "Aug.1982", "Aug.1983",  "Aug.1984", "Aug.1985", "Aug.1986",
diff --git a/statsmodels/multivariate/tests/results/datamlw.py b/statsmodels/multivariate/tests/results/datamlw.py
--- a/statsmodels/multivariate/tests/results/datamlw.py
+++ b/statsmodels/multivariate/tests/results/datamlw.py
@@ -1,8 +1,6 @@
 from numpy import array
 
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 data = Holder()
diff --git a/statsmodels/regression/tests/results/macro_gr_corc_stata.py b/statsmodels/regression/tests/results/macro_gr_corc_stata.py
--- a/statsmodels/regression/tests/results/macro_gr_corc_stata.py
+++ b/statsmodels/regression/tests/results/macro_gr_corc_stata.py
@@ -1,5 +1,6 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
 est = dict(
     N=201,
     df_m=2,
@@ -496,16 +497,7 @@
 fittedvalues_se_rownames = ['r'+str(n) for n in range(1, 203)]
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results = Bunch(
+results = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/regression/tests/results/results_grunfeld_ols_robust_cluster.py b/statsmodels/regression/tests/results/results_grunfeld_ols_robust_cluster.py
--- a/statsmodels/regression/tests/results/results_grunfeld_ols_robust_cluster.py
+++ b/statsmodels/regression/tests/results/results_grunfeld_ols_robust_cluster.py
@@ -1,13 +1,6 @@
 import numpy as np
 
-
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
+from statsmodels.tools.testing import ParamsTableTestBunch
 
 
 est = dict(
@@ -61,7 +54,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster = Bunch(
+results_cluster = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -144,7 +137,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster_large = Bunch(
+results_cluster_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -196,7 +189,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_nw_groupsum4 = Bunch(
+results_nw_groupsum4 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -246,7 +239,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_nw_panel4 = Bunch(
+results_nw_panel4 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -335,7 +328,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster_2groups_small = Bunch(
+results_cluster_2groups_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -422,7 +415,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster_2groups_large = Bunch(
+results_cluster_2groups_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -510,7 +503,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_nw_groupsum4_ivreg_large = Bunch(
+results_nw_groupsum4_ivreg_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -600,7 +593,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_nw_groupsum4_ivreg_small = Bunch(
+results_nw_groupsum4_ivreg_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -665,7 +658,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_hc1_wls_small = Bunch(
+results_hc1_wls_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -728,7 +721,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster_wls_small = Bunch(
+results_cluster_wls_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -813,7 +806,7 @@ def __init__(self, **kw):
 cov_rownames = 'mvalue kstock _cons'.split()
 
 
-results_cluster_wls_large = Bunch(
+results_cluster_wls_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/regression/tests/results/results_macro_ols_robust.py b/statsmodels/regression/tests/results/results_macro_ols_robust.py
--- a/statsmodels/regression/tests/results/results_macro_ols_robust.py
+++ b/statsmodels/regression/tests/results/results_macro_ols_robust.py
@@ -1,9 +1,10 @@
 '''autogenerated and edited by hand
-
 '''
 
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
     N=202,
     df_m=2,
@@ -53,16 +54,7 @@
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_hc0 = Bunch(
+results_hc0 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -113,7 +105,7 @@ def __init__(self, **kw):
 
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
-results_newey4 = Bunch(
+results_newey4 = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -198,7 +190,7 @@ def __init__(self, **kw):
 
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
-results_ivhac4_small = Bunch(
+results_ivhac4_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -280,7 +272,7 @@ def __init__(self, **kw):
 
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
-results_ivhac4_large = Bunch(
+results_ivhac4_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -363,7 +355,7 @@ def __init__(self, **kw):
 
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
-results_ivhc0_small = Bunch(
+results_ivhc0_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -444,7 +436,7 @@ def __init__(self, **kw):
 
 cov_rownames = 'g_realgdp L.realint _cons'.split()
 
-results_ivhc0_large = Bunch(
+results_ivhc0_large = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/regression/tests/results/results_quantile_regression.py b/statsmodels/regression/tests/results/results_quantile_regression.py
--- a/statsmodels/regression/tests/results/results_quantile_regression.py
+++ b/statsmodels/regression/tests/results/results_quantile_regression.py
@@ -1,10 +1,6 @@
 import numpy as np
 
-
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
+from statsmodels.tools.tools import Bunch
 
 
 epanechnikov_hsheather_q75 = Bunch()
diff --git a/statsmodels/regression/tests/results/results_theil_textile.py b/statsmodels/regression/tests/results/results_theil_textile.py
--- a/statsmodels/regression/tests/results/results_theil_textile.py
+++ b/statsmodels/regression/tests/results/results_theil_textile.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
     rmse=.0136097497582343,
     r2=.9741055881598619,
@@ -51,16 +53,7 @@
 cov_prior_rownames = 'lincome lprice _cons'.split()
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_theil_textile = Bunch(
+results_theil_textile = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/sandbox/regression/tests/results_gmm_griliches.py b/statsmodels/sandbox/regression/tests/results_gmm_griliches.py
--- a/statsmodels/sandbox/regression/tests/results_gmm_griliches.py
+++ b/statsmodels/sandbox/regression/tests/results_gmm_griliches.py
@@ -5,6 +5,8 @@
 
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 # gmm twostep
 
 est = dict(
@@ -128,16 +130,7 @@
 cov_rownames = ['_cons'] * 13
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_twostep = Bunch(
+results_twostep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -270,7 +263,7 @@ def __init__(self, **kw):
 cov_rownames = ['_cons'] * 13
 
 
-results_onestep = Bunch(
+results_onestep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/sandbox/regression/tests/results_gmm_griliches_iter.py b/statsmodels/sandbox/regression/tests/results_gmm_griliches_iter.py
--- a/statsmodels/sandbox/regression/tests/results_gmm_griliches_iter.py
+++ b/statsmodels/sandbox/regression/tests/results_gmm_griliches_iter.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
     rank=13,
     N=758,
@@ -122,16 +124,7 @@
 cov_rownames = ['_cons'] * 13
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results = Bunch(
+results = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/sandbox/regression/tests/results_gmm_poisson.py b/statsmodels/sandbox/regression/tests/results_gmm_poisson.py
--- a/statsmodels/sandbox/regression/tests/results_gmm_poisson.py
+++ b/statsmodels/sandbox/regression/tests/results_gmm_poisson.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 est = dict(
     rank=8,
     N=3629,
@@ -84,16 +86,7 @@
 cov_rownames = ['_cons'] * 8
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results_addonestep = Bunch(
+results_addonestep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -187,7 +180,7 @@ def __init__(self, **kw):
 
 cov_rownames = ['_cons'] * 8
 
-results_addtwostep = Bunch(
+results_addtwostep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -282,7 +275,7 @@ def __init__(self, **kw):
 cov_rownames = ['_cons'] * 8
 
 
-results_multonestep = Bunch(
+results_multonestep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -376,7 +369,7 @@ def __init__(self, **kw):
 
 cov_rownames = ['_cons'] * 8
 
-results_multtwostep = Bunch(
+results_multtwostep = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -473,7 +466,7 @@ def __init__(self, **kw):
                 'xb_educyr', 'xb_actlim', 'xb_totchr', 'b0']
 
 
-results_multtwostepdefault = Bunch(
+results_multtwostepdefault = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -568,7 +561,7 @@ def __init__(self, **kw):
 cov_rownames = ['_cons'] * 8
 
 
-results_multtwostepcenter = Bunch(
+results_multtwostepcenter = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/sandbox/regression/tests/results_ivreg2_griliches.py b/statsmodels/sandbox/regression/tests/results_ivreg2_griliches.py
--- a/statsmodels/sandbox/regression/tests/results_ivreg2_griliches.py
+++ b/statsmodels/sandbox/regression/tests/results_ivreg2_griliches.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.testing import ParamsTableTestBunch
+
 param_names = [
     's', 'iq', 'expr', 'tenure', 'rns', 'smsa',
     'dyear_67', 'dyear_68', 'dyear_69', 'dyear_70', 'dyear_71', 'dyear_73',
@@ -149,16 +151,7 @@
 cov_rownames = param_names
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
-
-
-results = Bunch(
+results = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -311,7 +304,7 @@ def __init__(self, **kw):
 cov_rownames = param_names
 
 
-results_robust = Bunch(
+results_robust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -478,7 +471,7 @@ def __init__(self, **kw):
     DWH=70.84970589405181
 )
 
-results_small = Bunch(
+results_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -634,7 +627,7 @@ def __init__(self, **kw):
 cov_rownames = param_names
 
 
-results_small_robust = Bunch(
+results_small_robust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
@@ -787,7 +780,7 @@ def __init__(self, **kw):
 cov_rownames = param_names
 
 
-results_gmm2s_robust = Bunch(
+results_gmm2s_robust = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/sandbox/tests/maketests_mlabwrap.py b/statsmodels/sandbox/tests/maketests_mlabwrap.py
--- a/statsmodels/sandbox/tests/maketests_mlabwrap.py
+++ b/statsmodels/sandbox/tests/maketests_mlabwrap.py
@@ -122,7 +122,7 @@ def save(self, what=None, filename=None, header=True, useinstant=True,
         if header:
             txt = ['import numpy as np\nfrom numpy import array\n\n']
             if useinstant:
-                txt.append('class Holder(object):\n    pass\n\n')
+                txt.append('from statsmodels.tools.testing import Holder\n\n')
         else:
             txt = []
 
diff --git a/statsmodels/stats/tests/results/results_multinomial_proportions.py b/statsmodels/stats/tests/results/results_multinomial_proportions.py
--- a/statsmodels/stats/tests/results/results_multinomial_proportions.py
+++ b/statsmodels/stats/tests/results/results_multinomial_proportions.py
@@ -8,10 +8,7 @@
 import collections
 import numpy as np
 
-
-class Holder(object):
-    pass
-
+from statsmodels.tools.testing import Holder
 
 res_multinomial = collections.defaultdict(Holder)
 
diff --git a/statsmodels/stats/tests/results/results_panelrobust.py b/statsmodels/stats/tests/results/results_panelrobust.py
--- a/statsmodels/stats/tests/results/results_panelrobust.py
+++ b/statsmodels/stats/tests/results/results_panelrobust.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 cov_clu_stata = np.array([
     .00025262993207,
     -.00065043385106,
@@ -78,12 +80,6 @@
     148.60456447156]).reshape(3, 3)
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     cov_clu_stata=cov_clu_stata,
     cov_pnw0_stata=cov_pnw0_stata,
diff --git a/statsmodels/stats/tests/results/results_power.py b/statsmodels/stats/tests/results/results_power.py
--- a/statsmodels/stats/tests/results/results_power.py
+++ b/statsmodels/stats/tests/results/results_power.py
@@ -8,9 +8,7 @@
 
 import collections
 
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 # numbers from R package `pwr` pwr.chisq.test
diff --git a/statsmodels/stats/tests/results/results_proportion.py b/statsmodels/stats/tests/results/results_proportion.py
--- a/statsmodels/stats/tests/results/results_proportion.py
+++ b/statsmodels/stats/tests/results/results_proportion.py
@@ -9,10 +9,7 @@
 import collections
 import numpy as np
 
-
-class Holder(object):
-    pass
-
+from statsmodels.tools.testing import Holder
 
 # numbers from R package `pwr` pwr.chisq.test
 res_binom = collections.defaultdict(Holder)
diff --git a/statsmodels/stats/tests/test_corrpsd.py b/statsmodels/stats/tests/test_corrpsd.py
--- a/statsmodels/stats/tests/test_corrpsd.py
+++ b/statsmodels/stats/tests/test_corrpsd.py
@@ -7,16 +7,18 @@
 """
 from statsmodels.compat.platform import PLATFORM_WIN32
 
-import pytest
+import warnings
+
 import numpy as np
-import scipy.sparse as sparse
 from numpy.testing import assert_almost_equal, assert_allclose
+import scipy.sparse as sparse
+import pytest
+
 from statsmodels.stats.correlation_tools import (
     corr_nearest, corr_clipped, cov_nearest,
     _project_correlation_factors, corr_nearest_factor, _spg_optim,
     corr_thresholded, cov_nearest_factor_homog, FactoredPSDMatrix)
-
-import warnings
+from statsmodels.tools.testing import Holder
 
 
 def norm_f(x, y):
@@ -25,8 +27,6 @@ def norm_f(x, y):
     d = ((x - y)**2).sum()
     return np.sqrt(d)
 
-class Holder(object):
-    pass
 
 # R library Matrix results
 cov1_r = Holder()
diff --git a/statsmodels/stats/tests/test_gof.py b/statsmodels/stats/tests/test_gof.py
--- a/statsmodels/stats/tests/test_gof.py
+++ b/statsmodels/stats/tests/test_gof.py
@@ -12,9 +12,8 @@
 
 from statsmodels.stats.gof import (chisquare, chisquare_power,
                                    chisquare_effectsize)
+from statsmodels.tools.testing import Holder
 
-class Holder(object):
-    pass
 
 def test_chisquare_power():
     from .results.results_power import pwr_chisquare
diff --git a/statsmodels/stats/tests/test_inter_rater.py b/statsmodels/stats/tests/test_inter_rater.py
--- a/statsmodels/stats/tests/test_inter_rater.py
+++ b/statsmodels/stats/tests/test_inter_rater.py
@@ -11,9 +11,7 @@
 
 from statsmodels.stats.inter_rater import (fleiss_kappa, cohens_kappa,
                                            to_table, aggregate_raters)
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 table0 = np.asarray('''\
diff --git a/statsmodels/stats/tests/test_proportion.py b/statsmodels/stats/tests/test_proportion.py
--- a/statsmodels/stats/tests/test_proportion.py
+++ b/statsmodels/stats/tests/test_proportion.py
@@ -17,18 +17,17 @@
                                           multinomial_proportions_confint)
 import statsmodels.stats.proportion as smprop
 from statsmodels.tools.sm_exceptions import HypothesisTestWarning
+from statsmodels.tools.testing import Holder
 
 
-class Holder(object):
-    pass
-
-probci_methods = {'agresti_coull' : 'agresti-coull',
-                  'normal' : 'asymptotic',
-                  'beta' : 'exact',
-                  'wilson' : 'wilson',
-                  'jeffreys' : 'bayes'
+probci_methods = {'agresti_coull': 'agresti-coull',
+                  'normal': 'asymptotic',
+                  'beta': 'exact',
+                  'wilson': 'wilson',
+                  'jeffreys': 'bayes'
                   }
 
+
 def test_confint_proportion():
     from .results.results_proportion import res_binom, res_binom_methods
 
diff --git a/statsmodels/stats/tests/test_tost.py b/statsmodels/stats/tests/test_tost.py
--- a/statsmodels/stats/tests/test_tost.py
+++ b/statsmodels/stats/tests/test_tost.py
@@ -11,6 +11,9 @@
 
 from numpy.testing import assert_almost_equal, assert_equal, assert_
 
+from statsmodels.tools.testing import Holder
+
+
 def assert_almost_equal_inf(x, y, decimal=6, msg=None):
     x = np.atleast_1d(x)
     y = np.atleast_1d(y)
@@ -20,10 +23,6 @@ def assert_almost_equal_inf(x, y, decimal=6, msg=None):
     assert_almost_equal(x[np.isfinite(x)], y[np.isfinite(y)])
 
 
-
-class Holder(object):
-    pass
-
 raw_clinic = '''\
 1     1 2.84 4.00 3.45 2.55 2.46
 2     1 2.51 3.26 3.10 2.82 2.48
diff --git a/statsmodels/stats/tests/test_weightstats.py b/statsmodels/stats/tests/test_weightstats.py
--- a/statsmodels/stats/tests/test_weightstats.py
+++ b/statsmodels/stats/tests/test_weightstats.py
@@ -22,10 +22,7 @@
 
 from statsmodels.stats.weightstats import (DescrStatsW, CompareMeans,
                                            ttest_ind, ztest, zconfint)
-
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 # Mixin for tests against other packages.
diff --git a/statsmodels/tools/testing.py b/statsmodels/tools/testing.py
--- a/statsmodels/tools/testing.py
+++ b/statsmodels/tools/testing.py
@@ -6,6 +6,53 @@
 import numpy.testing as npt
 import pandas
 
+from statsmodels.tools.tools import Bunch
+
+# Standard list for parsing tables
+PARAM_LIST = ['params', 'bse', 'tvalues', 'pvalues']
+
+
+def bunch_factory(attribute, columns):
+    """
+    Generates a special purpose Bunch class
+
+    Parameters
+    ----------
+    attribute: str
+        Attribute to access when splitting
+    columns: List[str]
+        List of names to use when splitting the columns of attribute
+
+    Notes
+    -----
+    After the class is initialized as a Bunch, the columne of attribute
+    are split so that Bunch has the keys in columns and
+    bunch[column[i]] = bunch[attribute][:, i]
+    """
+    class FactoryBunch(Bunch):
+        def __init__(self, *args, **kwargs):
+            super(FactoryBunch, self).__init__(*args, **kwargs)
+            if not hasattr(self, attribute):
+                raise AttributeError('{0} is required and must be passed to '
+                                     'the constructor'.format(attribute))
+            for i, att in enumerate(columns):
+                self[att] = getattr(self, attribute)[:, i]
+
+    return FactoryBunch
+
+
+ParamsTableTestBunch = bunch_factory('params_table', PARAM_LIST)
+
+MarginTableTestBunch = bunch_factory('margins_table', PARAM_LIST)
+
+
+class Holder(object):
+    """
+    Test-focused class to simplify accessing values by attribute
+    """
+    def __init__(self, **kwds):
+        self.__dict__.update(kwds)
+
 
 # adjusted functions
 
diff --git a/statsmodels/tools/tests/test_testing.py b/statsmodels/tools/tests/test_testing.py
new file mode 100644
--- /dev/null
+++ b/statsmodels/tools/tests/test_testing.py
@@ -0,0 +1,27 @@
+import pytest
+import numpy as np
+
+from statsmodels.tools.testing import ParamsTableTestBunch, \
+    MarginTableTestBunch, Holder
+
+
+@pytest.mark.parametrize('attribute, bunch_type',
+                         (('params_table', ParamsTableTestBunch),
+                          ('margins_table', MarginTableTestBunch)))
+def check_params_table_classes(attribute, bunch_type):
+    table = np.empty((10, 4))
+    bunch = bunch_type(**{attribute: table})
+    assert attribute in bunch
+
+
+def test_bad_table():
+    table = np.empty((10, 4))
+    with pytest.raises(AttributeError):
+        ParamsTableTestBunch(margins_table=table)
+
+
+def test_holder():
+    holder = Holder()
+    holder.new_attr = 1
+    assert hasattr(holder, 'new_attr')
+    assert getattr(holder, 'new_attr') == 1
diff --git a/statsmodels/tsa/tests/results/arima111_css_results.py b/statsmodels/tsa/tests/results/arima111_css_results.py
--- a/statsmodels/tsa/tests/results/arima111_css_results.py
+++ b/statsmodels/tsa/tests/results/arima111_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-242.06033399744])
 
 nobs = np.array([202])
@@ -1281,12 +1283,6 @@
     505.35373878448])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima111_results.py b/statsmodels/tsa/tests/results/arima111_results.py
--- a/statsmodels/tsa/tests/results/arima111_results.py
+++ b/statsmodels/tsa/tests/results/arima111_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-241.75576160303])
 
 nobs = np.array([202])
@@ -1281,12 +1283,6 @@
     504.74459399566])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima111nc_css_results.py b/statsmodels/tsa/tests/results/arima111nc_css_results.py
--- a/statsmodels/tsa/tests/results/arima111nc_css_results.py
+++ b/statsmodels/tsa/tests/results/arima111nc_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-242.89663276735])
 
 nobs = np.array([202])
@@ -1273,12 +1275,6 @@
     501.7180686269])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima111nc_results.py b/statsmodels/tsa/tests/results/arima111nc_results.py
--- a/statsmodels/tsa/tests/results/arima111nc_results.py
+++ b/statsmodels/tsa/tests/results/arima111nc_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-243.77512585356])
 
 nobs = np.array([202])
@@ -1273,12 +1275,6 @@
     503.47505479933])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima112_css_results.py b/statsmodels/tsa/tests/results/arima112_css_results.py
--- a/statsmodels/tsa/tests/results/arima112_css_results.py
+++ b/statsmodels/tsa/tests/results/arima112_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-244.3852892951])
 
 nobs = np.array([202])
@@ -1291,12 +1293,6 @@
     515.31191707721])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima112_results.py b/statsmodels/tsa/tests/results/arima112_results.py
--- a/statsmodels/tsa/tests/results/arima112_results.py
+++ b/statsmodels/tsa/tests/results/arima112_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-245.40783909604])
 
 nobs = np.array([202])
@@ -1291,12 +1293,6 @@
     517.35701667909])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima112nc_css_results.py b/statsmodels/tsa/tests/results/arima112nc_css_results.py
--- a/statsmodels/tsa/tests/results/arima112nc_css_results.py
+++ b/statsmodels/tsa/tests/results/arima112nc_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-239.75290561974])
 
 nobs = np.array([202])
@@ -1281,12 +1283,6 @@
     500.73888202909])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima112nc_results.py b/statsmodels/tsa/tests/results/arima112nc_results.py
--- a/statsmodels/tsa/tests/results/arima112nc_results.py
+++ b/statsmodels/tsa/tests/results/arima112nc_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-240.79351748413])
 
 nobs = np.array([202])
@@ -1076,12 +1078,6 @@
     502.82010575786])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima211_css_results.py b/statsmodels/tsa/tests/results/arima211_css_results.py
--- a/statsmodels/tsa/tests/results/arima211_css_results.py
+++ b/statsmodels/tsa/tests/results/arima211_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-240.29558272688])
 
 nobs = np.array([202])
@@ -1291,12 +1293,6 @@
     507.13250394077])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima211_results.py b/statsmodels/tsa/tests/results/arima211_results.py
--- a/statsmodels/tsa/tests/results/arima211_results.py
+++ b/statsmodels/tsa/tests/results/arima211_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-239.91961140777])
 
 nobs = np.array([202])
@@ -1291,12 +1293,6 @@
     506.38056130255])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima211nc_css_results.py b/statsmodels/tsa/tests/results/arima211nc_css_results.py
--- a/statsmodels/tsa/tests/results/arima211nc_css_results.py
+++ b/statsmodels/tsa/tests/results/arima211nc_css_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-240.21658671417])
 
 nobs = np.array([202])
@@ -1281,12 +1283,6 @@
     501.66624421795])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/arima211nc_results.py b/statsmodels/tsa/tests/results/arima211nc_results.py
--- a/statsmodels/tsa/tests/results/arima211nc_results.py
+++ b/statsmodels/tsa/tests/results/arima211nc_results.py
@@ -1,5 +1,7 @@
 import numpy as np
 
+from statsmodels.tools.tools import Bunch
+
 llf = np.array([-241.25977940638])
 
 nobs = np.array([202])
@@ -1076,12 +1078,6 @@
     503.75262960236])
 
 
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-
 results = Bunch(
     llf=llf,
     nobs=nobs,
diff --git a/statsmodels/tsa/tests/results/datamlw_tls.py b/statsmodels/tsa/tests/results/datamlw_tls.py
--- a/statsmodels/tsa/tests/results/datamlw_tls.py
+++ b/statsmodels/tsa/tests/results/datamlw_tls.py
@@ -1,8 +1,6 @@
 from numpy import array
 
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 mlpacf = Holder()
diff --git a/statsmodels/tsa/tests/results/results_process.py b/statsmodels/tsa/tests/results/results_process.py
--- a/statsmodels/tsa/tests/results/results_process.py
+++ b/statsmodels/tsa/tests/results/results_process.py
@@ -1,9 +1,6 @@
 from numpy import array
 
-
-class Holder(object):
-    pass
-
+from statsmodels.tools.testing import Holder
 
 armarep = Holder()
 armarep.comment = ('mlab.garchma(-res_armarep.ar[1:], res_armarep.ma[1:], 20)'
diff --git a/statsmodels/tsa/tests/results/savedrvs.py b/statsmodels/tsa/tests/results/savedrvs.py
--- a/statsmodels/tsa/tests/results/savedrvs.py
+++ b/statsmodels/tsa/tests/results/savedrvs.py
@@ -6,9 +6,7 @@
 
 from numpy import array
 
-
-class Holder(object):
-    pass
+from statsmodels.tools.testing import Holder
 
 
 rvsdata = Holder()
diff --git a/statsmodels/tsa/vector_ar/tests/results/results_svar_st.py b/statsmodels/tsa/vector_ar/tests/results/results_svar_st.py
--- a/statsmodels/tsa/vector_ar/tests/results/results_svar_st.py
+++ b/statsmodels/tsa/vector_ar/tests/results/results_svar_st.py
@@ -1,13 +1,6 @@
 import numpy as np
 
-
-class Bunch(dict):
-    def __init__(self, **kw):
-        dict.__init__(self, kw)
-        self.__dict__ = self
-
-        for i, att in enumerate(['params', 'bse', 'tvalues', 'pvalues']):
-            self[att] = self.params_table[:, i]
+from statsmodels.tools.testing import ParamsTableTestBunch
 
 
 est = dict(
@@ -704,7 +697,7 @@ def __init__(self, **kw):
 b_var_rownames = 'y1'.split()
 
 
-results_svar1_small = Bunch(
+results_svar1_small = ParamsTableTestBunch(
     params_table=params_table,
     params_table_colnames=params_table_colnames,
     params_table_rownames=params_table_rownames,
diff --git a/statsmodels/tsa/vector_ar/tests/results/results_var_data.py b/statsmodels/tsa/vector_ar/tests/results/results_var_data.py
--- a/statsmodels/tsa/vector_ar/tests/results/results_var_data.py
+++ b/statsmodels/tsa/vector_ar/tests/results/results_var_data.py
@@ -1,9 +1,6 @@
 from numpy import array, rec
 
-
-class Holder(object):
-    pass
-
+from statsmodels.tools.testing import Holder
 
 var_results = Holder()
 var_results.comment = 'VAR test data converted from vars_results.npz'