rounakbende commited on
Commit
9472758
·
1 Parent(s): e08b34a

Revert "Add Bradley-Terry rankings tab (#26)"

Browse files

This reverts commit e08b34a37838e80b978b0620ee54c7e0d7f86463.

Files changed (3) hide show
  1. app.py +132 -111
  2. requirements.txt +0 -2
  3. src/rankings.py +0 -152
app.py CHANGED
@@ -42,7 +42,6 @@ def patch_gradio_leaderboard():
42
 
43
  patch_gradio_leaderboard()
44
 
45
- import pandas as pd
46
  import gradio as gr
47
  from apscheduler.schedulers.background import BackgroundScheduler
48
  from gradio_leaderboard import ColumnFilter, Leaderboard, SelectColumns
@@ -51,25 +50,58 @@ from huggingface_hub import HfApi
51
  from src.charts import (
52
  clean_markdown_link,
53
  create_leaderboard_benchmark_plot,
54
- create_score_vs_cost_plot,
55
  )
56
  from src.display.text_blocks import (
57
  HOW_TO_USE_TEXT,
58
  INTRODUCTION_TEXT,
59
  LLM_BENCHMARKS_TEXT,
60
  )
61
- from src.leaderboard import get_benchmark_names, get_benchmark_run_df, get_score_vs_cost_df
62
- from src.rankings import load_and_rank, RANK_BY_OPTIONS
 
 
 
 
 
 
63
 
64
  REPO_ID = "taagarwa/coding-agent-leaderboard"
65
  TOKEN = os.environ.get("HF_TOKEN")
66
  API = HfApi(token=TOKEN)
67
  COLOR_BY_CHOICES = ["Model", "Harness"]
68
- COLOR_PALETTE_CHOICES = ["Citrus", "Okabe-Ito", "High contrast", "Rainbow"]
 
 
 
 
 
 
 
 
 
 
69
  DEFAULT_COLOR_PALETTE = "Citrus"
70
  PLOT_BACKGROUND_CHOICES = ["Dark", "White"]
71
  DEFAULT_PLOT_BACKGROUND = "Dark"
72
- FORCE_DARK_MODE_HEAD = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  <script>
74
  (() => {
75
  const url = new URL(window.location.href);
@@ -80,6 +112,8 @@ FORCE_DARK_MODE_HEAD = """
80
  })();
81
  </script>
82
  """
 
 
83
 
84
 
85
  def restart_space():
@@ -89,7 +123,7 @@ def restart_space():
89
  BENCHMARK_NAMES = get_benchmark_names()
90
  DEFAULT_BENCHMARK = BENCHMARK_NAMES[0] if BENCHMARK_NAMES else None
91
  BENCHMARK_RUN_DF = get_benchmark_run_df()
92
- SCORE_VS_COST_DF = get_score_vs_cost_df()
93
 
94
 
95
  def render_leaderboard_benchmark_plot(
@@ -107,19 +141,39 @@ def render_leaderboard_benchmark_plot(
107
  )
108
 
109
 
110
- def render_score_vs_cost_plot(
 
111
  benchmark_name,
 
112
  color_by,
 
 
 
113
  color_palette=DEFAULT_COLOR_PALETTE,
114
  plot_background=DEFAULT_PLOT_BACKGROUND,
115
  ):
116
- return create_score_vs_cost_plot(
117
- SCORE_VS_COST_DF,
118
  benchmark_name=benchmark_name,
 
 
 
 
 
 
 
 
 
 
 
 
119
  color_by=color_by,
 
 
 
120
  palette_name=color_palette,
121
  background_name=plot_background,
122
  )
 
123
 
124
 
125
  def build_header_html(df):
@@ -244,128 +298,95 @@ with demo:
244
  outputs=plot,
245
  )
246
 
247
- with gr.Tab("💰 Cost vs Performance"):
248
- cost_benchmark = gr.Dropdown(
249
- choices=BENCHMARK_NAMES,
250
- value=DEFAULT_BENCHMARK,
251
- label="Benchmark",
 
 
 
252
  )
253
  with gr.Row():
254
- cost_color_by = gr.Radio(
255
- choices=COLOR_BY_CHOICES,
 
 
 
 
 
 
 
 
 
 
256
  value="Model",
257
  label="Color by",
258
- elem_classes="color-control",
259
  )
260
- cost_palette = gr.Dropdown(
 
 
 
 
261
  choices=COLOR_PALETTE_CHOICES,
262
  value=DEFAULT_COLOR_PALETTE,
263
  label="Color palette",
264
- elem_classes="color-control",
265
  )
266
- cost_background = gr.Dropdown(
267
  choices=PLOT_BACKGROUND_CHOICES,
268
  value=DEFAULT_PLOT_BACKGROUND,
269
  label="Image background",
270
- elem_classes="color-control",
271
  )
272
- score_vs_cost_plot = gr.Plot(
273
- value=render_score_vs_cost_plot(
274
- DEFAULT_BENCHMARK,
275
- "Model",
276
- DEFAULT_COLOR_PALETTE,
277
- DEFAULT_PLOT_BACKGROUND,
278
- ),
 
 
 
 
 
 
 
279
  show_label=False,
280
  elem_classes="responsive-plot",
281
  )
282
- cost_benchmark.change(
283
- fn=render_score_vs_cost_plot,
284
- inputs=[cost_benchmark, cost_color_by, cost_palette, cost_background],
285
- outputs=score_vs_cost_plot,
286
- )
287
- cost_color_by.change(
288
- fn=render_score_vs_cost_plot,
289
- inputs=[cost_benchmark, cost_color_by, cost_palette, cost_background],
290
- outputs=score_vs_cost_plot,
291
- )
292
- cost_palette.change(
293
- fn=render_score_vs_cost_plot,
294
- inputs=[cost_benchmark, cost_color_by, cost_palette, cost_background],
295
- outputs=score_vs_cost_plot,
296
- )
297
- cost_background.change(
298
- fn=render_score_vs_cost_plot,
299
- inputs=[cost_benchmark, cost_color_by, cost_palette, cost_background],
300
- outputs=score_vs_cost_plot,
301
  )
302
 
303
- with gr.Tab("📊 Rankings"):
304
- gr.Markdown("### Bradley-Terry Paired Comparison Rankings")
305
- gr.Markdown("Rankings computed across all benchmarks using [paired comparison methods](https://github.com/erikerlandson/paired-comparison-ranking). Handles missing data and inconsistent orderings.")
306
-
307
- rank_csv = pd.read_csv("results.csv")
308
- rank_csv = rank_csv.dropna(subset=["metrics.score"])
309
- rank_model_choices = sorted(rank_csv["model.name"].unique().tolist())
310
- rank_harness_choices = sorted(rank_csv["harness.name"].unique().tolist())
311
- rank_benchmark_choices = sorted(rank_csv["benchmark.name"].unique().tolist())
312
-
313
- with gr.Row():
314
- rank_by = gr.Dropdown(
315
- choices=list(RANK_BY_OPTIONS.keys()),
316
- value="Benchmark Score",
317
- label="Rank by",
318
- )
319
- rank_oss_models = gr.Checkbox(value=False, label="Open models only")
320
- rank_oss_harnesses = gr.Checkbox(value=False, label="Open harnesses only")
321
- with gr.Row():
322
- rank_benchmark_filter = gr.CheckboxGroup(
323
- choices=rank_benchmark_choices,
324
- value=rank_benchmark_choices,
325
- label="Benchmarks",
326
- )
327
- with gr.Row():
328
- rank_model_filter = gr.CheckboxGroup(
329
- choices=rank_model_choices,
330
- value=rank_model_choices,
331
- label="Models",
332
- )
333
- with gr.Row():
334
- rank_harness_filter = gr.CheckboxGroup(
335
- choices=rank_harness_choices,
336
- value=rank_harness_choices,
337
- label="Harnesses",
338
- )
339
-
340
- harness_df_init, model_df_init, pair_df_init = load_and_rank("results.csv")
341
-
342
- gr.Markdown("#### Harness Rankings")
343
- harness_table = gr.Dataframe(value=harness_df_init, interactive=False)
344
- gr.Markdown("#### Model Rankings")
345
- model_table = gr.Dataframe(value=model_df_init, interactive=False)
346
- gr.Markdown("#### (Model, Harness) Rankings")
347
- pair_table = gr.Dataframe(value=pair_df_init, interactive=False)
348
-
349
- def update_rankings(rank_by_val, oss_models, oss_harnesses, benchmarks, models, harnesses):
350
- h, m, p = load_and_rank(
351
- "results.csv",
352
- open_models_only=oss_models,
353
- open_harnesses_only=oss_harnesses,
354
- benchmarks=benchmarks,
355
- models=models,
356
- harnesses=harnesses,
357
- rank_by=rank_by_val,
358
- )
359
- return h, m, p
360
-
361
- ranking_inputs = [rank_by, rank_oss_models, rank_oss_harnesses, rank_benchmark_filter, rank_model_filter, rank_harness_filter]
362
- for control in ranking_inputs:
363
  control.change(
364
- fn=update_rankings,
365
- inputs=ranking_inputs,
366
- outputs=[harness_table, model_table, pair_table],
367
  )
368
 
 
 
 
 
 
 
 
 
 
369
  with gr.Tab("🏃 Benchmark Runs"):
370
  benchmark_runs = init_benchmark_runs(BENCHMARK_RUN_DF)
371
 
 
42
 
43
  patch_gradio_leaderboard()
44
 
 
45
  import gradio as gr
46
  from apscheduler.schedulers.background import BackgroundScheduler
47
  from gradio_leaderboard import ColumnFilter, Leaderboard, SelectColumns
 
50
  from src.charts import (
51
  clean_markdown_link,
52
  create_leaderboard_benchmark_plot,
53
+ create_performance_vs_resource_plot,
54
  )
55
  from src.display.text_blocks import (
56
  HOW_TO_USE_TEXT,
57
  INTRODUCTION_TEXT,
58
  LLM_BENCHMARKS_TEXT,
59
  )
60
+ from src.leaderboard import (
61
+ EFFICIENCY_RESOURCE_METRICS,
62
+ get_analysis_df,
63
+ get_benchmark_names,
64
+ get_benchmark_run_df,
65
+ get_efficiency_df,
66
+ get_token_efficiency_table_df,
67
+ )
68
 
69
  REPO_ID = "taagarwa/coding-agent-leaderboard"
70
  TOKEN = os.environ.get("HF_TOKEN")
71
  API = HfApi(token=TOKEN)
72
  COLOR_BY_CHOICES = ["Model", "Harness"]
73
+ EFFICIENCY_COLOR_BY_CHOICES = ["Model", "Harness"]
74
+ COLOR_PALETTE_CHOICES = [
75
+ "Citrus",
76
+ "Okabe-Ito",
77
+ "High contrast",
78
+ "Rainbow",
79
+ "Grayscale",
80
+ "Viridis",
81
+ "Plasma",
82
+ "Cividis",
83
+ ]
84
  DEFAULT_COLOR_PALETTE = "Citrus"
85
  PLOT_BACKGROUND_CHOICES = ["Dark", "White"]
86
  DEFAULT_PLOT_BACKGROUND = "Dark"
87
+ RESPONSIVE_PLOT_MIN_HEIGHT_PX = 420
88
+ RESPONSIVE_PLOT_CSS = f"""
89
+ <style>
90
+ .responsive-plot {{
91
+ overflow-x: auto;
92
+ width: 100%;
93
+ min-height: {RESPONSIVE_PLOT_MIN_HEIGHT_PX}px;
94
+ }}
95
+ .responsive-plot .plot-container,
96
+ .responsive-plot .js-plotly-plot,
97
+ .responsive-plot .plotly-graph-div {{
98
+ width: 100% !important;
99
+ min-height: {RESPONSIVE_PLOT_MIN_HEIGHT_PX}px;
100
+ }}
101
+ </style>
102
+ """
103
+ FORCE_DARK_MODE_HEAD = (
104
+ """
105
  <script>
106
  (() => {
107
  const url = new URL(window.location.href);
 
112
  })();
113
  </script>
114
  """
115
+ + RESPONSIVE_PLOT_CSS
116
+ )
117
 
118
 
119
  def restart_space():
 
123
  BENCHMARK_NAMES = get_benchmark_names()
124
  DEFAULT_BENCHMARK = BENCHMARK_NAMES[0] if BENCHMARK_NAMES else None
125
  BENCHMARK_RUN_DF = get_benchmark_run_df()
126
+ ANALYSIS_DF = get_analysis_df()
127
 
128
 
129
  def render_leaderboard_benchmark_plot(
 
141
  )
142
 
143
 
144
+
145
+ def render_efficiency(
146
  benchmark_name,
147
+ token_metric,
148
  color_by,
149
+ x_scale,
150
+ show_pareto_frontier,
151
+ show_labels,
152
  color_palette=DEFAULT_COLOR_PALETTE,
153
  plot_background=DEFAULT_PLOT_BACKGROUND,
154
  ):
155
+ plot_df = get_efficiency_df(
 
156
  benchmark_name=benchmark_name,
157
+ resource_metric=token_metric,
158
+ analysis_df=ANALYSIS_DF,
159
+ )
160
+ table_df = get_token_efficiency_table_df(
161
+ benchmark_name=benchmark_name,
162
+ analysis_df=ANALYSIS_DF,
163
+ )
164
+ exclusion_count = plot_df.attrs.get("exclusion_count", 0)
165
+ note = f"{exclusion_count} runs excluded for this benchmark because {token_metric.lower()} was missing or non-positive."
166
+ figure = create_performance_vs_resource_plot(
167
+ plot_df,
168
+ resource_metric=token_metric,
169
  color_by=color_by,
170
+ x_scale=x_scale,
171
+ show_pareto_frontier=show_pareto_frontier,
172
+ show_labels=show_labels,
173
  palette_name=color_palette,
174
  background_name=plot_background,
175
  )
176
+ return figure, table_df, note
177
 
178
 
179
  def build_header_html(df):
 
298
  outputs=plot,
299
  )
300
 
301
+ with gr.Tab(" Efficiency") as efficiency_tab:
302
+ gr.Markdown(
303
+ "### Efficiency\n"
304
+ "Compare score with resource use within one benchmark. The x-axis can show total tokens, "
305
+ "cost per task, or agent execution time per task. The dashed Pareto frontier connects runs "
306
+ "for which no other displayed run uses an equal or lower amount of the selected resource "
307
+ "while achieving an equal or higher score. Tokens Per Solved Task remains in the ranking "
308
+ "table as a reference metric."
309
  )
310
  with gr.Row():
311
+ efficiency_benchmark = gr.Dropdown(
312
+ choices=BENCHMARK_NAMES,
313
+ value=BENCHMARK_NAMES[0] if BENCHMARK_NAMES else None,
314
+ label="Benchmark",
315
+ )
316
+ efficiency_metric = gr.Dropdown(
317
+ choices=list(EFFICIENCY_RESOURCE_METRICS),
318
+ value="Total tokens",
319
+ label="Resource metric",
320
+ )
321
+ efficiency_color_by = gr.Radio(
322
+ choices=EFFICIENCY_COLOR_BY_CHOICES,
323
  value="Model",
324
  label="Color by",
 
325
  )
326
+ efficiency_scale = gr.Radio(choices=["Log", "Linear"], value="Log", label="X-axis scale")
327
+ with gr.Row():
328
+ efficiency_pareto = gr.Checkbox(value=True, label="Show Pareto frontier")
329
+ efficiency_labels = gr.Checkbox(value=False, label="Show point labels")
330
+ efficiency_palette = gr.Dropdown(
331
  choices=COLOR_PALETTE_CHOICES,
332
  value=DEFAULT_COLOR_PALETTE,
333
  label="Color palette",
 
334
  )
335
+ efficiency_background = gr.Dropdown(
336
  choices=PLOT_BACKGROUND_CHOICES,
337
  value=DEFAULT_PLOT_BACKGROUND,
338
  label="Image background",
 
339
  )
340
+
341
+ initial_efficiency = render_efficiency(
342
+ BENCHMARK_NAMES[0] if BENCHMARK_NAMES else None,
343
+ "Total tokens",
344
+ "Model",
345
+ "Log",
346
+ True,
347
+ False,
348
+ DEFAULT_COLOR_PALETTE,
349
+ DEFAULT_PLOT_BACKGROUND,
350
+ )
351
+ efficiency_note = gr.Markdown(initial_efficiency[2])
352
+ efficiency_plot = gr.Plot(
353
+ value=initial_efficiency[0],
354
  show_label=False,
355
  elem_classes="responsive-plot",
356
  )
357
+ gr.Markdown("#### Efficiency ranking")
358
+ efficiency_table = gr.Dataframe(
359
+ value=initial_efficiency[1],
360
+ interactive=False,
361
+ show_label=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  )
363
 
364
+ efficiency_controls = [
365
+ efficiency_benchmark,
366
+ efficiency_metric,
367
+ efficiency_color_by,
368
+ efficiency_scale,
369
+ efficiency_pareto,
370
+ efficiency_labels,
371
+ efficiency_palette,
372
+ efficiency_background,
373
+ ]
374
+ for control in efficiency_controls:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  control.change(
376
+ fn=render_efficiency,
377
+ inputs=efficiency_controls,
378
+ outputs=[efficiency_plot, efficiency_table, efficiency_note],
379
  )
380
 
381
+ # Gradio initially lays out hidden tabs without reliable dimensions. Re-render
382
+ # once Efficiency becomes visible so Plotly can autosize against the real
383
+ # container instead of keeping the hidden-tab geometry.
384
+ efficiency_tab.select(
385
+ fn=render_efficiency,
386
+ inputs=efficiency_controls,
387
+ outputs=[efficiency_plot, efficiency_table, efficiency_note],
388
+ )
389
+
390
  with gr.Tab("🏃 Benchmark Runs"):
391
  benchmark_runs = init_benchmark_runs(BENCHMARK_RUN_DF)
392
 
requirements.txt CHANGED
@@ -15,5 +15,3 @@ tqdm
15
  transformers
16
  tokenizers>=0.15.0
17
  sentencepiece
18
- choix
19
- scipy
 
15
  transformers
16
  tokenizers>=0.15.0
17
  sentencepiece
 
 
src/rankings.py DELETED
@@ -1,152 +0,0 @@
1
- from typing import Any
2
- from pathlib import Path
3
-
4
- import numpy as np
5
- import pandas as pd
6
- import choix
7
-
8
-
9
- def prepare_ranking_data(
10
- df: pd.DataFrame,
11
- catcol: str | list[str],
12
- metcol: str,
13
- descending: bool = False,
14
- eqvcol: str | list[str] = [],
15
- ) -> tuple[list[tuple[int, int]], list[Any], dict]:
16
- ndata = df.shape[0]
17
- if ndata < 2:
18
- raise ValueError("Not enough data to prepare ranking comparisons")
19
- catcol = catcol if isinstance(catcol, list) else [catcol]
20
- eqvcol = eqvcol if isinstance(eqvcol, list) else [eqvcol]
21
- ncat = len(catcol)
22
- neqv = len(eqvcol)
23
- tcols = catcol + eqvcol + [metcol]
24
- t = list(df[tcols].itertuples(index=False, name=None))
25
- metvals = [x[-1] for x in t]
26
- if ncat > 1:
27
- catvals = [x[:ncat] for x in t]
28
- else:
29
- catvals = [x[0] for x in t]
30
- if neqv > 1:
31
- eqvvals = [x[ncat : ncat + neqv] for x in t]
32
- elif neqv == 1:
33
- eqvvals = [x[ncat] for x in t]
34
- else:
35
- eqvvals = ["[ALL]"] * ndata
36
- umap = dict([(y, x) for x, y in enumerate(sorted(set(catvals)))])
37
- cats = sorted(umap.keys())
38
- eqvcats = sorted(set(eqvvals))
39
- eqvdata = {}
40
- for eqv in eqvcats:
41
- eqvdata[eqv] = [[] for _ in range(len(cats))]
42
- compvals = [(umap[c], m, e) for c, m, e in zip(catvals, metvals, eqvvals)]
43
- for category, metric, equivalence in compvals:
44
- eqvdata[equivalence][category].append(metric)
45
- comps = []
46
- for i in range(ndata):
47
- ic, im, ie = compvals[i]
48
- for j in range(i):
49
- jc, jm, je = compvals[j]
50
- if ie != je:
51
- continue
52
- if im == jm:
53
- continue
54
- iwin = im < jm if descending else im > jm
55
- if iwin:
56
- comps.append((ic, jc))
57
- else:
58
- comps.append((jc, ic))
59
- return comps, cats, eqvdata
60
-
61
-
62
- def ranking_dataframe(cats, params, eqvdata) -> pd.DataFrame:
63
- ranking = np.argsort(params)[::-1]
64
- rows = []
65
- for rank, idx in enumerate(ranking, start=1):
66
- row = {
67
- "Rank": rank,
68
- "Category": cats[idx] if not isinstance(cats[idx], tuple) else " + ".join(cats[idx]),
69
- }
70
- for k in sorted(eqvdata.keys()):
71
- mets = eqvdata[k][idx]
72
- row[k] = round(float(np.mean(mets)), 3) if len(mets) > 0 else None
73
- rows.append(row)
74
- return pd.DataFrame(rows)
75
-
76
-
77
- RANK_BY_OPTIONS = {
78
- "Benchmark Score": ("metrics.score", False),
79
- "Mean Cost Per Task (USD)": ("metrics.mean_cost_usd_per_task", True),
80
- "Mean Tokens Per Task": ("metrics.mean_tokens_per_task", True),
81
- }
82
-
83
-
84
- def compute_ranking(df, catcol, metcol="metrics.score", descending=False, eqvcol="benchmark.name"):
85
- comps, cats, eqvdata = prepare_ranking_data(
86
- df, catcol, metcol, descending=descending, eqvcol=eqvcol
87
- )
88
- params = choix.ilsr_pairwise(len(cats), comps, alpha=1e-3)
89
- return ranking_dataframe(cats, params, eqvdata)
90
-
91
-
92
- def rank_harnesses(df: pd.DataFrame, metcol="metrics.score", descending=False) -> pd.DataFrame:
93
- return compute_ranking(df, "harness.name", metcol, descending)
94
-
95
-
96
- def rank_models(df: pd.DataFrame, metcol="metrics.score", descending=False) -> pd.DataFrame:
97
- return compute_ranking(df, "model.name", metcol, descending)
98
-
99
-
100
- def rank_pairs(df: pd.DataFrame, metcol="metrics.score", descending=False) -> pd.DataFrame:
101
- return compute_ranking(df, ["model.name", "harness.name"], metcol, descending)
102
-
103
-
104
- def _load_csv(csv_path: str | Path = "results.csv") -> pd.DataFrame:
105
- df = pd.read_csv(csv_path)
106
- df = df.dropna(subset=["metrics.score"])
107
- df = df.loc[df["metrics.score"] > 0]
108
- return df.reset_index(drop=True)
109
-
110
-
111
- def _empty_table():
112
- return pd.DataFrame({"Rank": [], "Category": []})
113
-
114
-
115
- def load_and_rank(
116
- csv_path: str | Path = "results.csv",
117
- open_models_only: bool = False,
118
- open_harnesses_only: bool = False,
119
- benchmarks: list[str] | None = None,
120
- models: list[str] | None = None,
121
- harnesses: list[str] | None = None,
122
- rank_by: str = "Benchmark Score",
123
- ) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
124
- df = _load_csv(csv_path)
125
- if open_models_only:
126
- df = df.loc[df["model.is_oss"] == True]
127
- if open_harnesses_only:
128
- df = df.loc[df["harness.is_oss"] == True]
129
- if benchmarks is not None:
130
- df = df.loc[df["benchmark.name"].isin(benchmarks)]
131
- if models is not None:
132
- df = df.loc[df["model.name"].isin(models)]
133
- if harnesses is not None:
134
- df = df.loc[df["harness.name"].isin(harnesses)]
135
-
136
- metcol, descending = RANK_BY_OPTIONS.get(rank_by, ("metrics.score", False))
137
- df = df.dropna(subset=[metcol])
138
- if descending:
139
- df = df.loc[df[metcol] > 0]
140
- df = df.reset_index(drop=True)
141
-
142
- if len(df) < 2:
143
- empty = _empty_table()
144
- return empty, empty, empty
145
-
146
- results = []
147
- for rank_fn in (rank_harnesses, rank_models, rank_pairs):
148
- try:
149
- results.append(rank_fn(df, metcol, descending))
150
- except ValueError:
151
- results.append(_empty_table())
152
- return results[0], results[1], results[2]