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
34 changes: 34 additions & 0 deletions src/rtichoke/calibration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Subpackage for Calibration
"""

import numpy as np

from . import calibration as _calibration
from ._interactive_aspect import enforce_square_calibration_panel

Expand All @@ -16,15 +18,47 @@
]


def _argument(args, kwargs, name, position):
return kwargs[name] if name in kwargs else args[position]


def _validate_probability_values(probs):
for values in probs.values():
values = np.asarray(values)
if not np.all(np.isfinite(values)) or np.any((values < 0) | (values > 1)):
raise ValueError("Estimated probabilities must be between 0 and 1.")


def _validate_outcome_values(reals, allowed_values):
values = reals.values() if isinstance(reals, dict) else [reals]
for outcome_values in values:
if not np.all(np.isin(np.asarray(outcome_values), allowed_values)):
if allowed_values == (0, 1):
raise ValueError("Binary outcomes must contain only 0 and 1.")
raise ValueError(
"Time-dependent outcomes must contain only 0, 1, and 2."
)


def create_calibration_curve(*args, **kwargs):
"""Create an interactive calibration plot with a square main panel."""
probs = _argument(args, kwargs, "probs", 0)
reals = _argument(args, kwargs, "reals", 1)
_validate_probability_values(probs)
_validate_outcome_values(reals, (0, 1))

return enforce_square_calibration_panel(
_original_create_calibration_curve(*args, **kwargs)
)


def create_calibration_curve_times(*args, **kwargs):
"""Create an interactive time-dependent calibration plot with a square main panel."""
probs = _argument(args, kwargs, "probs", 0)
reals = _argument(args, kwargs, "reals", 1)
_validate_probability_values(probs)
_validate_outcome_values(reals, (0, 1, 2))

heuristics_sets = kwargs.get("heuristics_sets")
if heuristics_sets is None:
heuristics_sets = [dict(_DEFAULT_TIME_CALIBRATION_HEURISTICS[0])]
Expand Down
82 changes: 82 additions & 0 deletions tests/test_calibration_input_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import numpy as np
import pytest

from rtichoke import (
create_calibration_curve as create_calibration_curve_top_level,
create_calibration_curve_times as create_calibration_curve_times_top_level,
)
from rtichoke.calibration import (
create_calibration_curve,
create_calibration_curve_times,
)
from rtichoke.calibration.calibration import (
create_calibration_curve as create_calibration_curve_direct,
create_calibration_curve_times as create_calibration_curve_times_direct,
)


BINARY_ENTRY_POINTS = [
create_calibration_curve_top_level,
create_calibration_curve,
create_calibration_curve_direct,
]
TIME_ENTRY_POINTS = [
create_calibration_curve_times_top_level,
create_calibration_curve_times,
create_calibration_curve_times_direct,
]


@pytest.mark.parametrize("entry_point", BINARY_ENTRY_POINTS)
def test_binary_calibration_rejects_probabilities_outside_unit_interval(entry_point):
with pytest.raises(ValueError, match="between 0 and 1"):
entry_point(
probs={"model": np.array([0.1, 1.1])},
reals=np.array([0, 1]),
)


@pytest.mark.parametrize("entry_point", BINARY_ENTRY_POINTS)
def test_binary_calibration_rejects_nonbinary_outcomes(entry_point):
with pytest.raises(ValueError, match="only 0 and 1"):
entry_point(
probs={"model": np.array([0.1, 0.9])},
reals=np.array([0, 2]),
)


@pytest.mark.parametrize("entry_point", TIME_ENTRY_POINTS)
def test_time_calibration_rejects_probabilities_outside_unit_interval(entry_point):
with pytest.raises(ValueError, match="between 0 and 1"):
entry_point(
probs={"model": np.array([-0.1, 0.9])},
reals=np.array([0, 1]),
times=np.array([1.0, 2.0]),
fixed_time_horizons=[1.5],
)


@pytest.mark.parametrize("entry_point", TIME_ENTRY_POINTS)
def test_time_calibration_rejects_unsupported_event_codes(entry_point):
with pytest.raises(ValueError, match="only 0, 1, and 2"):
entry_point(
probs={"model": np.array([0.1, 0.9])},
reals=np.array([0, 3]),
times=np.array([1.0, 2.0]),
fixed_time_horizons=[1.5],
)


def test_calibration_validation_preserves_supported_multiple_population_shape():
fig = create_calibration_curve(
probs={
"population_a": np.array([0.1, 0.8]),
"population_b": np.array([0.2, 0.9]),
},
reals={
"population_a": np.array([0, 1]),
"population_b": np.array([0, 1]),
},
)

assert fig is not None
Loading