Spaces:
Build error
Build error
Add toggleable metric charts
Browse files
app.py
CHANGED
|
@@ -13,6 +13,7 @@ import numpy as np
|
|
| 13 |
import pandas as pd
|
| 14 |
import plotly.express as px
|
| 15 |
import plotly.graph_objects as go
|
|
|
|
| 16 |
from sklearn.base import clone
|
| 17 |
from sklearn.compose import ColumnTransformer
|
| 18 |
from sklearn.datasets import fetch_california_housing, fetch_openml, load_breast_cancer, load_diabetes, load_digits, load_iris, load_wine, make_classification
|
|
@@ -37,6 +38,8 @@ APP_TITLE = "tabBench"
|
|
| 37 |
TABFM_MODEL_ID = "google/tabfm-1.0.0-pytorch"
|
| 38 |
RANDOM_STATE = 42
|
| 39 |
CANDY_DATA_URL = "https://raw.githubusercontent.com/fivethirtyeight/data/master/candy-power-ranking/candy-data.csv"
|
|
|
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
@dataclass(frozen=True)
|
|
@@ -609,19 +612,83 @@ def benchmark_frame(
|
|
| 609 |
return results[["rank", "model", "status", *metric_cols]], preview, summary
|
| 610 |
|
| 611 |
|
| 612 |
-
def metric_chart(results: pd.DataFrame) -> go.Figure:
|
| 613 |
if results is None or results.empty:
|
| 614 |
return go.Figure()
|
| 615 |
-
|
| 616 |
-
if
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
return fig
|
| 626 |
|
| 627 |
|
|
@@ -640,20 +707,46 @@ def time_chart(results: pd.DataFrame) -> go.Figure:
|
|
| 640 |
return fig
|
| 641 |
|
| 642 |
|
| 643 |
-
def run_catalog(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
spec = get_spec(dataset_name)
|
| 645 |
df = get_dataset(dataset_name, sample_size, seed)
|
| 646 |
results, preview, summary = benchmark_frame(df, spec.target, spec.task, sample_size, test_percent / 100, seed, selected_models, include_tabfm)
|
| 647 |
-
return summary, results.round(4), metric_chart(results), time_chart(results), preview
|
| 648 |
|
| 649 |
|
| 650 |
-
def run_upload(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
if file is None:
|
| 652 |
raise gr.Error("Upload a CSV file first.")
|
| 653 |
df = pd.read_csv(file.name)
|
| 654 |
selected_task = None if task == "Auto" else task.lower()
|
| 655 |
results, preview, summary = benchmark_frame(df, target, selected_task, sample_size, test_percent / 100, seed, selected_models, include_tabfm)
|
| 656 |
-
return summary, results.round(4), metric_chart(results), time_chart(results), preview
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
|
| 658 |
|
| 659 |
def catalog_table() -> pd.DataFrame:
|
|
@@ -714,6 +807,8 @@ def build_app() -> gr.Blocks:
|
|
| 714 |
seed = gr.Number(value=42, precision=0, label="Random seed")
|
| 715 |
models = gr.CheckboxGroup(DEFAULT_MODELS, value=["RandomForest", "HistGradientBoosting", "XGBoost", "Dummy"], label="Baselines")
|
| 716 |
include_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
|
|
|
|
|
|
|
| 717 |
run_btn = gr.Button("Run benchmark", variant="primary")
|
| 718 |
with gr.Column(scale=3):
|
| 719 |
summary = gr.Markdown()
|
|
@@ -722,8 +817,11 @@ def build_app() -> gr.Blocks:
|
|
| 722 |
chart = gr.Plot(label="Metric comparison")
|
| 723 |
speed = gr.Plot(label="Speed")
|
| 724 |
preview = gr.Dataframe(label="Held-out preview", interactive=False)
|
| 725 |
-
|
| 726 |
-
|
|
|
|
|
|
|
|
|
|
| 727 |
with gr.Tab("Upload Dataset"):
|
| 728 |
with gr.Row():
|
| 729 |
with gr.Column(scale=1, elem_classes=["panel"]):
|
|
@@ -735,6 +833,8 @@ def build_app() -> gr.Blocks:
|
|
| 735 |
upload_seed = gr.Number(value=42, precision=0, label="Random seed")
|
| 736 |
upload_models = gr.CheckboxGroup(DEFAULT_MODELS, value=["RandomForest", "HistGradientBoosting", "XGBoost", "Dummy"], label="Baselines")
|
| 737 |
upload_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
|
|
|
|
|
|
|
| 738 |
upload_btn = gr.Button("Run uploaded dataset", variant="primary")
|
| 739 |
with gr.Column(scale=3):
|
| 740 |
upload_summary = gr.Markdown()
|
|
@@ -745,9 +845,11 @@ def build_app() -> gr.Blocks:
|
|
| 745 |
upload_preview = gr.Dataframe(label="Held-out preview", interactive=False)
|
| 746 |
upload_btn.click(
|
| 747 |
run_upload,
|
| 748 |
-
[file, target, task, upload_sample, upload_test_pct, upload_seed, upload_models, upload_tabfm],
|
| 749 |
[upload_summary, upload_leaderboard, upload_chart, upload_speed, upload_preview],
|
| 750 |
)
|
|
|
|
|
|
|
| 751 |
with gr.Tab("Dataset Catalog"):
|
| 752 |
gr.Dataframe(catalog_table(), interactive=False, label="Included benchmark catalog")
|
| 753 |
gr.Markdown(
|
|
|
|
| 13 |
import pandas as pd
|
| 14 |
import plotly.express as px
|
| 15 |
import plotly.graph_objects as go
|
| 16 |
+
from plotly.subplots import make_subplots
|
| 17 |
from sklearn.base import clone
|
| 18 |
from sklearn.compose import ColumnTransformer
|
| 19 |
from sklearn.datasets import fetch_california_housing, fetch_openml, load_breast_cancer, load_diabetes, load_digits, load_iris, load_wine, make_classification
|
|
|
|
| 38 |
TABFM_MODEL_ID = "google/tabfm-1.0.0-pytorch"
|
| 39 |
RANDOM_STATE = 42
|
| 40 |
CANDY_DATA_URL = "https://raw.githubusercontent.com/fivethirtyeight/data/master/candy-power-ranking/candy-data.csv"
|
| 41 |
+
GOOGLE_COLORS = ["#4285F4", "#DB4437", "#F4B400", "#0F9D58", "#A142F4", "#00ACC1"]
|
| 42 |
+
METRIC_CHOICES = ["accuracy", "f1_weighted", "roc_auc", "rmse", "mae", "r2", "seconds"]
|
| 43 |
|
| 44 |
|
| 45 |
@dataclass(frozen=True)
|
|
|
|
| 612 |
return results[["rank", "model", "status", *metric_cols]], preview, summary
|
| 613 |
|
| 614 |
|
| 615 |
+
def metric_chart(results: pd.DataFrame, selected_metrics: list[str] | None = None, chart_style: str = "Line") -> go.Figure:
|
| 616 |
if results is None or results.empty:
|
| 617 |
return go.Figure()
|
| 618 |
+
selected_metrics = selected_metrics or METRIC_CHOICES
|
| 619 |
+
metric_cols = [c for c in selected_metrics if c in results.columns and results[c].notna().any()]
|
| 620 |
+
if not metric_cols:
|
| 621 |
+
metric_cols = [c for c in METRIC_CHOICES if c in results.columns and results[c].notna().any()]
|
| 622 |
+
if not metric_cols:
|
| 623 |
+
return go.Figure()
|
| 624 |
+
|
| 625 |
+
clean = results.sort_values("rank") if "rank" in results.columns else results.copy()
|
| 626 |
+
x_labels = clean["model"].astype(str).tolist()
|
| 627 |
+
if chart_style == "Radar":
|
| 628 |
+
normalized = clean[["model", *metric_cols]].copy()
|
| 629 |
+
for metric in metric_cols:
|
| 630 |
+
values = pd.to_numeric(normalized[metric], errors="coerce")
|
| 631 |
+
lo, hi = values.min(), values.max()
|
| 632 |
+
if pd.isna(lo) or pd.isna(hi) or hi == lo:
|
| 633 |
+
normalized[metric] = 0.5
|
| 634 |
+
elif metric in {"rmse", "mae", "seconds"}:
|
| 635 |
+
normalized[metric] = 1 - ((values - lo) / (hi - lo))
|
| 636 |
+
else:
|
| 637 |
+
normalized[metric] = (values - lo) / (hi - lo)
|
| 638 |
+
fig = go.Figure()
|
| 639 |
+
theta = metric_cols + [metric_cols[0]]
|
| 640 |
+
for idx, row in normalized.iterrows():
|
| 641 |
+
values = [row[m] for m in metric_cols] + [row[metric_cols[0]]]
|
| 642 |
+
fig.add_trace(
|
| 643 |
+
go.Scatterpolar(
|
| 644 |
+
r=values,
|
| 645 |
+
theta=theta,
|
| 646 |
+
fill="toself",
|
| 647 |
+
name=str(row["model"]),
|
| 648 |
+
line=dict(color=GOOGLE_COLORS[idx % len(GOOGLE_COLORS)], width=2),
|
| 649 |
+
opacity=0.78,
|
| 650 |
+
)
|
| 651 |
+
)
|
| 652 |
+
fig.update_layout(
|
| 653 |
+
template="plotly_white",
|
| 654 |
+
height=420,
|
| 655 |
+
margin=dict(l=35, r=35, t=35, b=25),
|
| 656 |
+
polar=dict(radialaxis=dict(visible=True, range=[0, 1])),
|
| 657 |
+
legend_title_text="Model",
|
| 658 |
+
title="Normalized metric shape (higher is better)",
|
| 659 |
+
)
|
| 660 |
+
return fig
|
| 661 |
+
|
| 662 |
+
fig = make_subplots(
|
| 663 |
+
rows=len(metric_cols),
|
| 664 |
+
cols=1,
|
| 665 |
+
shared_xaxes=True,
|
| 666 |
+
vertical_spacing=0.08,
|
| 667 |
+
subplot_titles=[metric.replace("_", " ").upper() for metric in metric_cols],
|
| 668 |
+
)
|
| 669 |
+
for idx, metric in enumerate(metric_cols, start=1):
|
| 670 |
+
fig.add_trace(
|
| 671 |
+
go.Scatter(
|
| 672 |
+
x=x_labels,
|
| 673 |
+
y=clean[metric],
|
| 674 |
+
mode="lines+markers",
|
| 675 |
+
name=metric,
|
| 676 |
+
line=dict(color=GOOGLE_COLORS[(idx - 1) % len(GOOGLE_COLORS)], width=3, shape="spline"),
|
| 677 |
+
marker=dict(size=9, line=dict(color="white", width=1.5)),
|
| 678 |
+
hovertemplate=f"<b>%{{x}}</b><br>{metric}: %{{y:.4f}}<extra></extra>",
|
| 679 |
+
),
|
| 680 |
+
row=idx,
|
| 681 |
+
col=1,
|
| 682 |
+
)
|
| 683 |
+
if metric in {"accuracy", "f1_weighted", "roc_auc", "r2"}:
|
| 684 |
+
fig.update_yaxes(range=[min(0, float(clean[metric].min()) - 0.05), 1.02], row=idx, col=1)
|
| 685 |
+
fig.update_layout(
|
| 686 |
+
template="plotly_white",
|
| 687 |
+
height=max(320, 185 * len(metric_cols)),
|
| 688 |
+
margin=dict(l=30, r=20, t=40, b=35),
|
| 689 |
+
showlegend=False,
|
| 690 |
+
hovermode="x unified",
|
| 691 |
+
)
|
| 692 |
return fig
|
| 693 |
|
| 694 |
|
|
|
|
| 707 |
return fig
|
| 708 |
|
| 709 |
|
| 710 |
+
def run_catalog(
|
| 711 |
+
dataset_name: str,
|
| 712 |
+
sample_size: int,
|
| 713 |
+
test_percent: int,
|
| 714 |
+
seed: int,
|
| 715 |
+
selected_models: list[str],
|
| 716 |
+
include_tabfm: bool,
|
| 717 |
+
selected_metrics: list[str],
|
| 718 |
+
chart_style: str,
|
| 719 |
+
):
|
| 720 |
spec = get_spec(dataset_name)
|
| 721 |
df = get_dataset(dataset_name, sample_size, seed)
|
| 722 |
results, preview, summary = benchmark_frame(df, spec.target, spec.task, sample_size, test_percent / 100, seed, selected_models, include_tabfm)
|
| 723 |
+
return summary, results.round(4), metric_chart(results, selected_metrics, chart_style), time_chart(results), preview
|
| 724 |
|
| 725 |
|
| 726 |
+
def run_upload(
|
| 727 |
+
file,
|
| 728 |
+
target: str,
|
| 729 |
+
task: str,
|
| 730 |
+
sample_size: int,
|
| 731 |
+
test_percent: int,
|
| 732 |
+
seed: int,
|
| 733 |
+
selected_models: list[str],
|
| 734 |
+
include_tabfm: bool,
|
| 735 |
+
selected_metrics: list[str],
|
| 736 |
+
chart_style: str,
|
| 737 |
+
):
|
| 738 |
if file is None:
|
| 739 |
raise gr.Error("Upload a CSV file first.")
|
| 740 |
df = pd.read_csv(file.name)
|
| 741 |
selected_task = None if task == "Auto" else task.lower()
|
| 742 |
results, preview, summary = benchmark_frame(df, target, selected_task, sample_size, test_percent / 100, seed, selected_models, include_tabfm)
|
| 743 |
+
return summary, results.round(4), metric_chart(results, selected_metrics, chart_style), time_chart(results), preview
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def redraw_metric_chart(results: pd.DataFrame, selected_metrics: list[str], chart_style: str):
|
| 747 |
+
if results is None or len(results) == 0:
|
| 748 |
+
return go.Figure()
|
| 749 |
+
return metric_chart(pd.DataFrame(results), selected_metrics, chart_style)
|
| 750 |
|
| 751 |
|
| 752 |
def catalog_table() -> pd.DataFrame:
|
|
|
|
| 807 |
seed = gr.Number(value=42, precision=0, label="Random seed")
|
| 808 |
models = gr.CheckboxGroup(DEFAULT_MODELS, value=["RandomForest", "HistGradientBoosting", "XGBoost", "Dummy"], label="Baselines")
|
| 809 |
include_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
|
| 810 |
+
metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
|
| 811 |
+
chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
|
| 812 |
run_btn = gr.Button("Run benchmark", variant="primary")
|
| 813 |
with gr.Column(scale=3):
|
| 814 |
summary = gr.Markdown()
|
|
|
|
| 817 |
chart = gr.Plot(label="Metric comparison")
|
| 818 |
speed = gr.Plot(label="Speed")
|
| 819 |
preview = gr.Dataframe(label="Held-out preview", interactive=False)
|
| 820 |
+
run_inputs = [dataset, sample, test_pct, seed, models, include_tabfm, metric_toggles, chart_style]
|
| 821 |
+
run_btn.click(run_catalog, run_inputs, [summary, leaderboard, chart, speed, preview])
|
| 822 |
+
metric_toggles.change(redraw_metric_chart, [leaderboard, metric_toggles, chart_style], chart)
|
| 823 |
+
chart_style.change(redraw_metric_chart, [leaderboard, metric_toggles, chart_style], chart)
|
| 824 |
+
demo.load(run_catalog, run_inputs, [summary, leaderboard, chart, speed, preview])
|
| 825 |
with gr.Tab("Upload Dataset"):
|
| 826 |
with gr.Row():
|
| 827 |
with gr.Column(scale=1, elem_classes=["panel"]):
|
|
|
|
| 833 |
upload_seed = gr.Number(value=42, precision=0, label="Random seed")
|
| 834 |
upload_models = gr.CheckboxGroup(DEFAULT_MODELS, value=["RandomForest", "HistGradientBoosting", "XGBoost", "Dummy"], label="Baselines")
|
| 835 |
upload_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
|
| 836 |
+
upload_metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
|
| 837 |
+
upload_chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
|
| 838 |
upload_btn = gr.Button("Run uploaded dataset", variant="primary")
|
| 839 |
with gr.Column(scale=3):
|
| 840 |
upload_summary = gr.Markdown()
|
|
|
|
| 845 |
upload_preview = gr.Dataframe(label="Held-out preview", interactive=False)
|
| 846 |
upload_btn.click(
|
| 847 |
run_upload,
|
| 848 |
+
[file, target, task, upload_sample, upload_test_pct, upload_seed, upload_models, upload_tabfm, upload_metric_toggles, upload_chart_style],
|
| 849 |
[upload_summary, upload_leaderboard, upload_chart, upload_speed, upload_preview],
|
| 850 |
)
|
| 851 |
+
upload_metric_toggles.change(redraw_metric_chart, [upload_leaderboard, upload_metric_toggles, upload_chart_style], upload_chart)
|
| 852 |
+
upload_chart_style.change(redraw_metric_chart, [upload_leaderboard, upload_metric_toggles, upload_chart_style], upload_chart)
|
| 853 |
with gr.Tab("Dataset Catalog"):
|
| 854 |
gr.Dataframe(catalog_table(), interactive=False, label="Included benchmark catalog")
|
| 855 |
gr.Markdown(
|