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
31 changes: 16 additions & 15 deletions src/rtichoke/processing/combinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ def create_strata_combinations(stratified_by: str, by: float, breaks) -> pl.Data
fmt = f"{{:.{decimals}f}}"

if stratified_by == "probability_threshold":
upper_bound = breaks[1:] # breaks
lower_bound = breaks[:-1] # np.roll(upper_bound, 1)
# lower_bound[0] = 0.0
cutoff_values = np.asarray(breaks)
bin_edges = (
cutoff_values
if cutoff_values[-1] == 1.0
else np.append(cutoff_values, 1.0)
)
upper_bound = bin_edges[1:]
lower_bound = bin_edges[:-1]
mid_point = upper_bound - by / 2
include_lower_bound = lower_bound > -0.1
include_upper_bound = upper_bound == 1.0 # upper_bound != 0.0
# chosen_cutoff = upper_bound
include_upper_bound = upper_bound == 1.0
strata = format_strata_column(
lower_bound=lower_bound,
upper_bound=upper_bound,
Expand All @@ -33,13 +37,13 @@ def create_strata_combinations(stratified_by: str, by: float, breaks) -> pl.Data
)

elif stratified_by == "ppcr":
cutoff_values = np.asarray(breaks)
strata_mid = breaks[1:]
lower_bound = strata_mid - by / 2
upper_bound = strata_mid + by / 2
mid_point = breaks[1:]
include_lower_bound = np.ones_like(strata_mid, dtype=bool)
include_upper_bound = np.zeros_like(strata_mid, dtype=bool)
# chosen_cutoff = strata_mid
strata = np.array([fmt.format(x) for x in strata_mid], dtype=object)
else:
raise ValueError(f"Unsupported stratified_by: {stratified_by}")
Expand All @@ -52,12 +56,11 @@ def create_strata_combinations(stratified_by: str, by: float, breaks) -> pl.Data
"mid_point": mid_point,
"include_lower_bound": include_lower_bound,
"include_upper_bound": include_upper_bound,
# "chosen_cutoff": chosen_cutoff,
"stratified_by": [stratified_by] * len(strata),
}
)

cutoffs_df = pl.DataFrame({"chosen_cutoff": breaks})
cutoffs_df = pl.DataFrame({"chosen_cutoff": cutoff_values})

return bins_df.join(cutoffs_df, how="cross")

Expand Down Expand Up @@ -92,9 +95,11 @@ def create_breaks_values(probs_vec, stratified_by, by):
if stratified_by != "probability_threshold":
breaks = np.quantile(probs_vec, np.linspace(1, 0, int(1 / by) + 1))
else:
breaks = np.round(
np.arange(0, 1 + by, by), decimals=len(str(by).split(".")[-1])
)
decimals = len(str(by).split(".")[-1])
n_steps = int(np.floor((1.0 / by) + 1e-12))
breaks = np.round(np.arange(n_steps + 1) * by, decimals=decimals)
if probs_vec is not None and breaks[-1] != 1.0:
breaks = np.append(breaks, 1.0)
return breaks


Expand Down Expand Up @@ -124,7 +129,6 @@ def _create_aj_data_combinations_binary(
]
)

# Define values for Cartesian product
reals_labels = ["real_negatives", "real_positives"]

combinations_frames: list[pl.DataFrame] = [
Expand Down Expand Up @@ -152,8 +156,6 @@ def create_aj_data_combinations(
dfs = [create_strata_combinations(sb, by, breaks) for sb in stratified_by]
strata_combinations = pl.concat(dfs, how="vertical")

# strata_enum = pl.Enum(strata_combinations["strata"])

strata_cats = (
strata_combinations.select(pl.col("strata").unique(maintain_order=True))
.to_series()
Expand All @@ -178,7 +180,6 @@ def create_aj_data_combinations(
}
)

# Define values for Cartesian product
reals_labels = [
"real_negatives",
"real_positives",
Expand Down
59 changes: 59 additions & 0 deletions tests/test_cutoff_grid_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import numpy as np
import pytest

from rtichoke import prepare_performance_data, prepare_performance_data_times
from rtichoke.processing.combinations import create_breaks_values


@pytest.mark.parametrize(
("by", "expected"),
[
(0.3, [0.0, 0.3, 0.6, 0.9]),
(0.4, [0.0, 0.4, 0.8]),
(0.25, [0.0, 0.25, 0.5, 0.75, 1.0]),
],
)
def test_probability_threshold_breaks_match_r_seq_semantics(by, expected):
breaks = create_breaks_values(None, "probability_threshold", by)

assert breaks.tolist() == expected
assert np.max(breaks) <= 1.0


@pytest.mark.parametrize(
("by", "expected_max"),
[(0.3, 0.9), (0.4, 0.8), (0.25, 1.0)],
)
def test_binary_performance_data_matches_r_cutoff_endpoint(by, expected_max):
probs = {"model": np.array([0.05, 0.25, 0.45, 0.65, 0.85, 0.95])}
reals = np.array([0, 0, 1, 0, 1, 1])

result = prepare_performance_data(probs, reals, by=by)
cutoffs = result.get_column("chosen_cutoff")

assert cutoffs.min() == 0.0
assert cutoffs.max() == expected_max
assert cutoffs.max() <= 1.0


@pytest.mark.parametrize(
("by", "expected_max"),
[(0.3, 0.9), (0.4, 0.8), (0.25, 1.0)],
)
def test_time_performance_data_matches_r_cutoff_endpoint(by, expected_max):
probs = {"model": np.array([0.05, 0.25, 0.45, 0.65, 0.85, 0.95])}
reals = np.array([0, 0, 1, 0, 1, 1])
times = np.array([2.0, 4.0, 3.0, 8.0, 5.0, 6.0])

result = prepare_performance_data_times(
probs,
reals,
times,
fixed_time_horizons=[6.0],
by=by,
)
cutoffs = result.get_column("chosen_cutoff")

assert cutoffs.min() == 0.0
assert cutoffs.max() == expected_max
assert cutoffs.max() <= 1.0
Loading