273 lines
9.0 KiB
Python
273 lines
9.0 KiB
Python
|
"""
|
||
|
Unit tests for fit_constrained
|
||
|
Tests for Poisson and Binomial are in discrete
|
||
|
|
||
|
|
||
|
Created on Sun Jan 7 09:21:39 2018
|
||
|
|
||
|
Author: Josef Perktold
|
||
|
"""
|
||
|
|
||
|
import warnings
|
||
|
|
||
|
import numpy as np
|
||
|
from numpy.testing import assert_allclose, assert_equal
|
||
|
|
||
|
from statsmodels.genmod.families import family
|
||
|
from statsmodels.genmod.generalized_linear_model import GLM
|
||
|
from statsmodels.regression.linear_model import OLS, WLS
|
||
|
from statsmodels.tools.sm_exceptions import ValueWarning
|
||
|
from statsmodels.tools.tools import add_constant
|
||
|
|
||
|
|
||
|
class ConstrainedCompareMixin:
|
||
|
|
||
|
@classmethod
|
||
|
def setup_class(cls):
|
||
|
nobs, k_exog = 100, 5
|
||
|
np.random.seed(987125)
|
||
|
x = np.random.randn(nobs, k_exog - 1)
|
||
|
x = add_constant(x)
|
||
|
|
||
|
y_true = x.sum(1) / 2
|
||
|
y = y_true + 2 * np.random.randn(nobs)
|
||
|
cls.endog = y
|
||
|
cls.exog = x
|
||
|
cls.idx_uc = [0, 2, 3, 4]
|
||
|
cls.idx_p_uc = np.array(cls.idx_uc)
|
||
|
cls.idx_c = [1]
|
||
|
cls.exogc = xc = x[:, cls.idx_uc]
|
||
|
mod_ols_c = OLS(y - 0.5 * x[:, 1], xc)
|
||
|
mod_ols_c.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.mod2 = mod_ols_c
|
||
|
cls.init()
|
||
|
|
||
|
def test_params(self):
|
||
|
res1 = self.res1
|
||
|
res2 = self.res2
|
||
|
assert_allclose(res1.params[self.idx_p_uc], res2.params, rtol=1e-10)
|
||
|
|
||
|
def test_se(self):
|
||
|
res1 = self.res1
|
||
|
res2 = self.res2
|
||
|
|
||
|
assert_equal(res1.df_resid, res2.df_resid)
|
||
|
assert_allclose(res1.scale, res2.scale, rtol=1e-10)
|
||
|
assert_allclose(res1.bse[self.idx_p_uc], res2.bse, rtol=1e-10)
|
||
|
assert_allclose(res1.cov_params()[self.idx_p_uc[:, None],
|
||
|
self.idx_p_uc], res2.cov_params(), rtol=5e-9, atol=1e-15)
|
||
|
|
||
|
def test_resid(self):
|
||
|
res1 = self.res1
|
||
|
res2 = self.res2
|
||
|
assert_allclose(res1.resid_response, res2.resid, rtol=1e-10)
|
||
|
|
||
|
|
||
|
class TestGLMGaussianOffset(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit()
|
||
|
mod = GLM(cls.endog, cls.exogc,
|
||
|
offset=0.5 * cls.exog[:, cls.idx_c].squeeze())
|
||
|
mod.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit()
|
||
|
cls.idx_p_uc = np.arange(cls.exogc.shape[1])
|
||
|
|
||
|
|
||
|
class TestGLMGaussianConstrained(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit()
|
||
|
mod = GLM(cls.endog, cls.exog)
|
||
|
mod.exog_names[:] = ['const', 'x1', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit_constrained('x1=0.5')
|
||
|
|
||
|
|
||
|
class TestGLMGaussianOffsetHC(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cov_type = 'HC0'
|
||
|
cls.res2 = cls.mod2.fit(cov_type=cov_type)
|
||
|
mod = GLM(cls.endog, cls.exogc,
|
||
|
offset=0.5 * cls.exog[:, cls.idx_c].squeeze())
|
||
|
mod.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit(cov_type=cov_type)
|
||
|
cls.idx_p_uc = np.arange(cls.exogc.shape[1])
|
||
|
|
||
|
|
||
|
class TestGLMGaussianConstrainedHC(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cov_type = 'HC0'
|
||
|
cls.res2 = cls.mod2.fit(cov_type=cov_type)
|
||
|
mod = GLM(cls.endog, cls.exog)
|
||
|
mod.exog_names[:] = ['const', 'x1', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit_constrained('x1=0.5', cov_type=cov_type)
|
||
|
|
||
|
|
||
|
class ConstrainedCompareWtdMixin(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def setup_class(cls):
|
||
|
nobs, k_exog = 100, 5
|
||
|
np.random.seed(987125)
|
||
|
x = np.random.randn(nobs, k_exog - 1)
|
||
|
x = add_constant(x)
|
||
|
cls.aweights = np.random.randint(1, 10, nobs)
|
||
|
|
||
|
y_true = x.sum(1) / 2
|
||
|
y = y_true + 2 * np.random.randn(nobs)
|
||
|
cls.endog = y
|
||
|
cls.exog = x
|
||
|
cls.idx_uc = [0, 2, 3, 4]
|
||
|
cls.idx_p_uc = np.array(cls.idx_uc)
|
||
|
cls.idx_c = [1]
|
||
|
cls.exogc = xc = x[:, cls.idx_uc]
|
||
|
mod_ols_c = WLS(y - 0.5 * x[:, 1], xc, weights=cls.aweights)
|
||
|
mod_ols_c.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.mod2 = mod_ols_c
|
||
|
cls.init()
|
||
|
|
||
|
|
||
|
class TestGLMWtdGaussianOffset(ConstrainedCompareWtdMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit()
|
||
|
mod = GLM(cls.endog, cls.exogc,
|
||
|
offset=0.5 * cls.exog[:, cls.idx_c].squeeze(),
|
||
|
var_weights=cls.aweights)
|
||
|
mod.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit()
|
||
|
cls.idx_p_uc = np.arange(cls.exogc.shape[1])
|
||
|
|
||
|
|
||
|
class TestGLMWtdGaussianConstrained(ConstrainedCompareWtdMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit()
|
||
|
mod = GLM(cls.endog, cls.exog, var_weights=cls.aweights)
|
||
|
mod.exog_names[:] = ['const', 'x1', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit_constrained('x1=0.5')
|
||
|
|
||
|
|
||
|
class TestGLMWtdGaussianOffsetHC(ConstrainedCompareWtdMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cov_type = 'HC0'
|
||
|
cls.res2 = cls.mod2.fit(cov_type=cov_type)
|
||
|
mod = GLM(cls.endog, cls.exogc,
|
||
|
offset=0.5 * cls.exog[:, cls.idx_c].squeeze(),
|
||
|
var_weights=cls.aweights)
|
||
|
mod.exog_names[:] = ['const', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit(cov_type=cov_type)
|
||
|
cls.idx_p_uc = np.arange(cls.exogc.shape[1])
|
||
|
|
||
|
|
||
|
class TestGLMWtdGaussianConstrainedHC(ConstrainedCompareWtdMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cov_type = 'HC0'
|
||
|
cls.res2 = cls.mod2.fit(cov_type=cov_type)
|
||
|
mod = GLM(cls.endog, cls.exog, var_weights=cls.aweights)
|
||
|
mod.exog_names[:] = ['const', 'x1', 'x2', 'x3', 'x4']
|
||
|
cls.res1 = mod.fit_constrained('x1=0.5', cov_type=cov_type)
|
||
|
|
||
|
|
||
|
class TestGLMBinomialCountConstrained(ConstrainedCompareMixin):
|
||
|
|
||
|
@classmethod
|
||
|
def setup_class(cls):
|
||
|
from statsmodels.datasets.star98 import load
|
||
|
|
||
|
#from statsmodels.genmod.tests.results.results_glm import Star98
|
||
|
data = load()
|
||
|
data.exog = np.asarray(data.exog)
|
||
|
data.endog = np.asarray(data.endog)
|
||
|
exog = add_constant(data.exog, prepend=True)
|
||
|
offset = np.ones(len(data.endog))
|
||
|
exog_keep = exog[:, :-5]
|
||
|
cls.mod2 = GLM(data.endog, exog_keep, family=family.Binomial(),
|
||
|
offset=offset)
|
||
|
|
||
|
cls.mod1 = GLM(data.endog, exog, family=family.Binomial(),
|
||
|
offset=offset)
|
||
|
cls.init()
|
||
|
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit()
|
||
|
k = cls.mod1.exog.shape[1]
|
||
|
cls.idx_p_uc = np.arange(k - 5)
|
||
|
constraints = np.eye(k)[-5:]
|
||
|
cls.res1 = cls.mod1.fit_constrained(constraints)
|
||
|
|
||
|
def test_resid(self):
|
||
|
# need to override because res2 does not have resid
|
||
|
res1 = self.res1
|
||
|
res2 = self.res2
|
||
|
assert_allclose(res1.resid_response, res2.resid_response, rtol=1e-8)
|
||
|
|
||
|
def test_glm_attr(self):
|
||
|
for attr in ['llf', 'null_deviance', 'aic', 'df_resid',
|
||
|
'df_model', 'pearson_chi2', 'scale']:
|
||
|
assert_allclose(getattr(self.res1, attr),
|
||
|
getattr(self.res2, attr), rtol=1e-10)
|
||
|
with warnings.catch_warnings():
|
||
|
warnings.simplefilter("ignore", FutureWarning)
|
||
|
# FutureWarning to silence BIC warning
|
||
|
assert_allclose(self.res1.bic, self.res2.bic, rtol=1e-10)
|
||
|
|
||
|
def test_wald(self):
|
||
|
res1 = self.res1
|
||
|
res2 = self.res2
|
||
|
k1 = len(res1.params)
|
||
|
k2 = len(res2.params)
|
||
|
|
||
|
use_f = False
|
||
|
with warnings.catch_warnings():
|
||
|
warnings.simplefilter('ignore', ValueWarning)
|
||
|
wt2 = res2.wald_test(np.eye(k2)[1:], use_f=use_f, scalar=True)
|
||
|
wt1 = res1.wald_test(np.eye(k1)[1:], use_f=use_f, scalar=True)
|
||
|
assert_allclose(wt2.pvalue, wt1.pvalue, atol=1e-20) # pvalue = 0
|
||
|
assert_allclose(wt2.statistic, wt1.statistic, rtol=1e-8)
|
||
|
assert_equal(wt2.df_denom, wt1.df_denom)
|
||
|
|
||
|
use_f = True
|
||
|
with warnings.catch_warnings():
|
||
|
warnings.simplefilter('ignore', ValueWarning)
|
||
|
wt2 = res2.wald_test(np.eye(k2)[1:], use_f=use_f, scalar=True)
|
||
|
wt1 = res1.wald_test(np.eye(k1)[1:], use_f=use_f, scalar=True)
|
||
|
assert_allclose(wt2.pvalue, wt1.pvalue, rtol=1) # pvalue = 8e-273
|
||
|
assert_allclose(wt2.statistic, wt1.statistic, rtol=1e-8)
|
||
|
assert_equal(wt2.df_denom, wt1.df_denom)
|
||
|
assert_equal(wt2.df_num, wt1.df_num)
|
||
|
assert_equal(wt2.summary()[-30:], wt1.summary()[-30:])
|
||
|
|
||
|
# smoke
|
||
|
with warnings.catch_warnings():
|
||
|
# RuntimeWarnings because of truedivide and scipy distributions
|
||
|
# Future to silence BIC warning
|
||
|
warnings.simplefilter("ignore", FutureWarning)
|
||
|
warnings.simplefilter('ignore', ValueWarning)
|
||
|
warnings.simplefilter('ignore', RuntimeWarning)
|
||
|
self.res1.summary()
|
||
|
self.res1.summary2()
|
||
|
|
||
|
|
||
|
class TestGLMBinomialCountConstrainedHC(TestGLMBinomialCountConstrained):
|
||
|
@classmethod
|
||
|
def init(cls):
|
||
|
cls.res2 = cls.mod2.fit(cov_type='HC0')
|
||
|
k = cls.mod1.exog.shape[1]
|
||
|
cls.idx_p_uc = np.arange(k - 5)
|
||
|
constraints = np.eye(k)[-5:]
|
||
|
cls.res1 = cls.mod1.fit_constrained(constraints, cov_type='HC0')
|