Spaces:
Running
Running
File size: 8,437 Bytes
d916e56 fa4e6e2 7ccbfbc 2f79998 52d607e 2f79998 d916e56 2f79998 d916e56 a28b2ed d916e56 6fef024 d916e56 6fef024 52d607e 6fef024 d916e56 52d607e 2f79998 52d607e d916e56 52d607e 2f79998 7ccbfbc 2a1a3d6 7ccbfbc 2a1a3d6 7ccbfbc 2a1a3d6 2f79998 fa4e6e2 7ccbfbc 2a1a3d6 fa4e6e2 2f79998 fa4e6e2 d916e56 6fef024 7ccbfbc 6fef024 d916e56 6fef024 d916e56 6fef024 7ccbfbc 2a1a3d6 7ccbfbc 6fef024 2a1a3d6 6fef024 7ccbfbc 2a1a3d6 6fef024 d916e56 6fef024 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | """Pre-compute Plotly figure dicts for every (industry Γ metric Γ view) combo
and write them to figures_cache.json next to app.py.
Run this whenever the underlying results or metadata change.
Usage:
cd hf/tabbench && python build_figures_cache.py
"""
import json, os, sys
import pandas as pd
# We need the plot function + the data-loading + Elo/improvement helpers from app.py.
# Approach: exec app.py's source up to the gr.Blocks() boundary in a namespace,
# pulling out the needed symbols. This avoids importing app.py (which would
# launch the Gradio server).
HERE = os.path.dirname(os.path.abspath(__file__))
os.chdir(HERE)
src = open("app.py").read()
END_MARK = "with gr.Blocks("
idx = src.find(END_MARK)
if idx < 0:
print("ERROR: couldn't find 'with gr.Blocks(' in app.py", file=sys.stderr)
sys.exit(1)
prefix = src[:idx]
ns = {"__file__": os.path.join(HERE, "app.py"), "__name__": "__main__"}
exec(compile(prefix, "app.py", "exec"), ns)
plot_global_model_ranking_plotly = ns["plot_global_model_ranking_plotly"]
plot_winrate_matrix = ns["plot_winrate_matrix"]
plot_significance_forest = ns["plot_significance_forest"]
# Use the FULL per-dataset frame as input. We can't use public_enter_per_dataset
# (which hides single-fit GBDTs) because compute_pct_improvement_over_baseline
# needs the `xgboost_ensemble` rows present as the baseline, or every %β cell
# turns NaN. The hidden display models are filtered out at the agg step below.
public_per_dataset = ns["public_per_dataset"]
public_enter_per_dataset = ns["public_enter_per_dataset"]
compute_elo_for_subset = ns["compute_elo_for_subset"]
compute_pct_improvement_over_baseline = ns["compute_pct_improvement_over_baseline"]
HIDDEN_DISPLAY_MODELS = ns.get("_HIDDEN_DISPLAY_MODELS", set())
# Same buckets as the app
INDUSTRY_BUCKETS = [
"All",
"Healthcare",
"Behavioral",
"Computer Vision",
"Industry & Science",
"Finance/insurance",
"Games & Synthetic",
"Social/Public",
"Other",
]
METRICS = ["Accuracy", "AUC", "F1_score", "Precision", "Recall", "Cross_entropy", "Elo_score", "%β over XGBoost"]
# External-benchmark dataset filters. Must stay in sync with the
# _BENCHMARK_DATASET_IDS dict in app.py (single source of truth: the app).
BENCHMARK_DATASET_IDS = {
"TabArena": {
46905, 46906, 46908, 46910, 46911, 46912, 46916, 46919,
46920, 46922, 46924, 46927, 46929, 46930, 46932, 46933,
46935, 46937, 46938, 46940, 46941, 46947, 46950, 46955,
46956, 46958, 46960, 46962, 46963, 46969, 46979, 46980,
# equivalents we have under different IDs:
1464, 40701, 31, 37, 1494,
# kddcup09_appetency
46939,
},
}
BENCHMARKS = ["All", "TabArena"]
# Same metadata merge as the app does at boot.
meta_df = pd.DataFrame(json.load(open("public_datasets_info.json")))
meta_df['dataset_id'] = meta_df['dataset_id'].astype(str)
public_per_dataset['dataset_id'] = public_per_dataset['dataset_id'].astype(str)
public_per_dataset_with_meta = public_per_dataset.merge(
meta_df[['dataset_id', 'dataset_industry', 'rows', 'features']],
on='dataset_id', how='left',
)
def _apply_benchmark(df, benchmark):
if not benchmark or benchmark == "All":
return df
ids = {str(i) for i in BENCHMARK_DATASET_IDS.get(benchmark, set())}
return df[df['dataset_id'].astype(str).isin(ids)]
def build_one(benchmark, industry, metric):
"""Return (bars_dict, forest_dict, forest_kpi_html, winrate_dict, agg_records, n_datasets)."""
df = _apply_benchmark(public_per_dataset_with_meta, benchmark)
if industry != "All":
df = df[df['dataset_industry'] == industry]
if df.empty:
return None, None, None, None, [], 0
# Recompute Elo + %β on the filtered subset. Must include the
# `xgboost_ensemble` baseline rows; we drop the hidden display models
# only AFTER the per-dataset comparison is done.
df = compute_elo_for_subset(df, metric="Accuracy")
df = compute_pct_improvement_over_baseline(df, baseline_model="xgboost_ensemble", metric="Accuracy")
df_display = df[~df['model'].isin(HIDDEN_DISPLAY_MODELS)].copy() if HIDDEN_DISPLAY_MODELS else df
agg = df_display.groupby('model')[['Accuracy', 'AUC', 'F1_score', 'Precision', 'Recall', 'Cross_entropy', 'Elo_score', '%β over XGBoost']].mean().reset_index()
bars = plot_global_model_ranking_plotly(agg, metric=metric, per_dataset_df=df_display)
# Forest plot (Wilcoxon-Holm leading-group highlight + paired-bootstrap CI).
# This is the slow figure on live render (B=1000 bootstrap Γ all model pairs),
# so pre-caching it is the whole point of this build script for the Bars view.
# Returns (figure, kpi_html) β both go in the cache.
try:
forest, forest_kpi = plot_significance_forest(df_display, metric=metric)
except Exception as _e:
print(f" [warn] forest plot failed for bench={benchmark} ind={industry} metric={metric}: {_e}")
forest, forest_kpi = None, None
# Win-rate matrix should ALSO use the display-filtered frame so the
# heatmap doesn't show duplicate display names (single-fit vs ensemble).
winrate = plot_winrate_matrix(df_display, metric=metric) if metric not in ("Elo_score", "%β over XGBoost") else None
return (
bars.to_plotly_json(),
forest.to_plotly_json() if forest is not None else None,
forest_kpi,
winrate.to_plotly_json() if winrate is not None else None,
agg.round(6).to_dict(orient="records"),
int(df_display['dataset_id'].nunique()),
)
# Cache shape:
# { benchmark_key: { industry: { "metrics": {metric: fig},
# "forest": {metric: fig},
# "winrate": {metric: fig},
# "n_datasets": int,
# "table": [...] } } }
# Backward compat: the "All" benchmark is also written at the top level so
# existing app.py readers that index `_figures_cache[industry]` keep working.
cache = {}
total = len(BENCHMARKS) * len(INDUSTRY_BUCKETS) * len(METRICS)
i = 0
for benchmark in BENCHMARKS:
cache[benchmark] = {}
for industry in INDUSTRY_BUCKETS:
cache[benchmark][industry] = {
"metrics": {}, "forest": {}, "forest_kpi": {}, "winrate": {},
"n_datasets": 0, "table": [],
}
for metric in METRICS:
i += 1
bars_dict, forest_dict, forest_kpi_html, winrate_dict, agg, n = build_one(benchmark, industry, metric)
cache[benchmark][industry]["metrics"][metric] = bars_dict
if forest_dict is not None:
cache[benchmark][industry]["forest"][metric] = forest_dict
if forest_kpi_html is not None:
cache[benchmark][industry]["forest_kpi"][metric] = forest_kpi_html
if winrate_dict is not None:
cache[benchmark][industry]["winrate"][metric] = winrate_dict
cache[benchmark][industry]["n_datasets"] = n
if not cache[benchmark][industry]["table"]:
cache[benchmark][industry]["table"] = agg
print(f"[{i:3d}/{total}] bench={benchmark:10s} industry={industry!r:25s} metric={metric:20s} n_datasets={n}")
# Backward-compat alias: top-level keys mirror the All-benchmark slice so
# legacy `cache[industry]` reads still resolve. Done as a separate dict
# snapshot first to avoid mutating-while-iterating issues.
_all_slice = {industry: cache["All"][industry] for industry in INDUSTRY_BUCKETS}
for industry, payload in _all_slice.items():
cache[industry] = payload
import numpy as np
def _default(o):
if isinstance(o, np.ndarray):
return o.tolist()
if isinstance(o, (np.integer,)):
return int(o)
if isinstance(o, (np.floating,)):
return float(o)
raise TypeError(f"not serializable: {type(o).__name__}")
out_path = os.path.join(HERE, "figures_cache.json")
with open(out_path, "w") as f:
json.dump(cache, f, default=_default) # NB: no indent β keeps file small
size_kb = os.path.getsize(out_path) / 1024
print(f"\nWrote {out_path} ({size_kb:.1f} KB)")
_n_combos = 0
for k in BENCHMARKS:
bench = cache.get(k, {})
if not isinstance(bench, dict):
continue
for ind_v in bench.values():
if isinstance(ind_v, dict):
_n_combos += len(ind_v.get("metrics", {}))
print(f"Combos cached: {_n_combos}")
|