diff --git a/src/rtichoke/calibration/__init__.py b/src/rtichoke/calibration/__init__.py index b04d51eb..95d013ce 100644 --- a/src/rtichoke/calibration/__init__.py +++ b/src/rtichoke/calibration/__init__.py @@ -16,8 +16,25 @@ def create_calibration_curve(*args, **kwargs): ) +def _extract_heuristics_sets(args, kwargs): + if "heuristics_sets" in kwargs: + return kwargs["heuristics_sets"] + if len(args) > 4: + return args[4] + return None + + def create_calibration_curve_times(*args, **kwargs): """Create an interactive time-dependent calibration plot with a square main panel.""" + heuristics_sets = _extract_heuristics_sets(args, kwargs) + if heuristics_sets is not None and len(heuristics_sets) != 1: + raise ValueError( + "create_calibration_curve_times() currently supports exactly one " + "heuristics set. Multiple heuristic sets would be combined in the " + "same plotted calibration curve. Call the function separately for " + "each heuristic set." + ) + return enforce_square_calibration_panel( _original_create_calibration_curve_times(*args, **kwargs) ) diff --git a/tests/test_calibration_times_heuristics_validation.py b/tests/test_calibration_times_heuristics_validation.py new file mode 100644 index 00000000..69b0eff0 --- /dev/null +++ b/tests/test_calibration_times_heuristics_validation.py @@ -0,0 +1,63 @@ +import numpy as np +import pytest + +from rtichoke import create_calibration_curve_times as create_calibration_curve_times_top_level +from rtichoke.calibration import create_calibration_curve_times +from rtichoke.calibration.calibration import ( + create_calibration_curve_times as create_calibration_curve_times_direct, +) + + +@pytest.mark.parametrize( + "entry_point", + [ + create_calibration_curve_times_top_level, + create_calibration_curve_times, + create_calibration_curve_times_direct, + ], +) +def test_create_calibration_curve_times_rejects_multiple_heuristic_sets(entry_point): + probs = {"model_1": np.array([0.1, 0.2, 0.3, 0.4])} + reals = np.array([0, 1, 0, 1]) + times = np.array([1.0, 2.0, 3.0, 4.0]) + + heuristics_sets = [ + { + "censoring_heuristic": "adjusted", + "competing_heuristic": "adjusted_as_negative", + }, + { + "censoring_heuristic": "excluded", + "competing_heuristic": "excluded", + }, + ] + + with pytest.raises(ValueError, match="exactly one heuristics set"): + entry_point( + probs, + reals, + times, + fixed_time_horizons=[2.0], + heuristics_sets=heuristics_sets, + ) + + +def test_create_calibration_curve_times_allows_one_heuristic_set(): + probs = {"model_1": np.array([0.1, 0.2, 0.3, 0.4])} + reals = np.array([0, 1, 0, 1]) + times = np.array([1.0, 2.0, 3.0, 4.0]) + + fig = create_calibration_curve_times( + probs, + reals, + times, + fixed_time_horizons=[2.0], + heuristics_sets=[ + { + "censoring_heuristic": "adjusted", + "competing_heuristic": "adjusted_as_negative", + } + ], + ) + + assert fig is not None diff --git a/user_guide/02-curve-api-compatibility.qmd b/user_guide/02-curve-api-compatibility.qmd index e603b3fd..d2eb52dd 100644 --- a/user_guide/02-curve-api-compatibility.qmd +++ b/user_guide/02-curve-api-compatibility.qmd @@ -48,10 +48,11 @@ These statements describe the effect of the heuristics on the estimates. Functio ## Time-dependent calibration heuristics -`create_calibration_curve_times()` differs from its ROC, precision-recall, Gains, Lift, and decision-curve siblings in two important ways: +`create_calibration_curve_times()` differs from its ROC, precision-recall, Gains, Lift, and decision-curve siblings in three important ways: 1. `heuristics_sets` is currently required rather than defaulted. -2. Calibration explicitly rejects unsupported heuristic combinations (specifically `competing_heuristic="adjusted_as_censored"`) with an `Unsupported calibration heuristics` error instead of silently skipping requested horizons. +2. Calibration currently supports exactly one heuristic set per call. Use separate calls when comparing heuristic choices so distinct calibration estimates are never combined into one plotted curve. +3. Calibration explicitly rejects unsupported heuristic combinations (specifically `competing_heuristic="adjusted_as_censored"`) with an `Unsupported calibration heuristics` error instead of silently skipping requested horizons. Pass the calibration heuristic explicitly. For adjusted or exclusion-based paths: