Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/rtichoke/calibration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
_original_create_calibration_curve = _calibration.create_calibration_curve
_original_create_calibration_curve_times = _calibration.create_calibration_curve_times

_DEFAULT_TIME_CALIBRATION_HEURISTICS = [
{
"censoring_heuristic": "adjusted",
"competing_heuristic": "adjusted_as_negative",
}
]


def create_calibration_curve(*args, **kwargs):
"""Create an interactive calibration plot with a square main panel."""
Expand All @@ -18,6 +25,18 @@ def create_calibration_curve(*args, **kwargs):

def create_calibration_curve_times(*args, **kwargs):
"""Create an interactive time-dependent calibration plot with a square main panel."""
heuristics_sets = kwargs.get("heuristics_sets")
if heuristics_sets is None:
heuristics_sets = [dict(_DEFAULT_TIME_CALIBRATION_HEURISTICS[0])]
kwargs["heuristics_sets"] = heuristics_sets

if len(heuristics_sets) != 1:
raise ValueError(
"create_calibration_curve_times() currently supports exactly one "
"heuristic set. Multiple heuristic sets would be combined in the "
"same calibration trace because the plot has no heuristic selector."
)

return enforce_square_calibration_panel(
_original_create_calibration_curve_times(*args, **kwargs)
)
Expand Down
74 changes: 74 additions & 0 deletions tests/test_calibration_times_heuristics_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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,
)


ENTRY_POINTS = [
create_calibration_curve_times_top_level,
create_calibration_curve_times,
create_calibration_curve_times_direct,
]


def _inputs():
probs = {"model_1": np.array([0.1, 0.2, 0.3, 0.4])}
reals = np.array([0, 1, 1, 1])
times = np.array([1.0, 2.0, 3.0, 4.0])
return probs, reals, times


@pytest.mark.parametrize("entry_point", ENTRY_POINTS)
def test_time_calibration_defaults_to_adjusted_heuristics(entry_point):
probs, reals, times = _inputs()

default_fig = entry_point(
probs,
reals,
times,
fixed_time_horizons=[2.0],
)
explicit_fig = entry_point(
probs,
reals,
times,
fixed_time_horizons=[2.0],
heuristics_sets=[
{
"censoring_heuristic": "adjusted",
"competing_heuristic": "adjusted_as_negative",
}
],
)

np.testing.assert_allclose(default_fig.data[1].x, explicit_fig.data[1].x)
np.testing.assert_allclose(default_fig.data[1].y, explicit_fig.data[1].y)


@pytest.mark.parametrize("entry_point", ENTRY_POINTS)
def test_time_calibration_rejects_multiple_heuristic_sets(entry_point):
probs, reals, times = _inputs()

with pytest.raises(ValueError, match="exactly one heuristic set"):
entry_point(
probs,
reals,
times,
fixed_time_horizons=[2.0],
heuristics_sets=[
{
"censoring_heuristic": "adjusted",
"competing_heuristic": "adjusted_as_negative",
},
{
"censoring_heuristic": "excluded",
"competing_heuristic": "excluded",
},
],
)
12 changes: 4 additions & 8 deletions user_guide/02-curve-api-compatibility.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,7 @@ 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:

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.

Pass the calibration heuristic explicitly. For adjusted or exclusion-based paths:
`create_calibration_curve_times()` defaults to one adjusted heuristic set:

```python
heuristics_sets = [
Expand All @@ -64,20 +59,21 @@ heuristics_sets = [
]
```

You can still provide a different single heuristic set explicitly. Calibration currently accepts exactly one heuristic set per call because the plot has no heuristic selector; passing several sets raises a clear `ValueError` instead of combining them into one calibration trace. Calibration also rejects unsupported combinations such as `competing_heuristic="adjusted_as_censored"`.

When `calibration_type="smooth"`, you can also specify the `smooth_method`:
- `"local_aj"` (default): Gerds' local Aalen-Johansen/KM neighborhood estimation.
- `"secondary_cox"`: Secondary Cox regression method (Austin, Harrell & McLernon 2020).
- `"pseudo_values"`: Leave-one-out Aalen-Johansen pseudo-observations lowess.

Then call:
The default call is therefore simply:

```python
fig = rk.create_calibration_curve_times(
probs=probs,
reals=reals,
times=times,
fixed_time_horizons=[3.0, 6.0, 9.0],
heuristics_sets=heuristics_sets,
)
```

Expand Down
Loading