|
| 1 | +import numpy as np |
| 2 | +import polars as pl |
| 3 | +import pytest |
| 4 | + |
| 5 | +from rtichoke import ( |
| 6 | + create_decision_curve, |
| 7 | + plot_decision_curve, |
| 8 | + prepare_performance_data, |
| 9 | +) |
| 10 | +from rtichoke.processing.plotly_helper_functions import _create_reference_lines_data |
| 11 | + |
| 12 | + |
| 13 | +PROBS = {"model": np.array([0.9, 0.8, 0.7, 0.6, 0.4, 0.3, 0.2, 0.1])} |
| 14 | +REALS = np.array([1, 0, 1, 0, 1, 0, 0, 1]) |
| 15 | +THRESHOLDS = [0.25, 0.5, 0.75] |
| 16 | +EXPECTED_IA = [-25.0, 0.0, 25.0] |
| 17 | +OLD_BUGGY_IA = [12.125, 24.75, 37.375] |
| 18 | +EXPECTED_TN = [1.0, 2.0, 3.0] |
| 19 | +EXPECTED_FN = [1.0, 2.0, 3.0] |
| 20 | + |
| 21 | + |
| 22 | +def _performance_rows() -> pl.DataFrame: |
| 23 | + data = prepare_performance_data(probs=PROBS, reals=REALS, by=0.25) |
| 24 | + data = data.filter(pl.col("chosen_cutoff").is_in(THRESHOLDS)) |
| 25 | + return data.sort("chosen_cutoff") |
| 26 | + |
| 27 | + |
| 28 | +def test_static_interventions_avoided_matches_r_definition_per_100(): |
| 29 | + """Expected values characterize the current R static IA definition.""" |
| 30 | + rows = _performance_rows() |
| 31 | + |
| 32 | + np.testing.assert_allclose(rows["true_negatives"].to_numpy(), EXPECTED_TN) |
| 33 | + np.testing.assert_allclose(rows["false_negatives"].to_numpy(), EXPECTED_FN) |
| 34 | + actual = rows["net_benefit_interventions_avoided"].to_numpy() |
| 35 | + np.testing.assert_allclose(actual, EXPECTED_IA) |
| 36 | + assert not np.allclose(actual, OLD_BUGGY_IA) |
| 37 | + |
| 38 | + |
| 39 | +def test_static_interventions_avoided_count_and_nb_forms_are_equivalent(): |
| 40 | + rows = _performance_rows() |
| 41 | + prevalence = float(REALS.mean()) |
| 42 | + |
| 43 | + for row in rows.iter_rows(named=True): |
| 44 | + threshold = float(row["chosen_cutoff"]) |
| 45 | + tn = float(row["true_negatives"]) |
| 46 | + fn = float(row["false_negatives"]) |
| 47 | + n = float(row["n"]) |
| 48 | + net_benefit = float(row["net_benefit"]) |
| 49 | + net_benefit_all = prevalence - (1 - prevalence) * threshold / (1 - threshold) |
| 50 | + from_counts = 100 * (tn / n - fn / n * (1 - threshold) / threshold) |
| 51 | + from_nb = 100 * (net_benefit - net_benefit_all) * (1 - threshold) / threshold |
| 52 | + actual = float(row["net_benefit_interventions_avoided"]) |
| 53 | + |
| 54 | + assert actual == pytest.approx(from_counts) |
| 55 | + assert from_counts == pytest.approx(from_nb) |
| 56 | + |
| 57 | + |
| 58 | +def test_interventions_avoided_model_and_references_use_per_100_units(): |
| 59 | + rows = _performance_rows() |
| 60 | + aj = pl.DataFrame({"reference_group": ["population"], "aj_estimate": [0.5]}) |
| 61 | + refs = _create_reference_lines_data( |
| 62 | + curve="interventions avoided", |
| 63 | + aj_estimates_from_performance_data=aj, |
| 64 | + multiple_populations=False, |
| 65 | + min_p_threshold=0.25, |
| 66 | + max_p_threshold=0.75, |
| 67 | + ) |
| 68 | + |
| 69 | + treat_all = refs.filter(pl.col("reference_group") == "treat_all") |
| 70 | + assert np.allclose(treat_all["y"].to_numpy(), 0.0) |
| 71 | + |
| 72 | + is_treat_none = pl.col("reference_group") == "treat_none" |
| 73 | + is_test_threshold = pl.col("x").is_in(THRESHOLDS) |
| 74 | + treat_none = refs.filter(is_treat_none & is_test_threshold).sort("x") |
| 75 | + expected_treat_none = [-100.0, 0.0, 100.0 / 3.0] |
| 76 | + np.testing.assert_allclose(treat_none["y"].to_numpy(), expected_treat_none) |
| 77 | + np.testing.assert_allclose( |
| 78 | + rows["net_benefit_interventions_avoided"].to_numpy(), EXPECTED_IA |
| 79 | + ) |
| 80 | + |
| 81 | + |
| 82 | +def test_static_interventions_avoided_public_apis_are_unchanged(): |
| 83 | + data = prepare_performance_data(probs=PROBS, reals=REALS, by=0.25) |
| 84 | + created = create_decision_curve( |
| 85 | + probs=PROBS, |
| 86 | + reals=REALS, |
| 87 | + decision_type="interventions avoided", |
| 88 | + by=0.25, |
| 89 | + min_p_threshold=0.25, |
| 90 | + max_p_threshold=0.75, |
| 91 | + ) |
| 92 | + plotted = plot_decision_curve( |
| 93 | + data, |
| 94 | + decision_type="interventions avoided", |
| 95 | + min_p_threshold=0.25, |
| 96 | + max_p_threshold=0.75, |
| 97 | + ) |
| 98 | + |
| 99 | + assert created.__class__.__name__ == "Figure" |
| 100 | + assert plotted.__class__.__name__ == "Figure" |
0 commit comments