Repository: statsmodels/statsmodels

Issue:
t_test wald_test raises if only cov_params_default
If normalized_cov_params is None, and the model only specifies cov_params_default, then the check in t_test and wald_test raise a ValueError.

This was missed when conv_params_default was implemented. The check in `cov_params` was adjusted.

this breaks t_test in SARIMAX which doesn't define `normalized_cov_params`



Discussion:

Resolving patch:
diff --git a/statsmodels/base/model.py b/statsmodels/base/model.py
--- a/statsmodels/base/model.py
+++ b/statsmodels/base/model.py
@@ -1164,7 +1164,8 @@ def t_test(self, r_matrix, cov_p=None, scale=None,
         num_ttests = r_matrix.shape[0]
         num_params = r_matrix.shape[1]
 
-        if cov_p is None and self.normalized_cov_params is None:
+        if (cov_p is None and self.normalized_cov_params is None and
+            not hasattr(self, 'cov_params_default')):
             raise ValueError('Need covariance of parameters for computing '
                              'T statistics')
         if num_params != self.params.shape[0]:
@@ -1366,7 +1367,7 @@ def wald_test(self, r_matrix, cov_p=None, scale=1.0, invcov=None,
         r_matrix, q_matrix = LC.coefs, LC.constants
 
         if (self.normalized_cov_params is None and cov_p is None and
-                invcov is None):
+                invcov is None and not hasattr(self, 'cov_params_default')):
             raise ValueError('need covariance of parameters for computing '
                              'F statistics')
 


Test patch:
diff --git a/statsmodels/discrete/tests/test_sandwich_cov.py b/statsmodels/discrete/tests/test_sandwich_cov.py
--- a/statsmodels/discrete/tests/test_sandwich_cov.py
+++ b/statsmodels/discrete/tests/test_sandwich_cov.py
@@ -19,6 +19,7 @@
 from statsmodels.tools.tools import add_constant
 
 from numpy.testing import assert_allclose, assert_equal
+import statsmodels.tools._testing as smt
 
 
 # get data and results as module global for now, TODO: move to class
@@ -82,6 +83,14 @@ def test_oth(self):
         assert_allclose(res1._results.llnull, res2.ll_0, 1e-4)
 
 
+    def test_ttest(self):
+        smt.check_ttest_tvalues(self.res1)
+
+
+    def test_waldtest(self):
+        smt.check_ftest_pvalues(self.res1)
+
+
 class TestPoissonClu(CheckCountRobustMixin):
 
     @classmethod
@@ -170,6 +179,11 @@ def setup_class(cls):
                            use_t=False, #True,
                            )
 
+        # The model results, t_test, ... should also work without
+        # normalized_cov_params, see #2209
+        # Note: we cannot set on the wrapper res1, we need res1._results
+        cls.res1._results.normalized_cov_params = None
+
         cls.bse_rob = cls.res1.bse
 
         # backwards compatibility with inherited test methods
@@ -305,6 +319,12 @@ def setup_class(cls):
                                                 df_correction=True),  #TODO has no effect
                                   use_t=False, #True,
                                                 )
+
+        # The model results, t_test, ... should also work without
+        # normalized_cov_params, see #2209
+        # Note: we cannot set on the wrapper res1, we need res1._results
+        cls.res1._results.normalized_cov_params = None
+
         cls.bse_rob = cls.res1.bse
 
         nobs, k_vars = mod.exog.shape
diff --git a/statsmodels/tools/_testing.py b/statsmodels/tools/_testing.py
new file mode 100644
--- /dev/null
+++ b/statsmodels/tools/_testing.py
@@ -0,0 +1,135 @@
+"""Testing helper functions
+
+Warning: current status experimental, mostly copy paste
+
+Warning: these functions will be changed without warning as the need
+during refactoring arises.
+
+The first group of functions provide consistency checks
+
+"""
+
+import numpy as np
+from numpy.testing import assert_allclose, assert_
+from nose import SkipTest
+
+# the following are copied from
+# statsmodels.base.tests.test_generic_methods.CheckGenericMixin
+# and only adjusted to work as standalone functions
+
+def check_ttest_tvalues(results):
+    # test that t_test has same results a params, bse, tvalues, ...
+    res = results
+    mat = np.eye(len(res.params))
+    tt = res.t_test(mat)
+
+    assert_allclose(tt.effect, res.params, rtol=1e-12)
+    # TODO: tt.sd and tt.tvalue are 2d also for single regressor, squeeze
+    assert_allclose(np.squeeze(tt.sd), res.bse, rtol=1e-10)
+    assert_allclose(np.squeeze(tt.tvalue), res.tvalues, rtol=1e-12)
+    assert_allclose(tt.pvalue, res.pvalues, rtol=5e-10)
+    assert_allclose(tt.conf_int(), res.conf_int(), rtol=1e-10)
+
+    # test params table frame returned by t_test
+    table_res = np.column_stack((res.params, res.bse, res.tvalues,
+                                res.pvalues, res.conf_int()))
+    table1 = np.column_stack((tt.effect, tt.sd, tt.tvalue, tt.pvalue,
+                             tt.conf_int()))
+    table2 = tt.summary_frame().values
+    assert_allclose(table2, table_res, rtol=1e-12)
+
+    # move this to test_attributes ?
+    assert_(hasattr(res, 'use_t'))
+
+    tt = res.t_test(mat[0])
+    tt.summary()   # smoke test for #1323
+    assert_allclose(tt.pvalue, res.pvalues[0], rtol=5e-10)
+
+
+def check_ftest_pvalues(results):
+    res = results
+    use_t = res.use_t
+    k_vars = len(res.params)
+    # check default use_t
+    pvals = [res.wald_test(np.eye(k_vars)[k], use_f=use_t).pvalue
+                                               for k in range(k_vars)]
+    assert_allclose(pvals, res.pvalues, rtol=5e-10, atol=1e-25)
+
+    # sutomatic use_f based on results class use_t
+    pvals = [res.wald_test(np.eye(k_vars)[k]).pvalue
+                                               for k in range(k_vars)]
+    assert_allclose(pvals, res.pvalues, rtol=5e-10, atol=1e-25)
+
+    # label for pvalues in summary
+    string_use_t = 'P>|z|' if use_t is False else 'P>|t|'
+    summ = str(res.summary())
+    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
+#@knownfailureif(True)
+def check_fitted(results):
+    # ignore wrapper for isinstance check
+    from statsmodels.genmod.generalized_linear_model import GLMResults
+    from statsmodels.discrete.discrete_model import DiscreteResults
+    # FIXME: work around GEE has no wrapper
+    if hasattr(results, '_results'):
+        results = results._results
+    else:
+        results = results
+    if (isinstance(results, GLMResults) or
+        isinstance(results, DiscreteResults)):
+        raise SkipTest
+
+    res = results
+    fitted = res.fittedvalues
+    assert_allclose(res.model.endog - fitted, res.resid, rtol=1e-12)
+    assert_allclose(fitted, res.predict(), rtol=1e-12)
+
+def check_predict_types(results):
+    res = results
+    # squeeze to make 1d for single regressor test case
+    p_exog = np.squeeze(np.asarray(res.model.exog[:2]))
+
+    # ignore wrapper for isinstance check
+    from statsmodels.genmod.generalized_linear_model import GLMResults
+    from statsmodels.discrete.discrete_model import DiscreteResults
+
+    # FIXME: work around GEE has no wrapper
+    if hasattr(results, '_results'):
+        results = results._results
+    else:
+        results = results
+
+    if (isinstance(results, GLMResults) or
+        isinstance(results, DiscreteResults)):
+        # SMOKE test only  TODO
+        res.predict(p_exog)
+        res.predict(p_exog.tolist())
+        res.predict(p_exog[0].tolist())
+    else:
+        fitted = res.fittedvalues[:2]
+        assert_allclose(fitted, res.predict(p_exog), rtol=1e-12)
+        # this needs reshape to column-vector:
+        assert_allclose(fitted, res.predict(np.squeeze(p_exog).tolist()),
+                        rtol=1e-12)
+        # only one prediction:
+        assert_allclose(fitted[:1], res.predict(p_exog[0].tolist()),
+                        rtol=1e-12)
+        assert_allclose(fitted[:1], res.predict(p_exog[0]),
+                        rtol=1e-12)
+
+        # predict doesn't preserve DataFrame, e.g. dot converts to ndarray
+        #import pandas
+        #predicted = res.predict(pandas.DataFrame(p_exog))
+        #assert_(isinstance(predicted, pandas.DataFrame))
+        #assert_allclose(predicted, fitted, rtol=1e-12)
+