louisldn commited on
Commit
ce87c62
·
1 Parent(s): 0a63a22

added pct_improvement metrics and updated space

Browse files
Files changed (4) hide show
  1. __pycache__/utils.cpython-310.pyc +0 -0
  2. app.py +72 -6
  3. requirements.txt +2 -0
  4. utils.py +154 -0
__pycache__/utils.cpython-310.pyc ADDED
Binary file (5.51 kB). View file
 
app.py CHANGED
@@ -10,6 +10,8 @@ import seaborn as sns
10
  from io import BytesIO
11
  import base64
12
 
 
 
13
  # TabBench currently supports the following models, with new additions that keep coming:
14
  # - **NICL (Neuralk In-Context-Learning)**: Our in-house tabular foundation model based on an in-context learning architecture (proprietary).
15
  # - **TabICL**: A transformer-based model that performs feature compression before doing in-context learning on tabular data by conditioning on labeled support examples to predict unseen queries without task-specific training.
@@ -182,14 +184,56 @@ def highlight_max(df: pd.DataFrame):
182
  )
183
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  with open("complete_results_acad.json", 'r') as f:
186
  public_per_dataset = pd.json_normalize(json.load(f))
187
 
 
 
 
 
188
  public_enter_per_dataset = public_per_dataset
189
 
190
  with open("complete_results_indus.json", "r") as f:
191
  private_per_dataset = pd.json_normalize(json.load(f))
192
 
 
 
 
 
 
193
  with open("qrt.json", "r") as f:
194
  qrt_scores = json.load(f)
195
 
@@ -298,6 +342,16 @@ css = """
298
  white-space: normal !important;
299
  }
300
 
 
 
 
 
 
 
 
 
 
 
301
  .dataframe td, .dataframe th {
302
  max-width: 200px; /* adjust column width as needed */
303
  overflow-wrap: break-word;
@@ -395,14 +449,14 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
395
  gr.Markdown(' ')
396
  with gr.Row(equal_height=False):
397
  with gr.Column(scale=8):
398
- public_model_agg = public_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score']].mean().reset_index()
399
- public_enter_model_agg = public_enter_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score']].mean().reset_index()
400
  gr.Markdown("## 🏆 Overview of the Leaderboard *(Evaluation on publicly available Enterprise Datasets)*")
401
  gr.Markdown(' ')
402
 
403
  with gr.Row():
404
  metric_selector = gr.Dropdown(
405
- choices=["Accuracy", "Precision", "Recall", "F1_score", "AUC", "Elo_score"],
406
  value="Accuracy",
407
  label="📊 Metric to display",
408
  elem_classes=["compact-dropdown"]
@@ -489,7 +543,7 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
489
  gr.Markdown("## 🏆 Overview ")
490
 
491
  # Compute per-model averages from public_per_dataset
492
- public_model_agg = public_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score']].mean().reset_index()
493
  public_model_agg = public_model_agg.round(3)
494
  # Add model_type from model_info.json
495
  with open("model_info.json", "r") as f:
@@ -497,7 +551,7 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
497
 
498
  public_model_agg = public_model_agg.merge(model_info[["model_name", "model_type"]], left_on="model", right_on="model_name", how="left").drop(columns=["model_name"])
499
  # Only keep the required columns, with model_type second
500
- public_model_agg = public_model_agg[['model', 'model_type', 'Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score']]
501
  public_model_agg = public_model_agg.sort_values(by=["model"], key=lambda x: x != "NICL")
502
  model_df = gr.DataFrame(
503
  value=highlight_max(public_model_agg),
@@ -576,6 +630,7 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
576
  "F1_score",
577
  "AUC",
578
  "Elo_score",
 
579
  ]
580
 
581
  # --- Filtering & aggregation ---
@@ -592,6 +647,11 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
592
  if filtered.empty:
593
  return pd.DataFrame(columns=dataset_perf_cols)
594
 
 
 
 
 
 
595
  per_model_avg = (
596
  filtered
597
  .groupby("model")[dataset_perf_cols[1:]]
@@ -638,10 +698,14 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
638
  )
639
  domain_per_dataset = domain_per_dataset[domain_per_dataset['dataset_industry'] == industry]
640
 
 
 
 
 
641
 
642
  model_agg = (
643
  domain_per_dataset
644
- .groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score']]
645
  .mean()
646
  .round(3)
647
  .reset_index()
@@ -668,6 +732,7 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
668
  interactive=False,
669
  wrap=True,
670
  type="pandas",
 
671
  )
672
 
673
  gr.Markdown("## 🔍 Explore performance by dataset")
@@ -697,6 +762,7 @@ with gr.Blocks(css=css, theme=gr.themes.Default()) as demo:
697
  "F1_score",
698
  "AUC",
699
  "Elo_score",
 
700
  ]
701
 
702
  # Available datasets
 
10
  from io import BytesIO
11
  import base64
12
 
13
+ from utils import scores_to_battles, compute_bt_elo, compute_pct_improvement_over_baseline
14
+
15
  # TabBench currently supports the following models, with new additions that keep coming:
16
  # - **NICL (Neuralk In-Context-Learning)**: Our in-house tabular foundation model based on an in-context learning architecture (proprietary).
17
  # - **TabICL**: A transformer-based model that performs feature compression before doing in-context learning on tabular data by conditioning on labeled support examples to predict unseen queries without task-specific training.
 
184
  )
185
 
186
 
187
+ def compute_elo_for_subset(df: pd.DataFrame, metric: str = "Accuracy") -> pd.DataFrame:
188
+ """
189
+ Recompute Elo scores for a filtered subset of results.
190
+
191
+ Takes a DataFrame with per-dataset model scores and computes Elo ratings
192
+ based only on the battles within that subset.
193
+
194
+ Args:
195
+ df: DataFrame with columns 'model', 'dataset_name', and the metric column
196
+ metric: The metric to use for determining battle winners (default: "Accuracy")
197
+
198
+ Returns:
199
+ DataFrame with updated 'Elo_score' column
200
+ """
201
+ if df.empty:
202
+ return df
203
+
204
+ # Convert scores to pairwise battles
205
+ battles = scores_to_battles(df, metric)
206
+
207
+ if not battles:
208
+ return df
209
+
210
+ # Compute Elo scores from battles
211
+ elo_dict = compute_bt_elo(battles)
212
+
213
+ # Map Elo scores back to each row
214
+ df = df.copy()
215
+ df["Elo_score"] = df["model"].map(elo_dict)
216
+
217
+ return df
218
+
219
+
220
  with open("complete_results_acad.json", 'r') as f:
221
  public_per_dataset = pd.json_normalize(json.load(f))
222
 
223
+ # Recompute Elo scores for the full public dataset
224
+ public_per_dataset = compute_elo_for_subset(public_per_dataset, metric="Accuracy")
225
+ # Compute percentage improvement over XGBoost
226
+ public_per_dataset = compute_pct_improvement_over_baseline(public_per_dataset, baseline_model="xgboost_ensemble", metric="Accuracy")
227
  public_enter_per_dataset = public_per_dataset
228
 
229
  with open("complete_results_indus.json", "r") as f:
230
  private_per_dataset = pd.json_normalize(json.load(f))
231
 
232
+ # Recompute Elo scores for the full private dataset
233
+ private_per_dataset = compute_elo_for_subset(private_per_dataset, metric="Accuracy")
234
+ # Compute percentage improvement over XGBoost
235
+ private_per_dataset = compute_pct_improvement_over_baseline(private_per_dataset, baseline_model="xgboost_ensemble", metric="Accuracy")
236
+
237
  with open("qrt.json", "r") as f:
238
  qrt_scores = json.load(f)
239
 
 
342
  white-space: normal !important;
343
  }
344
 
345
+ .dataframe {
346
+ height: auto !important;
347
+ max-height: none !important;
348
+ }
349
+
350
+ .dataframe .table-wrap {
351
+ height: auto !important;
352
+ max-height: none !important;
353
+ }
354
+
355
  .dataframe td, .dataframe th {
356
  max-width: 200px; /* adjust column width as needed */
357
  overflow-wrap: break-word;
 
449
  gr.Markdown(' ')
450
  with gr.Row(equal_height=False):
451
  with gr.Column(scale=8):
452
+ public_model_agg = public_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score', 'Pct_Improvement_over_XGBoost']].mean().reset_index()
453
+ public_enter_model_agg = public_enter_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score', 'Pct_Improvement_over_XGBoost']].mean().reset_index()
454
  gr.Markdown("## 🏆 Overview of the Leaderboard *(Evaluation on publicly available Enterprise Datasets)*")
455
  gr.Markdown(' ')
456
 
457
  with gr.Row():
458
  metric_selector = gr.Dropdown(
459
+ choices=["Accuracy", "Precision", "Recall", "F1_score", "AUC", "Elo_score", "Pct_Improvement_over_XGBoost"],
460
  value="Accuracy",
461
  label="📊 Metric to display",
462
  elem_classes=["compact-dropdown"]
 
543
  gr.Markdown("## 🏆 Overview ")
544
 
545
  # Compute per-model averages from public_per_dataset
546
+ public_model_agg = public_per_dataset.groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score', 'Pct_Improvement_over_XGBoost']].mean().reset_index()
547
  public_model_agg = public_model_agg.round(3)
548
  # Add model_type from model_info.json
549
  with open("model_info.json", "r") as f:
 
551
 
552
  public_model_agg = public_model_agg.merge(model_info[["model_name", "model_type"]], left_on="model", right_on="model_name", how="left").drop(columns=["model_name"])
553
  # Only keep the required columns, with model_type second
554
+ public_model_agg = public_model_agg[['model', 'model_type', 'Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score', 'Pct_Improvement_over_XGBoost']]
555
  public_model_agg = public_model_agg.sort_values(by=["model"], key=lambda x: x != "NICL")
556
  model_df = gr.DataFrame(
557
  value=highlight_max(public_model_agg),
 
630
  "F1_score",
631
  "AUC",
632
  "Elo_score",
633
+ "Pct_Improvement_over_XGBoost",
634
  ]
635
 
636
  # --- Filtering & aggregation ---
 
647
  if filtered.empty:
648
  return pd.DataFrame(columns=dataset_perf_cols)
649
 
650
+ # Recompute Elo scores for this filtered subset
651
+ filtered = compute_elo_for_subset(filtered, metric="Accuracy")
652
+ # Recompute percentage improvement over XGBoost
653
+ filtered = compute_pct_improvement_over_baseline(filtered, baseline_model="xgboost_ensemble", metric="Accuracy")
654
+
655
  per_model_avg = (
656
  filtered
657
  .groupby("model")[dataset_perf_cols[1:]]
 
698
  )
699
  domain_per_dataset = domain_per_dataset[domain_per_dataset['dataset_industry'] == industry]
700
 
701
+ # Recompute Elo scores for this industry subset
702
+ domain_per_dataset = compute_elo_for_subset(domain_per_dataset, metric="Accuracy")
703
+ # Recompute percentage improvement over XGBoost for this industry subset
704
+ domain_per_dataset = compute_pct_improvement_over_baseline(domain_per_dataset, baseline_model="xgboost_ensemble", metric="Accuracy")
705
 
706
  model_agg = (
707
  domain_per_dataset
708
+ .groupby('model')[['Accuracy', 'Precision', 'Recall', 'F1_score', 'AUC', 'Elo_score', 'Pct_Improvement_over_XGBoost']]
709
  .mean()
710
  .round(3)
711
  .reset_index()
 
732
  interactive=False,
733
  wrap=True,
734
  type="pandas",
735
+ row_count=(len(model_agg), "fixed"),
736
  )
737
 
738
  gr.Markdown("## 🔍 Explore performance by dataset")
 
762
  "F1_score",
763
  "AUC",
764
  "Elo_score",
765
+ "Pct_Improvement_over_XGBoost",
766
  ]
767
 
768
  # Available datasets
requirements.txt CHANGED
@@ -14,3 +14,5 @@ scikit-learn
14
  requests
15
 
16
  pyarrow
 
 
 
14
  requests
15
 
16
  pyarrow
17
+
18
+ scipy
utils.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from collections import defaultdict
3
+ from typing import Dict, Iterable
4
+ import pandas as pd
5
+
6
+ import pandas as pd
7
+ from itertools import combinations
8
+ import scipy
9
+
10
+
11
+ def compute_pct_improvement_over_baseline(
12
+ df: pd.DataFrame,
13
+ baseline_model: str = "xgboost_ensemble",
14
+ metric: str = "Accuracy"
15
+ ) -> pd.DataFrame:
16
+ """
17
+ Compute the percentage improvement of each model over a baseline model.
18
+
19
+ For each dataset, computes: ((model_metric - baseline_metric) / baseline_metric) * 100
20
+
21
+ Args:
22
+ df: DataFrame with columns 'model', 'dataset_name', and the metric column
23
+ baseline_model: The model to use as baseline (default: "xgboost_ensemble")
24
+ metric: The metric to compute improvement on (default: "Accuracy")
25
+
26
+ Returns:
27
+ DataFrame with a new 'Pct_Improvement_over_XGBoost' column
28
+ """
29
+ if df.empty:
30
+ return df
31
+
32
+ df = df.copy()
33
+
34
+ # Get baseline scores per dataset
35
+ baseline_scores = (
36
+ df[df["model"] == baseline_model]
37
+ .set_index("dataset_name")[metric]
38
+ .to_dict()
39
+ )
40
+
41
+ # Compute percentage improvement for each row
42
+ def calc_pct_improvement(row):
43
+ baseline = baseline_scores.get(row["dataset_name"])
44
+ if baseline is None or baseline == 0:
45
+ return None
46
+ return ((row[metric] - baseline) / baseline) * 100
47
+
48
+ df["Pct_Improvement_over_XGBoost"] = df.apply(calc_pct_improvement, axis=1)
49
+
50
+ return df
51
+
52
+
53
+ def scores_to_battles(df: pd.DataFrame, metric: str = "Accuracy") -> pd.DataFrame:
54
+ battles = []
55
+
56
+ for dataset, group in df.groupby("dataset_name"):
57
+ # Sort classifiers in descending order of metric
58
+ group_sorted = group.sort_values(by=metric, ascending=False)
59
+ for (i1, row1), (i2, row2) in combinations(group_sorted.iterrows(), 2):
60
+ if row1[metric] == row2[metric]:
61
+ winner = "tie"
62
+ elif row1[metric] > row2[metric]:
63
+ winner = "model_a"
64
+ else:
65
+ winner = "model_b"
66
+ battles.append({
67
+ "model_a": row1["model"],
68
+ "model_b": row2["model"],
69
+ "winner": winner,
70
+ "dataset": dataset,
71
+ })
72
+
73
+ return battles
74
+
75
+
76
+ def _sigmoid(x: float, eps: float = 1e-7) -> float:
77
+ """Stable sigmoid with clipped output."""
78
+ val = 0.5 * (1 + math.tanh(0.5 * x))
79
+ return max(eps, min(1.0 - eps, val))
80
+
81
+
82
+ def compute_bt_elo(
83
+ battles: Iterable[Dict[str, str]],
84
+ SCALE: float = 400.0,
85
+ BASE: float = 10.0,
86
+ INIT_RATING: float = 1000.0,
87
+ lr: float = 0.05,
88
+ n_iter: int = 1000,
89
+ use_scipy: bool = True,
90
+ ) -> Dict[str, float]:
91
+ """Fit a Bradley--Terry model.
92
+
93
+ ``BASE`` controls the link function scale. If ``BASE=10`` (the default),
94
+ the win probability follows the usual Elo form
95
+
96
+ ``P(win) = 1 / (1 + BASE ** ((rating_b - rating_a) / SCALE))``.
97
+
98
+ The function will use :mod:`scipy.optimize` if available for a fast
99
+ optimisation of the negative log-likelihood. If SciPy is not installed,
100
+ it falls back to the simple gradient-descent routine previously used.
101
+ """
102
+
103
+ models = sorted({b["model_a"] for b in battles} | {b["model_b"] for b in battles})
104
+ battles_list = list(battles)
105
+
106
+ if use_scipy:
107
+ try:
108
+ import numpy as np
109
+ from scipy.optimize import minimize
110
+ except Exception: # pragma: no cover - SciPy not available
111
+ use_scipy = False
112
+
113
+ if use_scipy:
114
+ idx = {m: k for k, m in enumerate(models)}
115
+
116
+ def nll(theta_vec: "np.ndarray") -> float:
117
+ loss = 0.0
118
+ for row in battles_list:
119
+ i = idx[row["model_a"]]
120
+ j = idx[row["model_b"]]
121
+ s = math.log(BASE) * (theta_vec[i] - theta_vec[j])
122
+ p = _sigmoid(s)
123
+ y = 1.0 if row["winner"] == "model_a" else 0.0
124
+ if str(row["winner"]).startswith("tie"):
125
+ y = 0.5
126
+ # Binary cross entropy with y in [0, 1]
127
+ loss -= y * math.log(p) + (1 - y) * math.log(1 - p)
128
+ return loss
129
+
130
+ theta0 = [0.0] * len(models)
131
+ res = minimize(nll, theta0, method="BFGS")
132
+ theta_opt = res.x - sum(res.x) / len(res.x)
133
+ theta = {m: theta_opt[idx[m]] for m in models}
134
+ else:
135
+ theta = {m: 0.0 for m in models}
136
+ for _ in range(n_iter):
137
+ grad = {m: 0.0 for m in models}
138
+ for row in battles_list:
139
+ i = row["model_a"]
140
+ j = row["model_b"]
141
+ w = row["winner"]
142
+ y = 1.0 if w == "model_a" else 0.0
143
+ if str(w).startswith("tie"):
144
+ y = 0.5
145
+ s = math.log(BASE) * (theta[i] - theta[j])
146
+ p = _sigmoid(s)
147
+ diff = (p - y) * math.log(BASE)
148
+ grad[i] += diff
149
+ grad[j] -= diff
150
+ for m in models:
151
+ theta[m] -= lr * grad[m] / len(battles_list)
152
+
153
+ return {m: SCALE * theta[m] + INIT_RATING for m in sorted(models, key=lambda x: -theta[x])}
154
+