From 8c74e36df2c13ca4f33b3539a6652d6ba6d08e5e Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Sun, 23 Aug 2026 15:31:11 +0300 Subject: [PATCH 1/4] Fix canonical Lift v2 y-axis domains --- src/rtichoke/_viz_spec_v2.py | 37 ++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/rtichoke/_viz_spec_v2.py b/src/rtichoke/_viz_spec_v2.py index 70992627..d3db469d 100644 --- a/src/rtichoke/_viz_spec_v2.py +++ b/src/rtichoke/_viz_spec_v2.py @@ -102,9 +102,12 @@ def _lift_v2_spec_from_performance_data( references: list[dict[str, object]] = [ {"type": "horizontal", "value": 1.0, "scope": "global", "label": "Random"} ] + perfect_heights: list[float] = [] for population in populations: p = prevalence[population] if p > 0: + perfect_height = 1.0 / p + perfect_heights.append(perfect_height) references.append( { "type": "path", @@ -112,12 +115,16 @@ def _lift_v2_spec_from_performance_data( "population": population, "label": "Perfect Model", "points": [ - {"x": 0.0, "y": 1.0 / p}, - {"x": p, "y": 1.0 / p}, + {"x": 0.0, "y": perfect_height}, + {"x": p, "y": perfect_height}, {"x": 1.0, "y": 1.0}, ], } ) + spec["yAxis"] = { + "label": "Lift", + "domain": [0, _lift_y_axis_upper_bound(performance_data, perfect_heights)], + } spec["references"] = references return spec @@ -356,8 +363,11 @@ def _lift_times_v2_spec_from_performance_data( references: list[dict[str, object]] = [ {"type": "horizontal", "value": 1.0, "scope": "global", "label": "Random"} ] + perfect_heights: list[float] = [] for (population, horizon), risk in risks.items(): if risk > 0: + perfect_height = 1.0 / risk + perfect_heights.append(perfect_height) references.append( { "type": "path", @@ -366,8 +376,8 @@ def _lift_times_v2_spec_from_performance_data( "horizon": horizon, "label": "Perfect Model", "points": [ - {"x": 0.0, "y": 1.0 / risk}, - {"x": risk, "y": 1.0 / risk}, + {"x": 0.0, "y": perfect_height}, + {"x": risk, "y": perfect_height}, {"x": 1.0, "y": 1.0}, ], } @@ -382,7 +392,10 @@ def _lift_times_v2_spec_from_performance_data( "x": "ppcr", "y": "lift", "xAxis": {"label": "Predicted Positives (Rate)", "domain": [0, 1]}, - "yAxis": {"label": "Lift", "domain": [0, None]}, + "yAxis": { + "label": "Lift", + "domain": [0, _lift_y_axis_upper_bound(performance_data, perfect_heights)], + }, "references": references, } @@ -473,6 +486,15 @@ def _gains_population_prevalence( return prevalence +def _lift_y_axis_upper_bound( + performance_data: pl.DataFrame, + perfect_heights: list[float], +) -> float: + """Return the finite numeric Lift bound implied by existing plot quantities.""" + observed_lift = float(performance_data["lift"].max()) + return max(1.0, observed_lift, *perfect_heights) + + def _curve_v2_spec_from_performance_data( performance_data: pl.DataFrame, evaluation_metadata: Mapping[str, _EvaluationMetadata], @@ -609,6 +631,9 @@ def _curve_v2_spec_from_performance_data( "x": "ppcr", "y": "lift", "xAxis": {"label": "Predicted Positives (Rate)", "domain": [0, 1]}, - "yAxis": {"label": "Lift", "domain": [0, None]}, + "yAxis": { + "label": "Lift", + "domain": [0, _lift_y_axis_upper_bound(performance_data, [])], + }, "references": [], } From d078bd21ac0d5c6bf178e57efa8d99cc3a50378b Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Sun, 23 Aug 2026 15:31:33 +0300 Subject: [PATCH 2/4] Test static Lift v2 numeric y-axis domain --- tests/test_lift_v2.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_lift_v2.py b/tests/test_lift_v2.py index e41c2bc5..7e242bed 100644 --- a/tests/test_lift_v2.py +++ b/tests/test_lift_v2.py @@ -61,6 +61,31 @@ def test_static_lift_v2_spec_single_model(): ] +def test_static_lift_v2_y_axis_domain_is_numeric_and_covers_plot(): + probs = { + "Population A": np.array([0.1, 0.2, 0.7, 0.9]), + "Population B": np.array([0.1, 0.3, 0.4, 0.8]), + } + reals = { + "Population A": np.array([0, 0, 1, 1]), + "Population B": np.array([0, 0, 0, 1]), + } + spec = _spec(probs, reals) + + upper = spec["yAxis"]["domain"][1] + observed_upper = max(float(row["lift"]) for row in spec["data"]) + perfect_upper = max( + float(point["y"]) + for reference in spec["references"] + if reference["type"] == "path" + for point in reference["points"] + ) + + assert isinstance(upper, (int, float)) + assert np.isfinite(upper) + assert spec["yAxis"]["domain"] == [0, max(1.0, observed_upper, perfect_upper)] + + def test_static_lift_v2_spec_shared_population(): probs, reals = _shared_model_inputs() spec = _spec(probs, reals) From 24d5fc7d530b99e9fa76d04d0839b1133f543ae0 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Sun, 23 Aug 2026 15:31:51 +0300 Subject: [PATCH 3/4] Test time-dependent Lift v2 numeric y-axis domain --- tests/test_time_lift_v2.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_time_lift_v2.py b/tests/test_time_lift_v2.py index cd31fd60..c7335390 100644 --- a/tests/test_time_lift_v2.py +++ b/tests/test_time_lift_v2.py @@ -86,6 +86,24 @@ def test_time_lift_uses_one_evaluation_and_series_per_model_horizon(): } +def test_time_lift_v2_y_axis_domain_is_numeric_and_covers_plot(): + probs, reals, times = _shared_inputs() + spec = _spec(probs, reals, times) + + upper = spec["yAxis"]["domain"][1] + observed_upper = max(float(row["lift"]) for row in spec["data"]) + perfect_upper = max( + float(point["y"]) + for reference in spec["references"] + if reference["type"] == "path" + for point in reference["points"] + ) + + assert isinstance(upper, (int, float)) + assert np.isfinite(upper) + assert spec["yAxis"]["domain"] == [0, max(1.0, observed_upper, perfect_upper)] + + def test_equal_risk_population_horizons_remain_distinct_lift_reference_owners(): probs = { "Population A": np.array([0.05, 0.2, 0.7, 0.95]), From b1b096900a0c63fd3d57f8945685f26ecbc4289f Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Sun, 23 Aug 2026 15:47:53 +0300 Subject: [PATCH 4/4] Fix Lift v2 domain type narrowing --- src/rtichoke/_viz_spec_v2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rtichoke/_viz_spec_v2.py b/src/rtichoke/_viz_spec_v2.py index d3db469d..8aacf154 100644 --- a/src/rtichoke/_viz_spec_v2.py +++ b/src/rtichoke/_viz_spec_v2.py @@ -491,8 +491,9 @@ def _lift_y_axis_upper_bound( perfect_heights: list[float], ) -> float: """Return the finite numeric Lift bound implied by existing plot quantities.""" - observed_lift = float(performance_data["lift"].max()) - return max(1.0, observed_lift, *perfect_heights) + observed_lift = performance_data["lift"].max() + assert isinstance(observed_lift, (int, float)) + return max(1.0, float(observed_lift), *perfect_heights) def _curve_v2_spec_from_performance_data(