test_logistic.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import numpy as np
  2. import pytest
  3. from irae_risk.logistic import LogisticPolyRegression
  4. @pytest.fixture
  5. def regression_data():
  6. x = np.linspace(-2.0, 2.0, 20)
  7. y = np.array([0, 1] * 10)
  8. theta = np.array([-0.2, 0.8])
  9. return x, y, theta
  10. def test_goodness_of_fit_uses_unpenalized_likelihood_by_default(regression_data):
  11. x, y, theta = regression_data
  12. model = LogisticPolyRegression(degree=1, lam=(0.0, 0.5))
  13. result = model.goodness_of_fit(x, y, theta, bootstrap_samples=20)
  14. expected_llf = -model.get_nllf(x, y, theta)
  15. k = len(theta)
  16. n = len(x)
  17. assert result["LLF"] == pytest.approx(expected_llf)
  18. assert result["AIC"] == pytest.approx(2 * k - 2 * expected_llf)
  19. assert result["BIC"] == pytest.approx(k * np.log(n) - 2 * expected_llf)
  20. def test_goodness_of_fit_can_include_regularization(regression_data):
  21. x, y, theta = regression_data
  22. model = LogisticPolyRegression(degree=1, lam=(0.0, 0.5))
  23. unregularized = model.goodness_of_fit(
  24. x, y, theta, bootstrap_samples=20
  25. )
  26. regularized = model.goodness_of_fit(
  27. x,
  28. y,
  29. theta,
  30. regularization=True,
  31. bootstrap_samples=20,
  32. )
  33. penalty = model.penalty(theta)
  34. assert regularized["LLF"] == pytest.approx(
  35. -model.get_cost(x, y, theta)
  36. )
  37. assert unregularized["LLF"] - regularized["LLF"] == pytest.approx(penalty)
  38. assert regularized["AIC"] - unregularized["AIC"] == pytest.approx(
  39. 2 * penalty
  40. )
  41. assert regularized["BIC"] - unregularized["BIC"] == pytest.approx(
  42. 2 * penalty
  43. )
  44. def test_regularization_switch_has_no_effect_without_penalty(regression_data):
  45. x, y, theta = regression_data
  46. model = LogisticPolyRegression(degree=1)
  47. default = model.goodness_of_fit(x, y, theta, bootstrap_samples=20)
  48. regularized = model.goodness_of_fit(
  49. x,
  50. y,
  51. theta,
  52. regularization=True,
  53. bootstrap_samples=20,
  54. )
  55. assert regularized == pytest.approx(default)
  56. def test_goodness_of_fit_reports_reproducible_bootstrap_deviance(regression_data):
  57. x, y, theta = regression_data
  58. model = LogisticPolyRegression(degree=1)
  59. first = model.goodness_of_fit(
  60. x,
  61. y,
  62. theta,
  63. bootstrap_samples=25,
  64. bootstrap_seed=123,
  65. )
  66. second = model.goodness_of_fit(
  67. x,
  68. y,
  69. theta,
  70. bootstrap_samples=25,
  71. bootstrap_seed=123,
  72. )
  73. assert "chi2" not in first
  74. assert "p-value(chi2)" not in first
  75. assert first["deviance"] == pytest.approx(2 * model.get_nllf(x, y, theta))
  76. assert first["p-value(deviance_bootstrap)"] == second[
  77. "p-value(deviance_bootstrap)"
  78. ]
  79. assert 0 < first["p-value(deviance_bootstrap)"] <= 1
  80. assert first["deviance_bootstrap_samples"] == 25
  81. def test_covariance_methods_match_sandwich_formulas(regression_data):
  82. x, y, theta = regression_data
  83. ridge = 0.5
  84. model = LogisticPolyRegression(degree=1, lam=(0.0, ridge))
  85. design = np.column_stack([np.ones_like(x), x])
  86. probabilities = model.model(x, theta)
  87. weights = probabilities * (1 - probabilities)
  88. information = (design.T * weights) @ design
  89. bread = information + 2 * ridge * np.eye(2)
  90. bread_inv = np.linalg.pinv(bread, hermitian=True)
  91. scores = (y - probabilities)[:, None] * design
  92. robust_meat = scores.T @ scores
  93. expected_model = bread_inv @ information @ bread_inv
  94. expected_robust = bread_inv @ robust_meat @ bread_inv
  95. np.testing.assert_allclose(
  96. model.get_cov(x, y, theta),
  97. expected_model,
  98. )
  99. np.testing.assert_allclose(
  100. model.get_cov(x, y, theta, method="robust_sandwich"),
  101. expected_robust,
  102. )
  103. np.testing.assert_allclose(
  104. model.get_cov(x, y, theta, method="inverse_hessian"),
  105. bread_inv,
  106. )
  107. def test_covariance_rejects_unknown_method_and_l1_penalty(regression_data):
  108. x, y, theta = regression_data
  109. model = LogisticPolyRegression(degree=1)
  110. with pytest.raises(ValueError, match="Unknown covariance method"):
  111. model.get_cov(x, y, theta, method="not-a-method")
  112. l1_model = LogisticPolyRegression(degree=1, lam=(0.1, 0.0))
  113. with pytest.raises(ValueError, match="nonzero L1"):
  114. l1_model.get_cov(x, y, theta)