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

from .calibration import create_calibration_curve, create_calibration_curve_times
from . import calibration as _calibration
from ._interactive_aspect import enforce_square_calibration_panel

_original_create_calibration_curve = _calibration.create_calibration_curve
_original_create_calibration_curve_times = _calibration.create_calibration_curve_times


def create_calibration_curve(*args, **kwargs):
"""Create an interactive calibration plot with a square main panel."""
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."""
return enforce_square_calibration_panel(
_original_create_calibration_curve_times(*args, **kwargs)
)


# Keep direct imports from rtichoke.calibration.calibration aligned with the
# public package entry points.
_calibration.create_calibration_curve = create_calibration_curve
_calibration.create_calibration_curve_times = create_calibration_curve_times

__all__ = ["create_calibration_curve", "create_calibration_curve_times"]
27 changes: 27 additions & 0 deletions src/rtichoke/calibration/_interactive_aspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Shared layout constraints for interactive calibration plots."""

from typing import Any

from plotly.graph_objs._figure import Figure


def enforce_square_calibration_panel(fig: Figure) -> Figure:
"""Keep the upper calibration panel on a 1:1 predicted/observed scale.

Calibration figures include a histogram in a separate lower subplot. The
aspect-ratio constraint therefore belongs only to the upper calibration
panel, not to the full Plotly widget or to the histogram.
"""
fig.update_yaxes(
scaleanchor="x",
scaleratio=1,
constrain="domain",
row=1,
col=1,
)
return fig


def shared_calibration_axis_layout(axis_range: list[float]) -> dict[str, Any]:
"""Return the common zoom settings used by calibration x and y axes."""
return {"range": axis_range, "fixedrange": False}
45 changes: 45 additions & 0 deletions tests/test_calibration_interactive_aspect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import numpy as np

from rtichoke.calibration import create_calibration_curve, create_calibration_curve_times


def _assert_square_main_panel(fig):
assert fig.layout.yaxis.scaleanchor == "x"
assert fig.layout.yaxis.scaleratio == 1
assert fig.layout.yaxis.constrain == "domain"
# The histogram has its own y axis and must remain unconstrained.
assert fig.layout.yaxis2.scaleanchor is None


def test_interactive_calibration_main_panel_is_square():
probs = {"model": np.linspace(0.05, 0.95, 20)}
reals = np.array([0, 1] * 10)

for calibration_type in ("discrete", "smooth"):
fig = create_calibration_curve(
probs, reals, calibration_type=calibration_type
)
_assert_square_main_panel(fig)
assert list(fig.layout.xaxis.range) == list(fig.layout.yaxis.range)


def test_interactive_calibration_times_main_panel_is_square():
probs = {"model": np.linspace(0.05, 0.95, 20)}
reals = np.array([0, 1] * 10)
times = np.arange(1.0, 21.0)

fig = create_calibration_curve_times(
probs,
reals,
times,
fixed_time_horizons=[10.0, 15.0],
heuristics_sets=[
{
"censoring_heuristic": "excluded",
"competing_heuristic": "excluded",
}
],
)

_assert_square_main_panel(fig)
assert list(fig.layout.xaxis.range) == list(fig.layout.yaxis.range)
Loading