Skip to content

Commit d4da2f5

Browse files
committed
Harden Great Tables renderer
1 parent b61f9e8 commit d4da2f5

1 file changed

Lines changed: 40 additions & 30 deletions

File tree

‎src/rtichoke/performance_table_great_tables.py‎

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ def render_performance_table_great_tables(
2222
) -> GT:
2323
"""Render prepared performance data as a Great Tables table.
2424
25-
This renderer is intended for Marimo and other HTML notebook/document
26-
contexts. Reactable remains available when expandable row details are wanted.
25+
This renderer is intended for Marimo and HTML/Quarto contexts. Reactable
26+
remains available when expandable confusion-matrix row details are wanted.
2727
"""
2828
if performance_data.is_empty():
2929
raise ValueError("performance_data must contain at least one row")
@@ -33,11 +33,11 @@ def render_performance_table_great_tables(
3333
raise ValueError("performance_data must contain exactly one stratification")
3434
stratified_by = stratifications[0]
3535

36-
columns = [
36+
source_columns = [
3737
"reference_group", "chosen_cutoff", "sensitivity", "specificity", "ppv",
3838
"npv", "lift", "predicted_positives", "net_benefit", "ppcr",
3939
]
40-
data = performance_data.select([c for c in columns if c in performance_data.columns])
40+
data = performance_data.select([c for c in source_columns if c in performance_data.columns])
4141
if "reference_group" in data.columns:
4242
data = data.rename({"reference_group": "Model"})
4343

@@ -50,40 +50,50 @@ def render_performance_table_great_tables(
5050
if sort_columns:
5151
data = data.sort(sort_columns)
5252

53-
# Match the R table's combined predicted-positive display.
53+
# Match the R table's count + percentage display for predicted positives.
54+
ppcr_percent = (pl.col("ppcr") * 100).round(2).cast(pl.String)
5455
data = data.with_columns(
55-
pl.format(
56-
"{} ({:.2f}%)",
57-
pl.col("predicted_positives"),
58-
pl.col("ppcr") * 100,
56+
pl.concat_str(
57+
[
58+
pl.col("predicted_positives").cast(pl.String),
59+
pl.lit(" ("),
60+
ppcr_percent,
61+
pl.lit("%)"),
62+
]
5963
).alias("Predicted Positives")
6064
).drop(["predicted_positives", "ppcr"])
6165

6266
if stratified_by != "probability_threshold" and "net_benefit" in data.columns:
6367
data = data.drop("net_benefit")
6468

65-
metric_columns = [c for c in ["sensitivity", "specificity", "ppv", "npv", "lift", "net_benefit"] if c in data.columns]
66-
67-
table = (
68-
GT(data)
69-
.cols_label(
70-
Model="Model",
71-
Threshold="Probability Threshold",
72-
sensitivity="Sens",
73-
specificity="Spec",
74-
ppv="PPV",
75-
npv="NPV",
76-
lift="Lift",
77-
net_benefit="Net Benefit",
78-
)
79-
.tab_spanner(label="Performance Metrics", columns=metric_columns)
80-
.fmt_number(columns=[c for c in metric_columns if c != "lift"], decimals=2)
81-
.fmt_number(columns=[c for c in ["lift"] if c in data.columns], decimals=2)
69+
metric_columns = [
70+
c for c in ["sensitivity", "specificity", "ppv", "npv", "lift", "net_benefit"]
71+
if c in data.columns
72+
]
73+
labels = {
74+
"Model": "Model",
75+
"Threshold": "Probability Threshold",
76+
"sensitivity": "Sens",
77+
"specificity": "Spec",
78+
"ppv": "PPV",
79+
"npv": "NPV",
80+
"lift": "Lift",
81+
"net_benefit": "Net Benefit",
82+
"Predicted Positives": "Predicted Positives",
83+
}
84+
labels = {column: label for column, label in labels.items() if column in data.columns}
85+
86+
table = GT(data).cols_label(cases=labels)
87+
if metric_columns:
88+
table = table.tab_spanner(label="Performance Metrics", columns=metric_columns)
89+
table = table.fmt_number(columns=metric_columns, decimals=2)
90+
91+
# Keep model identification visually consistent with rtichoke's palette.
92+
models = (
93+
data.get_column("Model").unique(maintain_order=True).to_list()
94+
if "Model" in data.columns
95+
else []
8296
)
83-
84-
# Great Tables is presentation-first, so use gentle cell fills rather than
85-
# recreating Reactable's JS data bars. Keep model colors consistent with R.
86-
models = data.get_column("Model").unique(maintain_order=True).to_list() if "Model" in data.columns else []
8797
for index, model in enumerate(models):
8898
table = table.tab_style(
8999
style=[

0 commit comments

Comments
 (0)