| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- import numpy as np
- import pytest
- from irae_risk.logistic import LogisticPolyRegression
- @pytest.fixture
- def regression_data():
- x = np.linspace(-2.0, 2.0, 20)
- y = np.array([0, 1] * 10)
- theta = np.array([-0.2, 0.8])
- return x, y, theta
- def test_goodness_of_fit_uses_unpenalized_likelihood_by_default(regression_data):
- x, y, theta = regression_data
- model = LogisticPolyRegression(degree=1, lam=(0.0, 0.5))
- result = model.goodness_of_fit(x, y, theta, bootstrap_samples=20)
- expected_llf = -model.get_nllf(x, y, theta)
- k = len(theta)
- n = len(x)
- assert result["LLF"] == pytest.approx(expected_llf)
- assert result["AIC"] == pytest.approx(2 * k - 2 * expected_llf)
- assert result["BIC"] == pytest.approx(k * np.log(n) - 2 * expected_llf)
- def test_goodness_of_fit_can_include_regularization(regression_data):
- x, y, theta = regression_data
- model = LogisticPolyRegression(degree=1, lam=(0.0, 0.5))
- unregularized = model.goodness_of_fit(
- x, y, theta, bootstrap_samples=20
- )
- regularized = model.goodness_of_fit(
- x,
- y,
- theta,
- regularization=True,
- bootstrap_samples=20,
- )
- penalty = model.penalty(theta)
- assert regularized["LLF"] == pytest.approx(
- -model.get_cost(x, y, theta)
- )
- assert unregularized["LLF"] - regularized["LLF"] == pytest.approx(penalty)
- assert regularized["AIC"] - unregularized["AIC"] == pytest.approx(
- 2 * penalty
- )
- assert regularized["BIC"] - unregularized["BIC"] == pytest.approx(
- 2 * penalty
- )
- def test_regularization_switch_has_no_effect_without_penalty(regression_data):
- x, y, theta = regression_data
- model = LogisticPolyRegression(degree=1)
- default = model.goodness_of_fit(x, y, theta, bootstrap_samples=20)
- regularized = model.goodness_of_fit(
- x,
- y,
- theta,
- regularization=True,
- bootstrap_samples=20,
- )
- assert regularized == pytest.approx(default)
- def test_goodness_of_fit_reports_reproducible_bootstrap_deviance(regression_data):
- x, y, theta = regression_data
- model = LogisticPolyRegression(degree=1)
- first = model.goodness_of_fit(
- x,
- y,
- theta,
- bootstrap_samples=25,
- bootstrap_seed=123,
- )
- second = model.goodness_of_fit(
- x,
- y,
- theta,
- bootstrap_samples=25,
- bootstrap_seed=123,
- )
- assert "chi2" not in first
- assert "p-value(chi2)" not in first
- assert first["deviance"] == pytest.approx(2 * model.get_nllf(x, y, theta))
- assert first["p-value(deviance_bootstrap)"] == second[
- "p-value(deviance_bootstrap)"
- ]
- assert 0 < first["p-value(deviance_bootstrap)"] <= 1
- assert first["deviance_bootstrap_samples"] == 25
- def test_covariance_methods_match_sandwich_formulas(regression_data):
- x, y, theta = regression_data
- ridge = 0.5
- model = LogisticPolyRegression(degree=1, lam=(0.0, ridge))
- design = np.column_stack([np.ones_like(x), x])
- probabilities = model.model(x, theta)
- weights = probabilities * (1 - probabilities)
- information = (design.T * weights) @ design
- bread = information + 2 * ridge * np.eye(2)
- bread_inv = np.linalg.pinv(bread, hermitian=True)
- scores = (y - probabilities)[:, None] * design
- robust_meat = scores.T @ scores
- expected_model = bread_inv @ information @ bread_inv
- expected_robust = bread_inv @ robust_meat @ bread_inv
- np.testing.assert_allclose(
- model.get_cov(x, y, theta),
- expected_model,
- )
- np.testing.assert_allclose(
- model.get_cov(x, y, theta, method="robust_sandwich"),
- expected_robust,
- )
- np.testing.assert_allclose(
- model.get_cov(x, y, theta, method="inverse_hessian"),
- bread_inv,
- )
- def test_covariance_rejects_unknown_method_and_l1_penalty(regression_data):
- x, y, theta = regression_data
- model = LogisticPolyRegression(degree=1)
- with pytest.raises(ValueError, match="Unknown covariance method"):
- model.get_cov(x, y, theta, method="not-a-method")
- l1_model = LogisticPolyRegression(degree=1, lam=(0.1, 0.0))
- with pytest.raises(ValueError, match="nonzero L1"):
- l1_model.get_cov(x, y, theta)
|