"""Regenerate the perfmap: refit PCA, recluster, auto-derive cluster names. Run from /Users/aabraham/hf/tabbench/. Writes: - perfmap_freeze.json : new PCA basis + cluster labels - perfmap_regen_report.json : Spearman correlations + cluster diagnostics """ import json import os import sys from collections import Counter, defaultdict import numpy as np import pandas as pd from scipy.stats import spearmanr from sklearn.cluster import SpectralCoclustering from sklearn.decomposition import PCA HERE = os.path.dirname(os.path.abspath(__file__)) os.chdir(HERE) # Exec app.py up to gr.Blocks to get loaded per-dataset data + helpers src = open("app.py").read() END = src.find("with gr.Blocks(") ns = {"__file__": os.path.join(HERE, "app.py"), "__name__": "__main__"} exec(compile(src[:END], "app.py", "exec"), ns) public_per_dataset = ns["public_per_dataset"] public_per_dataset["dataset_id"] = public_per_dataset["dataset_id"].astype(str) # Drop hidden display models (single-fit XGBoost/CatBoost/LightGBM) before # fitting the PCA / clustering. Otherwise GBDTs get six columns per metric # (single + ensemble) and the PCA basis is biased toward GBDT structure. HIDDEN = ns.get("_HIDDEN_DISPLAY_MODELS", set()) if HIDDEN: before = public_per_dataset["model"].nunique() public_per_dataset = public_per_dataset[~public_per_dataset["model"].isin(HIDDEN)].copy() after = public_per_dataset["model"].nunique() print(f"Dropped hidden display models ({sorted(HIDDEN)}): {before} → {after} models in M") # --- 1. Build the rank-of-rank matrix M (datasets x model-metric columns) --- METRICS = ("Accuracy", "AUC", "F1_score", "Precision", "Recall", "Cross_entropy") LOWER = {"Cross_entropy"} rank_frames = [] for metric in METRICS: piv = public_per_dataset.pivot_table( index="dataset_id", columns="model", values=metric, aggfunc="mean", ) asc = metric not in LOWER # higher-is-better → ascending=False ranks = piv.rank(axis=1, ascending=not asc, method="average") n = piv.notna().sum(axis=1).clip(lower=1) rp = (ranks.sub(1, axis=0)).div(n - 1, axis=0).fillna(0.5) rp.columns = [f"{metric}/{m}" for m in rp.columns] rank_frames.append(rp) M = pd.concat(rank_frames, axis=1) M = 1 - M # higher = wins print(f"M shape: {M.shape} (datasets × model_metric_cols)") # --- 2. Refit PCA(2) on M --- pca = PCA(n_components=2, random_state=0) scores_all = pca.fit_transform(M.values) print(f"PCA explained variance: PC1={pca.explained_variance_ratio_[0]:.4f} " f"PC2={pca.explained_variance_ratio_[1]:.4f}") # --- 3. Spearman correlations of PC1/PC2 vs dataset metadata --- meta = pd.DataFrame(json.load(open("public_datasets_info.json"))) meta["dataset_id"] = meta["dataset_id"].astype(str) acc_f1 = public_per_dataset.groupby("dataset_id").agg( acc=("Accuracy", "mean"), f1=("F1_score", "mean"), ) acc_f1["imbalance"] = acc_f1["acc"] - acc_f1["f1"] scores_df = pd.DataFrame(scores_all, index=M.index, columns=["PC1", "PC2"]) joined = scores_df.merge( meta.set_index("dataset_id")[["rows", "features", "n_classes", "pct_cat", "avg_card"]], left_index=True, right_index=True, how="left", ).merge(acc_f1[["imbalance"]], left_index=True, right_index=True, how="left") correlations = {} for pc in ("PC1", "PC2"): correlations[pc] = {} for col in ("rows", "features", "n_classes", "pct_cat", "avg_card", "imbalance"): sub = joined[[pc, col]].dropna() if len(sub) < 5: correlations[pc][col] = None else: rho, p = spearmanr(sub[pc], sub[col]) correlations[pc][col] = {"rho": float(rho), "p": float(p), "n": int(len(sub))} print("\nSpearman ρ for PC1:") for k, v in correlations["PC1"].items(): if v: print(f" {k:14s} ρ={v['rho']:+.3f} (p={v['p']:.3e})") print("Spearman ρ for PC2:") for k, v in correlations["PC2"].items(): if v: print(f" {k:14s} ρ={v['rho']:+.3f} (p={v['p']:.3e})") # --- 4. Re-run SpectralCoclustering --- N_CLUSTERS = 5 # clip to safe range for SVD M_for_co = M.clip(0.0, 1.0).values cc = SpectralCoclustering(n_clusters=N_CLUSTERS, random_state=0).fit(M_for_co) row_labels = cc.row_labels_.astype(int) col_labels = cc.column_labels_.astype(int) print(f"\nCluster sizes:") sizes = pd.Series(row_labels).value_counts().sort_index() for c, n in sizes.items(): print(f" cluster {c}: {n} datasets") # --- 5. Cluster name auto-derivation --- # For each cluster, determine: # - winner family (most often #1 in per-dataset rank) # - 2nd family (most often #2 in rank) # - median rows / pct_cat → size + structure # - n_classes distribution → binary vs multi-class FAMILIES = { "FM2": ["seldon", "tabpfn_v3", "tabicl_v2"], "FM1": ["tabicl", "tabpfn", "mitra", "limix", "tabdpt"], "GBDT": ["xgboost_ensemble", "catboost_ensemble", "lightgbm_ensemble", "xgboost", "catboost", "lightgbm"], "NN": ["realmlp", "tabm", "modern_nca"], } FAMILY_OF = {m: fam for fam, models in FAMILIES.items() for m in models} PRETTY = { "FM2": "2nd-gen FM Supremacy", "FM1": "1st-gen FM Supremacy", "GBDT": "GBDT Supremacy", "NN": "Tuned NN Supremacy", } RUNNER = { "FM2": "1st-gen FMs runner-up", "FM1": "GBDTs runner-up", "GBDT": "2nd-gen FMs runner-up", "NN": "FMs runner-up", } # Per-dataset accuracy pivot acc_piv = public_per_dataset.pivot_table( index="dataset_id", columns="model", values="Accuracy", aggfunc="mean", ).dropna(axis=0, how="any") # Per-family mean accuracy per dataset → per-dataset family ranking fam_acc = {} for fam, models in FAMILIES.items(): cols = [m for m in models if m in acc_piv.columns] if cols: fam_acc[fam] = acc_piv[cols].mean(axis=1) fam_acc = pd.DataFrame(fam_acc) fam_rank = fam_acc.rank(axis=1, ascending=False, method="min") # 1 = best family def derive_cluster_name(cluster_idx, member_ids): sub_acc = fam_rank.loc[fam_rank.index.intersection(member_ids)] if sub_acc.empty: return f"Cluster {cluster_idx}" # Most common family at rank 1 and at rank 2 rank1 = (sub_acc == 1).sum(axis=0).idxmax() other_cols = [c for c in sub_acc.columns if c != rank1] rank2_counts = (sub_acc[other_cols] == 2).sum(axis=0) rank2_family = rank2_counts.idxmax() if not rank2_counts.empty else None # Get metadata for size and structure meta_sub = meta[meta["dataset_id"].isin(member_ids)] median_rows = float(meta_sub["rows"].median()) if not meta_sub["rows"].dropna().empty else np.nan median_pct_cat = float(meta_sub["pct_cat"].median()) if not meta_sub["pct_cat"].dropna().empty else np.nan n_classes_vals = meta_sub["n_classes"].dropna().tolist() pct_binary = sum(1 for c in n_classes_vals if c == 2) / max(len(n_classes_vals), 1) # Composition descriptor if median_rows < 3000: size = "Small" elif median_rows < 20000: size = "Mid-size" else: size = "Large" if pct_binary > 0.8: cls = "binary" elif pct_binary < 0.3: cls = "multi-class" else: cls = "mixed" if median_pct_cat is not np.nan and median_pct_cat > 0.4: struct = "categorical" elif median_pct_cat is not np.nan and median_pct_cat < 0.1: struct = "numeric" else: struct = "" composition_parts = [size, struct, cls] if struct else [size, cls] composition = " ".join(p for p in composition_parts if p) leader_label = PRETTY.get(rank1, rank1) if rank2_family: runner_label = ( f"{rank2_family.replace('FM2', '2nd-gen FMs').replace('FM1', '1st-gen FMs').replace('GBDT','GBDTs').replace('NN','Tuned NNs')} runner-up" ) else: runner_label = "Mixed runner-ups" return f"{leader_label}
{runner_label}
{composition}" cluster_names = {} cluster_diag = {} for c in sorted(set(row_labels)): c = int(c) # python int for JSON members = M.index[row_labels == c].tolist() name = derive_cluster_name(c, members) cluster_names[c] = name meta_sub = meta[meta["dataset_id"].isin(members)] cluster_diag[c] = { "n_datasets": int((row_labels == c).sum()), "name": name, "median_rows": float(meta_sub["rows"].median()) if not meta_sub["rows"].dropna().empty else None, "median_features": float(meta_sub["features"].median()) if not meta_sub["features"].dropna().empty else None, "median_n_classes": float(meta_sub["n_classes"].median()) if not meta_sub["n_classes"].dropna().empty else None, "median_pct_cat": float(meta_sub["pct_cat"].median()) if not meta_sub["pct_cat"].dropna().empty else None, "pct_binary": float(sum(1 for v in meta_sub["n_classes"].dropna().tolist() if v == 2) / max(len(meta_sub["n_classes"].dropna()), 1)), } print(f"\nCluster {c} (n={cluster_diag[c]['n_datasets']}):") print(f" name: {name}") print(f" median rows={cluster_diag[c]['median_rows']} features={cluster_diag[c]['median_features']} classes={cluster_diag[c]['median_n_classes']}") print(f" pct_cat median={cluster_diag[c]['median_pct_cat']} binary={cluster_diag[c]['pct_binary']:.2f}") # --- 6. Write the new freeze --- freeze = { "pca_components": pca.components_.tolist(), "pca_mean": pca.mean_.tolist(), "feature_cols": list(M.columns), "dataset_ids": list(M.index), "row_labels": [int(x) for x in row_labels], "col_labels": [int(x) for x in col_labels], "cluster_names": {str(k): v for k, v in cluster_names.items()}, "regenerated_at": "2026-05-29", "n_datasets": int(M.shape[0]), "n_features": int(M.shape[1]), } # Backup old freeze old_freeze_path = "perfmap_freeze.json" if os.path.exists(old_freeze_path): bak = old_freeze_path + ".bak.20260529" if not os.path.exists(bak): import shutil shutil.copyfile(old_freeze_path, bak) print(f"\nBacked up old freeze → {bak}") with open(old_freeze_path, "w") as f: json.dump(freeze, f) print(f"Wrote {old_freeze_path}") # --- 7. Diagnostics report --- report = { "pca_explained_variance": { "PC1": float(pca.explained_variance_ratio_[0]), "PC2": float(pca.explained_variance_ratio_[1]), }, "spearman_correlations": correlations, "cluster_diagnostics": {str(k): v for k, v in cluster_diag.items()}, "cluster_names_for_app": {str(k): v for k, v in cluster_names.items()}, } with open("perfmap_regen_report.json", "w") as f: json.dump(report, f, indent=2) print("Wrote perfmap_regen_report.json")