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
4 changes: 2 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ jobs:
prefix = "rtichoke/_vendor/rtichoke_viz/"
required = {
f"{prefix}VENDORED_FROM",
f"{prefix}rtichoke-viz-0.9.0.tar.gz",
f"{prefix}rtichoke-viz-0.10.0.tar.gz",
f"{prefix}rtichoke-viz.js",
f"{prefix}rtichoke-viz.css",
f"{prefix}rtichoke-viz.schema.json",
f"{prefix}rtichoke-viz-v2.schema.json",
}
assert required <= names
assert f"{prefix}rtichoke-viz-0.7.0.tar.gz" not in names
assert f"{prefix}rtichoke-viz-0.9.0.tar.gz" not in names
PY

- name: Run tests
Expand Down
201 changes: 201 additions & 0 deletions src/rtichoke/_interventions_avoided_viz_spec_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
"n",
}

_REQUIRED_TIMES_COLUMNS = _REQUIRED_COLUMNS | {
"fixed_time_horizon",
"censoring_heuristic",
"competing_heuristic",
}


def _interventions_avoided_v2_spec_from_performance_data(
performance_data: pl.DataFrame,
Expand Down Expand Up @@ -177,3 +183,198 @@ def _interventions_avoided_v2_spec_from_performance_data(
"yAxis": {"label": "Interventions Avoided (per 100)"},
"references": references,
}


def _interventions_avoided_times_v2_spec_from_performance_data(
performance_data: pl.DataFrame,
evaluation_metadata: Mapping[str, _EvaluationMetadata],
*,
min_p_threshold: float = 0.0,
max_p_threshold: float = 1.0,
) -> dict[str, object]:
"""Build canonical time-dependent Interventions Avoided v2.

Model values are copied from the existing production calculation. Only
Treat None reference geometry is derived from the existing AJ event risk.
"""
missing = _REQUIRED_TIMES_COLUMNS.difference(performance_data.columns)
if missing:
raise ValueError(
"Time-dependent Interventions Avoided performance data is missing columns: "
+ ", ".join(sorted(missing))
)

rows = (
performance_data.filter(
pl.col("chosen_cutoff").is_finite()
& pl.col("net_benefit_interventions_avoided").is_finite()
)
.select(
"reference_group",
"fixed_time_horizon",
"censoring_heuristic",
"competing_heuristic",
"chosen_cutoff",
"net_benefit_interventions_avoided",
"real_positives",
"n",
)
.to_dicts()
)
row_groups = {str(row["reference_group"]) for row in rows}
missing_metadata = row_groups.difference(evaluation_metadata)
if missing_metadata:
raise ValueError(
"Time-dependent Interventions Avoided rows are missing evaluation metadata: "
+ ", ".join(sorted(missing_metadata))
)

ordered_groups = [group for group in evaluation_metadata if group in row_groups]
evaluation_ids = {
group: f"evaluation-{index}"
for index, group in enumerate(ordered_groups, start=1)
}
evaluations: list[dict[str, object]] = []
for group in ordered_groups:
metadata = evaluation_metadata[group]
evaluation: dict[str, object] = {
"id": evaluation_ids[group],
"population": metadata.population,
}
if metadata.model is not None:
evaluation["model"] = metadata.model
evaluations.append(evaluation)

series_keys = list(
dict.fromkeys(
(str(row["reference_group"]), float(row["fixed_time_horizon"]))
for row in rows
)
)
series_ids = {
key: f"series-{index}" for index, key in enumerate(series_keys, start=1)
}
series: list[dict[str, object]] = []
for group, horizon in series_keys:
metadata = evaluation_metadata[group]
display_value = metadata.model or metadata.population
series.append(
{
"id": series_ids[(group, horizon)],
"evaluationId": evaluation_ids[group],
"horizon": horizon,
"display": {
"label": display_value,
"group": display_value,
"role": "model" if metadata.model is not None else "population",
},
}
)

data = [
{
"seriesId": series_ids[
(str(row["reference_group"]), float(row["fixed_time_horizon"]))
],
"threshold": float(row["chosen_cutoff"]),
"interventionsAvoided": float(row["net_benefit_interventions_avoided"]),
}
for row in rows
]

risk_rows = (
performance_data.filter(pl.col("chosen_cutoff") == 0)
.select(
"reference_group",
"fixed_time_horizon",
(pl.col("real_positives") / pl.col("n")).alias("event_risk"),
)
.unique()
.to_dicts()
)
risk_values: dict[tuple[str, float], set[float]] = {}
for row in risk_rows:
group = str(row["reference_group"])
metadata = evaluation_metadata.get(group)
if metadata is None:
continue
key = (metadata.population, float(row["fixed_time_horizon"]))
risk_values.setdefault(key, set()).add(float(row["event_risk"]))

thresholds: dict[tuple[str, float], list[float]] = {}
for row in rows:
group = str(row["reference_group"])
key = (
evaluation_metadata[group].population,
float(row["fixed_time_horizon"]),
)
threshold = float(row["chosen_cutoff"])
if 0.0 < threshold <= 1.0:
thresholds.setdefault(key, []).append(threshold)

references: list[dict[str, object]] = [
{
"type": "horizontal",
"scope": "global",
"value": 0.0,
"label": "Treat All",
"benchmark": "treat_all",
}
]
populations = list(
dict.fromkeys(metadata.population for metadata in evaluation_metadata.values())
)
horizons = sorted(
float(value)
for value in performance_data["fixed_time_horizon"].unique().to_list()
)
for population in populations:
for horizon in horizons:
key = (population, horizon)
candidates = risk_values.get(key, set())
if not candidates:
continue
if len(candidates) != 1:
raise ValueError(
"Time-dependent Interventions Avoided must have one calculated "
f"event risk per population and horizon: {population} at {horizon}"
)
event_risk = next(iter(candidates))
references.append(
{
"type": "path",
"scope": "population_horizon",
"population": population,
"horizon": horizon,
"label": f"Treat None — {population}",
"benchmark": "treat_none",
"points": [
{
"x": threshold,
"y": 100.0
* (
1.0
- event_risk
- event_risk * (1.0 - threshold) / threshold
),
}
for threshold in sorted(set(thresholds.get(key, [])))
],
}
)

return {
"schemaVersion": "2.0",
"type": "interventions_avoided",
"evaluations": evaluations,
"series": series,
"data": data,
"x": "threshold",
"y": "interventionsAvoided",
"xAxis": {
"label": "Probability Threshold",
"domain": [min_p_threshold, max_p_threshold],
},
"yAxis": {"label": "Interventions Avoided (per 100)"},
"references": references,
}
8 changes: 4 additions & 4 deletions src/rtichoke/_vendor/rtichoke_viz/VENDORED_FROM
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repository=https://github.com/uriahf/rtichoke_viz
release=v0.9.0
source_commit=56e2ba95f83c889385c38619571368f74250d428
archive=rtichoke-viz-0.9.0.tar.gz
sha256=6a231c7bc951cdd3f5381e2a0937036a9d9f62b8ea1b36d8dbf81f62c6188ef8
release=v0.10.0
source_commit=b65b903e9456b5eb323dbe0bac823e9ff0c1bd01
archive=rtichoke-viz-0.10.0.tar.gz
sha256=030a9e58c6367147b4904d73cf641d8ef3990d95e70b66022a99d6c8d37b3233
Binary file not shown.
Binary file not shown.
142 changes: 100 additions & 42 deletions src/rtichoke/_vendor/rtichoke_viz/rtichoke-viz-v2.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4345,53 +4345,111 @@
}
},
{
"type": "object",
"required": [
"type",
"points",
"scope",
"population",
"benchmark"
],
"properties": {
"type": {
"const": "path",
"type": "string"
},
"points": {
"minItems": 2,
"type": "array",
"items": {
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
"anyOf": [
{
"type": "object",
"required": [
"type",
"points",
"benchmark",
"scope",
"population"
],
"properties": {
"type": {
"const": "path",
"type": "string"
},
"points": {
"minItems": 2,
"type": "array",
"items": {
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
}
},
"label": {
"type": "string"
},
"benchmark": {
"const": "treat_none",
"type": "string"
},
"scope": {
"const": "population",
"type": "string"
},
"population": {
"type": "string"
}
}
},
"label": {
"type": "string"
},
"scope": {
"const": "population",
"type": "string"
},
"population": {
"type": "string"
},
"benchmark": {
"const": "treat_none",
"type": "string"
{
"type": "object",
"required": [
"type",
"points",
"benchmark",
"scope",
"population",
"horizon"
],
"properties": {
"type": {
"const": "path",
"type": "string"
},
"points": {
"minItems": 2,
"type": "array",
"items": {
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
}
},
"label": {
"type": "string"
},
"benchmark": {
"const": "treat_none",
"type": "string"
},
"scope": {
"const": "population_horizon",
"type": "string"
},
"population": {
"type": "string"
},
"horizon": {
"minimum": 0,
"type": "number"
}
}
}
}
]
}
]
}
Expand Down
Loading
Loading