josephsoo commited on
Commit
6694fb5
·
1 Parent(s): a303a1e

Simplify leaderboard controls and labels

Browse files
Files changed (3) hide show
  1. README.md +1 -1
  2. app.py +68 -51
  3. assets/styles.css +5 -1
README.md CHANGED
@@ -8,7 +8,7 @@ app_port: 7860
8
 
9
  Interactive dashboard for the Tang Lab neural model benchmark.
10
 
11
- The dashboard compares 23 methods across five datasets, with sortable task
12
  performance, robustness, cross-session alignment, neuron and trial influence,
13
  compute cost, and 3D latent-space views.
14
 
 
8
 
9
  Interactive dashboard for the Tang Lab neural model benchmark.
10
 
11
+ The dashboard compares 23 methods across five datasets, with sortable decoding
12
  performance, robustness, cross-session alignment, neuron and trial influence,
13
  compute cost, and 3D latent-space views.
14
 
app.py CHANGED
@@ -104,8 +104,8 @@ TABLE_LABELS = {
104
  "method": "Method",
105
  "family": "Family",
106
  "hardware": "Hardware",
107
- "task_score": "Task performance",
108
- "score": "Task performance",
109
  "robustness_auc": "Robustness AUC",
110
  "alignment_score": "Cross-session alignment",
111
  "training_time_sec": "Training time (s)",
@@ -430,6 +430,24 @@ def metric_text(value: object) -> str:
430
  return str(value)
431
 
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  def value_text(value: object, digits: int = 3) -> str:
434
  if value is None or pd.isna(value):
435
  return "Not available"
@@ -623,7 +641,7 @@ def leaderboard_summary(dataset: str, table_df: pd.DataFrame) -> list[html.Div]:
623
  def performance_heatmap(dataset: str, models: list[str] | None) -> go.Figure:
624
  df = filter_models(prediction, models)
625
  if df.empty:
626
- return empty_figure("No task performance results are available.")
627
  df = add_method_columns(df)
628
  df["score"] = pd.to_numeric(df["score"], errors="coerce")
629
  df["dataset_label"] = df["dataset"].map(DATASET_LABELS).fillna(df["dataset"])
@@ -659,15 +677,16 @@ def performance_heatmap(dataset: str, models: list[str] | None) -> go.Figure:
659
  hovertemplate="Method=%{y}<br>Dataset=%{x}<br>Score=%{z:.4f}<extra></extra>",
660
  )
661
  )
662
- fig.update_layout(title=f"Task performance matrix, sorted by {selected_label}")
663
  return fig_layout(fig, height=max(430, 26 * len(pivot.index) + 150))
664
 
665
 
666
  def ranking_figure(dataset: str, table_df: pd.DataFrame) -> go.Figure:
667
  rank_df = table_df.dropna(subset=["task_score"]).sort_values("task_score", ascending=True)
668
  if rank_df.empty:
669
- return empty_figure(f"No task performance results are available for {DATASET_LABELS.get(dataset, dataset)}.")
670
  metric = metric_text(rank_df["metric"].dropna().iloc[0]) if rank_df["metric"].notna().any() else "score"
 
671
  fig = go.Figure(
672
  go.Bar(
673
  x=rank_df["task_score"],
@@ -677,8 +696,8 @@ def ranking_figure(dataset: str, table_df: pd.DataFrame) -> go.Figure:
677
  hovertemplate="Method=%{y}<br>Score=%{x:.4f}<extra></extra>",
678
  )
679
  )
680
- fig.update_layout(title=f"{DATASET_LABELS.get(dataset, dataset)} ranking")
681
- fig.update_xaxes(title=metric)
682
  fig.update_yaxes(title="")
683
  return fig_layout(fig, height=max(420, 25 * len(rank_df) + 150))
684
 
@@ -983,31 +1002,44 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
983
  return fig
984
 
985
 
986
- def robustness_figure(dataset: str, models: list[str] | None) -> go.Figure:
987
  df = filter_models(present_rows(robustness), models)
988
  df = df[df["dataset"].astype(str) == str(dataset)].copy() if not df.empty else df
989
  if df.empty:
990
  return empty_figure("No robustness results are available for this selection.")
991
  df = add_method_columns(df).sort_values("model_order")
 
 
 
 
992
  fig = go.Figure()
 
993
  for idx, row in enumerate(df.itertuples()):
994
  xs = parse_float_list(row.noise_levels)
995
  ys = parse_float_list(row.scores)
996
  if xs and len(xs) == len(ys):
 
997
  fig.add_trace(
998
  go.Scatter(
999
  x=xs,
1000
  y=ys,
1001
  mode="lines+markers",
1002
  name=row.method,
1003
- line=dict(color=CATEGORICAL_PALETTE[idx % len(CATEGORICAL_PALETTE)], width=2),
1004
- marker=dict(size=5),
 
 
 
 
1005
  hovertemplate="Noise=%{x:.2f}<br>Score=%{y:.4f}<extra></extra>",
1006
  )
1007
  )
 
1008
  fig.update_layout(title=f"{DATASET_LABELS.get(dataset, dataset)} robustness curves")
 
1009
  fig.update_xaxes(title="Noise fraction")
1010
- fig.update_yaxes(title=metric_text(df["metric"].dropna().iloc[0]) if df["metric"].notna().any() else "score")
 
1011
  return fig_layout(fig, height=520)
1012
 
1013
 
@@ -1177,7 +1209,7 @@ app.layout = html.Div(
1177
  html.Div("Tang Lab", className="eyebrow"),
1178
  html.H1("Neural Model Benchmark"),
1179
  html.P(
1180
- "Explore task performance, robustness, latent alignment, neuron and trial influence, and compute cost across 23 benchmarked methods.",
1181
  className="lede",
1182
  ),
1183
  ],
@@ -1186,7 +1218,7 @@ app.layout = html.Div(
1186
  html.Div(
1187
  [
1188
  metric_card("Methods", str(len(MODELS)), "Benchmarked in the paper"),
1189
- metric_card("Datasets", str(len(DATASETS)), "Selectable below"),
1190
  ],
1191
  className="hero-metrics",
1192
  ),
@@ -1207,19 +1239,6 @@ app.layout = html.Div(
1207
  ],
1208
  className="control",
1209
  ),
1210
- html.Div(
1211
- [
1212
- html.Label("Method filter"),
1213
- dcc.Dropdown(
1214
- id="model-filter",
1215
- options=[{"label": model_label(model), "value": model} for model in MODELS],
1216
- value=[],
1217
- multi=True,
1218
- placeholder="All methods",
1219
- ),
1220
- ],
1221
- className="control control-wide",
1222
- ),
1223
  ],
1224
  className="toolbar",
1225
  ),
@@ -1248,7 +1267,7 @@ app.layout = html.Div(
1248
  className="leaderboard-grid",
1249
  ),
1250
  dcc.Graph(id="performance-heatmap", config={"displayModeBar": False}),
1251
- subtitle="Sorted by the selected dataset. Regression datasets use R2; classification datasets use accuracy. Higher is better.",
1252
  className="leaderboard-panel",
1253
  )
1254
  ],
@@ -1305,9 +1324,9 @@ app.layout = html.Div(
1305
  children=[
1306
  panel(
1307
  "Robustness",
1308
- dcc.Graph(id="robustness-curve", config={"displayModeBar": False}),
1309
  details_table("View robustness rows", dataframe_table("robustness-table")),
1310
- subtitle="Curves show how task performance changes as neural count noise increases.",
1311
  )
1312
  ],
1313
  ),
@@ -1378,11 +1397,10 @@ app.layout = html.Div(
1378
  Output("dataset-ranking", "figure"),
1379
  Output("performance-heatmap", "figure"),
1380
  Input("dataset-filter", "value"),
1381
- Input("model-filter", "value"),
1382
  Input("leaderboard-table", "sort_by"),
1383
  )
1384
- def update_leaderboard(dataset: str, models: list[str] | None, sort_by: list[dict] | None):
1385
- df = leaderboard_frame(dataset, models)
1386
  visible_cols = [
1387
  "rank",
1388
  "method",
@@ -1391,15 +1409,17 @@ def update_leaderboard(dataset: str, models: list[str] | None, sort_by: list[dic
1391
  "alignment_score",
1392
  "training_time_sec",
1393
  "peak_ram_gb",
 
1394
  ]
1395
  sorted_df = sort_table(df, sort_by, [("task_score", False), ("model_order", True)])
1396
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
 
1397
  return (
1398
  leaderboard_summary(dataset, df),
1399
- column_defs([c for c in visible_cols if c in table_df.columns]),
1400
  records(table_df),
1401
  ranking_figure(dataset, df),
1402
- performance_heatmap(dataset, models),
1403
  )
1404
 
1405
 
@@ -1410,12 +1430,11 @@ def update_leaderboard(dataset: str, models: list[str] | None, sort_by: list[dic
1410
  Output("consistency-bars", "figure"),
1411
  Output("consistency-heatmap", "figure"),
1412
  Input("dataset-filter", "value"),
1413
- Input("model-filter", "value"),
1414
  Input("consistency-table", "active_cell"),
1415
  Input("consistency-table", "sort_by"),
1416
  )
1417
- def update_consistency(dataset: str, models: list[str] | None, active_cell: dict | None, sort_by: list[dict] | None):
1418
- df = consistency_frame(dataset, models)
1419
  model = selected_consistency_model(df, active_cell)
1420
  visible_cols = ["rank", "method", "alignment_score", "n_sessions", "latent_dim", "n_pairwise"]
1421
  sorted_df = sort_table(df, sort_by, [("alignment_score", False), ("model_order", True)])
@@ -1425,7 +1444,7 @@ def update_consistency(dataset: str, models: list[str] | None, active_cell: dict
1425
  records(table_df),
1426
  latent_space_figure(dataset, model),
1427
  consistency_bar_figure(dataset, df),
1428
- consistency_heatmap(models),
1429
  )
1430
 
1431
 
@@ -1434,12 +1453,12 @@ def update_consistency(dataset: str, models: list[str] | None, active_cell: dict
1434
  Output("robustness-table", "columns"),
1435
  Output("robustness-table", "data"),
1436
  Input("dataset-filter", "value"),
1437
- Input("model-filter", "value"),
1438
  )
1439
- def update_robustness(dataset: str, models: list[str] | None):
1440
- table = robustness_table_frame(dataset, models)
1441
  return (
1442
- robustness_figure(dataset, models),
1443
  column_defs(table.columns),
1444
  records(table),
1445
  )
@@ -1451,10 +1470,9 @@ def update_robustness(dataset: str, models: list[str] | None):
1451
  Output("compute-table", "columns"),
1452
  Output("compute-table", "data"),
1453
  Input("dataset-filter", "value"),
1454
- Input("model-filter", "value"),
1455
  )
1456
- def update_compute(dataset: str, models: list[str] | None):
1457
- scatter, memory, table = compute_figures(dataset, models)
1458
  return scatter, memory, column_defs(table.columns), records(table)
1459
 
1460
 
@@ -1464,20 +1482,19 @@ def update_compute(dataset: str, models: list[str] | None):
1464
  Output("influence-table", "columns"),
1465
  Output("influence-table", "data"),
1466
  Input("dataset-filter", "value"),
1467
- Input("model-filter", "value"),
1468
  )
1469
- def update_influence(dataset: str, models: list[str] | None):
1470
- neuron_fig, trial_fig, table = influence_figures(dataset, models)
1471
  return neuron_fig, trial_fig, column_defs(table.columns), records(table)
1472
 
1473
 
1474
  @app.callback(
1475
  Output("methods-table", "columns"),
1476
  Output("methods-table", "data"),
1477
- Input("model-filter", "value"),
1478
  )
1479
- def update_methods(models: list[str] | None):
1480
- table = methods_frame(models)
1481
  return column_defs(table.columns), records(table)
1482
 
1483
 
 
104
  "method": "Method",
105
  "family": "Family",
106
  "hardware": "Hardware",
107
+ "task_score": "Decoding score",
108
+ "score": "Decoding score",
109
  "robustness_auc": "Robustness AUC",
110
  "alignment_score": "Cross-session alignment",
111
  "training_time_sec": "Training time (s)",
 
430
  return str(value)
431
 
432
 
433
+ def decoding_label(metric: object) -> str:
434
+ text = metric_text(metric)
435
+ if text.lower() == "r2":
436
+ return "Decoding R2"
437
+ if text.lower() == "accuracy":
438
+ return "Decoding accuracy"
439
+ return f"Decoding {text}"
440
+
441
+
442
+ def leaderboard_columns(columns: Iterable[str], metric: object) -> list[dict]:
443
+ defs = column_defs(columns)
444
+ label = decoding_label(metric)
445
+ for item in defs:
446
+ if item["id"] == "task_score":
447
+ item["name"] = label
448
+ return defs
449
+
450
+
451
  def value_text(value: object, digits: int = 3) -> str:
452
  if value is None or pd.isna(value):
453
  return "Not available"
 
641
  def performance_heatmap(dataset: str, models: list[str] | None) -> go.Figure:
642
  df = filter_models(prediction, models)
643
  if df.empty:
644
+ return empty_figure("No decoding results are available.")
645
  df = add_method_columns(df)
646
  df["score"] = pd.to_numeric(df["score"], errors="coerce")
647
  df["dataset_label"] = df["dataset"].map(DATASET_LABELS).fillna(df["dataset"])
 
677
  hovertemplate="Method=%{y}<br>Dataset=%{x}<br>Score=%{z:.4f}<extra></extra>",
678
  )
679
  )
680
+ fig.update_layout(title=f"Decoding score matrix, sorted by {selected_label}")
681
  return fig_layout(fig, height=max(430, 26 * len(pivot.index) + 150))
682
 
683
 
684
  def ranking_figure(dataset: str, table_df: pd.DataFrame) -> go.Figure:
685
  rank_df = table_df.dropna(subset=["task_score"]).sort_values("task_score", ascending=True)
686
  if rank_df.empty:
687
+ return empty_figure(f"No decoding results are available for {DATASET_LABELS.get(dataset, dataset)}.")
688
  metric = metric_text(rank_df["metric"].dropna().iloc[0]) if rank_df["metric"].notna().any() else "score"
689
+ label = decoding_label(metric)
690
  fig = go.Figure(
691
  go.Bar(
692
  x=rank_df["task_score"],
 
696
  hovertemplate="Method=%{y}<br>Score=%{x:.4f}<extra></extra>",
697
  )
698
  )
699
+ fig.update_layout(title=f"{DATASET_LABELS.get(dataset, dataset)} {label} ranking")
700
+ fig.update_xaxes(title=label)
701
  fig.update_yaxes(title="")
702
  return fig_layout(fig, height=max(420, 25 * len(rank_df) + 150))
703
 
 
1002
  return fig
1003
 
1004
 
1005
+ def robustness_figure(dataset: str, models: list[str] | None, hover_data: dict | None = None) -> go.Figure:
1006
  df = filter_models(present_rows(robustness), models)
1007
  df = df[df["dataset"].astype(str) == str(dataset)].copy() if not df.empty else df
1008
  if df.empty:
1009
  return empty_figure("No robustness results are available for this selection.")
1010
  df = add_method_columns(df).sort_values("model_order")
1011
+ highlighted_trace = None
1012
+ if hover_data and hover_data.get("points"):
1013
+ highlighted_trace = hover_data["points"][0].get("curveNumber")
1014
+
1015
  fig = go.Figure()
1016
+ trace_idx = 0
1017
  for idx, row in enumerate(df.itertuples()):
1018
  xs = parse_float_list(row.noise_levels)
1019
  ys = parse_float_list(row.scores)
1020
  if xs and len(xs) == len(ys):
1021
+ is_highlighted = highlighted_trace is None or highlighted_trace == trace_idx
1022
  fig.add_trace(
1023
  go.Scatter(
1024
  x=xs,
1025
  y=ys,
1026
  mode="lines+markers",
1027
  name=row.method,
1028
+ opacity=0.92 if is_highlighted else 0.16,
1029
+ line=dict(
1030
+ color=CATEGORICAL_PALETTE[idx % len(CATEGORICAL_PALETTE)],
1031
+ width=4 if highlighted_trace == trace_idx else 2,
1032
+ ),
1033
+ marker=dict(size=8 if highlighted_trace == trace_idx else 5),
1034
  hovertemplate="Noise=%{x:.2f}<br>Score=%{y:.4f}<extra></extra>",
1035
  )
1036
  )
1037
+ trace_idx += 1
1038
  fig.update_layout(title=f"{DATASET_LABELS.get(dataset, dataset)} robustness curves")
1039
+ fig.update_layout(hovermode="closest")
1040
  fig.update_xaxes(title="Noise fraction")
1041
+ metric = metric_text(df["metric"].dropna().iloc[0]) if df["metric"].notna().any() else "score"
1042
+ fig.update_yaxes(title=decoding_label(metric))
1043
  return fig_layout(fig, height=520)
1044
 
1045
 
 
1209
  html.Div("Tang Lab", className="eyebrow"),
1210
  html.H1("Neural Model Benchmark"),
1211
  html.P(
1212
+ "Explore decoding performance, robustness, latent alignment, neuron and trial influence, and compute cost across 23 benchmarked methods.",
1213
  className="lede",
1214
  ),
1215
  ],
 
1218
  html.Div(
1219
  [
1220
  metric_card("Methods", str(len(MODELS)), "Benchmarked in the paper"),
1221
+ metric_card("Datasets", str(len(DATASETS)), "Select one below"),
1222
  ],
1223
  className="hero-metrics",
1224
  ),
 
1239
  ],
1240
  className="control",
1241
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
1242
  ],
1243
  className="toolbar",
1244
  ),
 
1267
  className="leaderboard-grid",
1268
  ),
1269
  dcc.Graph(id="performance-heatmap", config={"displayModeBar": False}),
1270
+ subtitle="Sorted by the selected dataset. Regression datasets use decoding R2; classification datasets use decoding accuracy. Higher is better.",
1271
  className="leaderboard-panel",
1272
  )
1273
  ],
 
1324
  children=[
1325
  panel(
1326
  "Robustness",
1327
+ dcc.Graph(id="robustness-curve", clear_on_unhover=True, config={"displayModeBar": False}),
1328
  details_table("View robustness rows", dataframe_table("robustness-table")),
1329
+ subtitle="Hover a method to highlight its curve. Curves show how decoding performance changes as neural count noise increases.",
1330
  )
1331
  ],
1332
  ),
 
1397
  Output("dataset-ranking", "figure"),
1398
  Output("performance-heatmap", "figure"),
1399
  Input("dataset-filter", "value"),
 
1400
  Input("leaderboard-table", "sort_by"),
1401
  )
1402
+ def update_leaderboard(dataset: str, sort_by: list[dict] | None):
1403
+ df = leaderboard_frame(dataset, None)
1404
  visible_cols = [
1405
  "rank",
1406
  "method",
 
1409
  "alignment_score",
1410
  "training_time_sec",
1411
  "peak_ram_gb",
1412
+ "peak_vram_gb",
1413
  ]
1414
  sorted_df = sort_table(df, sort_by, [("task_score", False), ("model_order", True)])
1415
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
1416
+ metric = df["metric"].dropna().iloc[0] if df["metric"].notna().any() else "score"
1417
  return (
1418
  leaderboard_summary(dataset, df),
1419
+ leaderboard_columns([c for c in visible_cols if c in table_df.columns], metric),
1420
  records(table_df),
1421
  ranking_figure(dataset, df),
1422
+ performance_heatmap(dataset, None),
1423
  )
1424
 
1425
 
 
1430
  Output("consistency-bars", "figure"),
1431
  Output("consistency-heatmap", "figure"),
1432
  Input("dataset-filter", "value"),
 
1433
  Input("consistency-table", "active_cell"),
1434
  Input("consistency-table", "sort_by"),
1435
  )
1436
+ def update_consistency(dataset: str, active_cell: dict | None, sort_by: list[dict] | None):
1437
+ df = consistency_frame(dataset, None)
1438
  model = selected_consistency_model(df, active_cell)
1439
  visible_cols = ["rank", "method", "alignment_score", "n_sessions", "latent_dim", "n_pairwise"]
1440
  sorted_df = sort_table(df, sort_by, [("alignment_score", False), ("model_order", True)])
 
1444
  records(table_df),
1445
  latent_space_figure(dataset, model),
1446
  consistency_bar_figure(dataset, df),
1447
+ consistency_heatmap(None),
1448
  )
1449
 
1450
 
 
1453
  Output("robustness-table", "columns"),
1454
  Output("robustness-table", "data"),
1455
  Input("dataset-filter", "value"),
1456
+ Input("robustness-curve", "hoverData"),
1457
  )
1458
+ def update_robustness(dataset: str, hover_data: dict | None):
1459
+ table = robustness_table_frame(dataset, None)
1460
  return (
1461
+ robustness_figure(dataset, None, hover_data),
1462
  column_defs(table.columns),
1463
  records(table),
1464
  )
 
1470
  Output("compute-table", "columns"),
1471
  Output("compute-table", "data"),
1472
  Input("dataset-filter", "value"),
 
1473
  )
1474
+ def update_compute(dataset: str):
1475
+ scatter, memory, table = compute_figures(dataset, None)
1476
  return scatter, memory, column_defs(table.columns), records(table)
1477
 
1478
 
 
1482
  Output("influence-table", "columns"),
1483
  Output("influence-table", "data"),
1484
  Input("dataset-filter", "value"),
 
1485
  )
1486
+ def update_influence(dataset: str):
1487
+ neuron_fig, trial_fig, table = influence_figures(dataset, None)
1488
  return neuron_fig, trial_fig, column_defs(table.columns), records(table)
1489
 
1490
 
1491
  @app.callback(
1492
  Output("methods-table", "columns"),
1493
  Output("methods-table", "data"),
1494
+ Input("dataset-filter", "value"),
1495
  )
1496
+ def update_methods(_dataset: str):
1497
+ table = methods_frame(None)
1498
  return column_defs(table.columns), records(table)
1499
 
1500
 
assets/styles.css CHANGED
@@ -116,7 +116,7 @@ h2 {
116
  top: 0;
117
  z-index: 10;
118
  display: grid;
119
- grid-template-columns: minmax(230px, 310px) minmax(360px, 1fr);
120
  gap: 16px;
121
  margin-top: 14px;
122
  margin-bottom: 14px;
@@ -152,6 +152,10 @@ h2 {
152
  }
153
 
154
  .tab {
 
 
 
 
155
  padding: 11px 18px !important;
156
  border: 0 !important;
157
  border-bottom: 2px solid transparent !important;
 
116
  top: 0;
117
  z-index: 10;
118
  display: grid;
119
+ grid-template-columns: minmax(230px, 310px);
120
  gap: 16px;
121
  margin-top: 14px;
122
  margin-bottom: 14px;
 
152
  }
153
 
154
  .tab {
155
+ display: flex !important;
156
+ align-items: center !important;
157
+ justify-content: center !important;
158
+ min-height: 44px;
159
  padding: 11px 18px !important;
160
  border: 0 !important;
161
  border-bottom: 2px solid transparent !important;