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
13 changes: 13 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ jobs:
if: github.event.action != 'closed'
run: uv run great-docs build

- name: Export Great Tables performance table demo
if: github.event.action != 'closed'
run: uv run marimo export html --no-include-code examples/performance_table_demo.py -o great-docs/_site/performance-table-demo.html

- name: Export Reactable performance table demo
if: github.event.action != 'closed'
run: |
uv run quarto render examples/performance_table_reactable.qmd --output performance-table-reactable.html
grep -q '.Reactable {' performance-table-reactable.html
grep -q 'Real Positive' performance-table-reactable.html
mv performance-table-reactable.html great-docs/_site/performance-table-reactable.html
cp -R examples/performance_table_reactable_files great-docs/_site/performance_table_reactable_files

- name: Deploy PR preview
uses: rossjrw/pr-preview-action@v1
with:
Expand Down
88 changes: 88 additions & 0 deletions examples/performance_table_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import marimo

__generated_with = "0.18.4"
app = marimo.App(width="full")


@app.cell
def _():
import marimo as mo
import numpy as np
from rtichoke import create_performance_table, create_performance_table_times

return create_performance_table, create_performance_table_times, mo, np


@app.cell
def _(mo):
mo.md(
"""
# rtichoke performance table

PR preview for the Python port of `rtichoke::create_performance_table()`.
This Marimo preview uses the **Great Tables** renderer. The same public
API also supports `renderer="reactable"` for Quarto/Jupyter contexts.
"""
)
return


@app.cell
def _(np):
reals = np.array([0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1])
model_a = np.array([0.04, 0.10, 0.20, 0.24, 0.33, 0.42, 0.48, 0.61, 0.70, 0.82, 0.86, 0.94])
model_b = np.array([0.08, 0.18, 0.14, 0.39, 0.30, 0.50, 0.43, 0.57, 0.65, 0.74, 0.76, 0.88])
return model_a, model_b, reals


@app.cell
def _(create_performance_table, mo, model_a, reals):
mo.md("## One model — probability threshold")
create_performance_table(probs={"Model A": model_a}, reals=reals, by=0.10)
return


@app.cell
def _(create_performance_table, mo, model_a, model_b, reals):
mo.md("## Multiple models — probability threshold")
create_performance_table(probs={"Model A": model_a, "Model B": model_b}, reals=reals, by=0.10)
return


@app.cell
def _(create_performance_table, mo, model_a, model_b, reals):
mo.md("## Multiple models — PPCR")
create_performance_table(probs={"Model A": model_a, "Model B": model_b}, reals=reals, by=0.10, stratified_by=("ppcr",))
return


@app.cell
def _(np):
time_probs = {"Model A": np.array([0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00])}
time_reals = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])
times = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
return time_probs, time_reals, times


@app.cell
def _(create_performance_table_times, mo, time_probs, time_reals, times):
mo.md("## Fixed time horizons — 5 and 10")
create_performance_table_times(probs=time_probs, reals=time_reals, times=times, fixed_time_horizons=[5, 10], by=0.10)
return


@app.cell
def _(mo):
mo.md(
"""
The Great Tables backend is the Marimo-safe renderer. Reactable is kept
as an optional richer backend because it supports sortable columns and
expandable confusion-matrix details in environments that support its
Jupyter widget bridge.
"""
)
return


if __name__ == "__main__":
app.run()
55 changes: 55 additions & 0 deletions examples/performance_table_reactable.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: "rtichoke performance table — Reactable"
format:
html:
toc: false
jupyter: python3
page-layout: full
execute:
echo: false
warning: false
---

This page shows the optional `renderer="reactable"` output from the same public API used by the Great Tables preview.

```{python}
import numpy as np
from reactable import embed_css
from rtichoke import create_performance_table, create_performance_table_times

embed_css()
```

## One model — probability threshold

```{python}
reals = np.array([0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1])
model_a = np.array([0.04, 0.10, 0.20, 0.24, 0.33, 0.42, 0.48, 0.61, 0.70, 0.82, 0.86, 0.94])
model_b = np.array([0.08, 0.18, 0.14, 0.39, 0.30, 0.50, 0.43, 0.57, 0.65, 0.74, 0.76, 0.88])

create_performance_table(probs={"Model A": model_a}, reals=reals, by=0.10, renderer="reactable")
```

## Multiple models — probability threshold

```{python}
create_performance_table(probs={"Model A": model_a, "Model B": model_b}, reals=reals, by=0.10, renderer="reactable")
```

## Multiple models — PPCR

```{python}
create_performance_table(probs={"Model A": model_a, "Model B": model_b}, reals=reals, by=0.10, stratified_by=("ppcr",), renderer="reactable")
```

## Fixed time horizons — 5 and 10

```{python}
time_probs = {"Model A": np.array([0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00])}
time_reals = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])
times = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

create_performance_table_times(probs=time_probs, reals=time_reals, times=times, fixed_time_horizons=[5, 10], by=0.10, renderer="reactable")
```

The Reactable renderer is intended for widget-capable notebook/Quarto contexts. Click a row to inspect its expandable confusion matrix, and click column headers to sort.
7 changes: 7 additions & 0 deletions great-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ reference:
- prepare_performance_data_times
- prepare_binned_classification_data_times

- title: Performance Tables
desc: Summarize model performance across thresholds and time horizons.
contents:
- create_performance_table
- create_performance_table_times
- render_performance_table

- title: Discrimination
desc: ROC, precision-recall, gains, and lift visualizations.
contents:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dependencies = [
"pyarrow>=21.0.0",
"statsmodels>=0.14.0",
"polars>=1.31.0",
"reactable>=0.1.5",
"great-tables>=0.18.0",
]
name = "rtichoke"
version = "0.1.28"
Expand Down
9 changes: 9 additions & 0 deletions src/rtichoke/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
prepare_binned_classification_data_times as prepare_binned_classification_data_times,
)

from rtichoke.performance_table import (
create_performance_table as create_performance_table,
create_performance_table_times as create_performance_table_times,
render_performance_table as render_performance_table,
)

from rtichoke.summary_report.summary_report import (
create_summary_report as create_summary_report,
)
Expand All @@ -69,5 +75,8 @@
"plot_decision_curve",
"prepare_performance_data",
"prepare_performance_data_times",
"create_performance_table",
"create_performance_table_times",
"render_performance_table",
"create_summary_report",
]
91 changes: 91 additions & 0 deletions src/rtichoke/performance_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Performance-table API with multiple rendering backends."""

from __future__ import annotations

from collections.abc import Sequence
from typing import Dict, Literal, Union

import numpy as np
import polars as pl

from rtichoke.performance_data.performance_data import prepare_performance_data
from rtichoke.performance_data.performance_data_times import prepare_performance_data_times
from rtichoke.performance_table_great_tables import render_performance_table_great_tables
from rtichoke.performance_table_reactable import (
DEFAULT_COLORS,
render_performance_table_reactable,
)

PerformanceTableRenderer = Literal["great_tables", "reactable"]

_DEFAULT_HEURISTICS = [
{
"censoring_heuristic": "adjusted",
"competing_heuristic": "adjusted_as_negative",
}
]


def render_performance_table(
performance_data: pl.DataFrame,
color_values: Sequence[str] = DEFAULT_COLORS,
renderer: PerformanceTableRenderer = "great_tables",
):
"""Render prepared performance data with a selected table backend."""
if renderer == "great_tables":
return render_performance_table_great_tables(performance_data, color_values=color_values)
if renderer == "reactable":
return render_performance_table_reactable(performance_data, color_values=color_values)
raise ValueError("renderer must be either 'great_tables' or 'reactable'")


def create_performance_table(
probs: Dict[str, np.ndarray],
reals: Union[np.ndarray, Dict[str, np.ndarray]],
by: float = 0.01,
stratified_by: Sequence[str] = ("probability_threshold",),
color_values: Sequence[str] = DEFAULT_COLORS,
renderer: PerformanceTableRenderer = "great_tables",
):
"""Create an R-style rtichoke performance table."""
performance_data = prepare_performance_data(probs=probs, reals=reals, by=by, stratified_by=stratified_by)
return render_performance_table(performance_data, color_values=color_values, renderer=renderer)


def create_performance_table_times(
probs: Dict[str, np.ndarray],
reals: Union[np.ndarray, Dict[str, np.ndarray]],
times: Union[np.ndarray, Dict[str, np.ndarray]],
fixed_time_horizons: list[float],
heuristics_sets: list[Dict] = _DEFAULT_HEURISTICS,
by: float = 0.01,
stratified_by: Sequence[str] = ("probability_threshold",),
color_values: Sequence[str] = DEFAULT_COLORS,
renderer: PerformanceTableRenderer = "great_tables",
):
"""Create a time-dependent rtichoke performance table.

Numerical results come from ``prepare_performance_data_times()``. The table
keeps time horizon and censoring/competing-event heuristics visible so that
multiple requested evaluation scenarios are not collapsed in presentation.
Observed times are normalized to floating point at this public wrapper
boundary; fixed-horizon normalization is handled by the shared time-dependent
performance pipeline.
"""
if isinstance(times, dict):
normalized_times = {
key: np.asarray(value, dtype=float) for key, value in times.items()
}
else:
normalized_times = np.asarray(times, dtype=float)

performance_data = prepare_performance_data_times(
probs=probs,
reals=reals,
times=normalized_times,
fixed_time_horizons=fixed_time_horizons,
heuristics_sets=heuristics_sets,
by=by,
stratified_by=stratified_by,
)
return render_performance_table(performance_data, color_values=color_values, renderer=renderer)
Loading
Loading