class_docstr: Summarize the benchmark results in JSON files to be displayed in the UI.
docstr: Create a table for where each row is an adapter (for which we have a set of runs) and columns are pairs of
  run_group (natural_qa) and metrics (accuracy). This method can be used to either create a table with multiple
  metrics for a single scenario or a table with multiple scenarios together.
  adapter (e.g,  model) and columns are constructed based on metrics.
docstr_tok: ["Create","a","table","for","where","each","row","is","an","adapter","(","for","which","we","have","a","set","of","runs",")","and","columns","are","pairs","of","run_group","(","natural_qa",")","and","metrics","(","accuracy",")",".","This","method","can","be","used","to","either","create","a","table","with","multiple","metrics","for","a","single","scenario","or","a","table","with","multiple","scenarios","together",".","adapter","(","e.g",",","model",")","and","columns","are","constructed","based","on","metrics","."]
code: def create_group_table(
      self,
      name: str,
      title: str,
      adapter_to_runs: Dict[AdapterSpec, List[Run]],
      link_to_runs: bool,
      columns: List[Tuple[RunGroup, str]],  # run_group, metric_group
      sort_by_model_order: bool = True,
      sub_split: Optional[str] = None,
      bold_columns: bool = True,
      add_win_rate: bool = False,
  ) -> Table:
      """
      Create a table for where each row is an adapter (for which we have a set of runs) and columns are pairs of
      run_group (natural_qa) and metrics (accuracy). This method can be used to either create a table with multiple
      metrics for a single scenario or a table with multiple scenarios together.
      adapter (e.g,  model) and columns are constructed based on metrics.
      """
  
      # Figure out what the columns of the table are.
      # Create header (cells to display) and the list of metric name filters
      # (to pull out information later).
      if not columns or not adapter_to_runs:
          hlog(
              f"WARNING: table {title}, has no rows or columns, leaving empty"
          )
          return Table("empty", [], [])
  
      header: List[HeaderCell] = []
      matchers: List[MetricNameMatcher] = []
      group_names: List[str] = []  # for each column
      num_groups = len(
          set(run_group.name for run_group, _ in columns)
      )  # number of unique groups, determines headers
  
      # Column headers
      header.append(HeaderCell("Model/adapter"))
      for run_group, metric_group_name in columns:
          # check if at least the basic version of a metric group is evaluated (e.g., "bias" for "bias_detailed")
          if (
              metric_group_name.replace("_detailed", "")
              not in run_group.metric_groups
          ):
              continue
          metric_group = self.schema.name_to_metric_group[
              metric_group_name
          ]
          for metric in metric_group.metrics:
              matcher = metric.substitute(run_group.environment)
              if sub_split is not None:
                  matcher = replace(matcher, sub_split=sub_split)
              header_field = self.schema.name_to_metric.get(
                  matcher.name
              )
              if header_field is None:
                  hlog(
                      f"WARNING: metric name {matcher.name} undefined in {SCHEMA_YAML_FILENAME}, skipping"
                  )
                  continue
              metadata = {
                  "metric": header_field.get_short_display_name(),
                  "run_group": run_group.get_short_display_name(),
              }
  
              header_name = header_field.get_short_display_name()
              description = (
                  run_group.description + "\n\n"
                  if run_group.description is not None
                  else ""
              ) + (
                  header_field.display_name
                  + ": "
                  + header_field.description
              )
  
              if matcher.perturbation_name is not None:
                  perturbation_field = self.schema.name_to_perturbation[
                      matcher.perturbation_name
                  ]
                  header_name += (
                      " ("
                      + perturbation_field.get_short_display_name()
                      + ")"
                  )
                  description += (
                      "\n- Perturbation "
                      + perturbation_field.display_name
                      + ": "
                      + (perturbation_field.description or "???")
                  )
                  metadata[
                      "perturbation"
                  ] = perturbation_field.get_short_display_name()
  
              if (
                  num_groups > 1
              ):  # we have multiple groups in the same table, so display the name in the column
                  header_name = f"{run_group.get_short_display_name()} - {header_name}"
  
              header.append(
                  HeaderCell(
                      header_name,
                      description=description,
                      lower_is_better=header_field.lower_is_better,
                      metadata=metadata,
                  )
              )
              matchers.append(matcher)
              group_names.append(run_group.name)
  
      # TODO: Fix run_group logic
      run_group = columns[0][0]
  
      def run_spec_names_to_url(run_spec_names: List[str]) -> str:
          return get_benchmarking_url(
              {
                  "group": run_group.name,
                  "subgroup": title,
                  "runSpecs": json.dumps(run_spec_names),
              }
          )
  
      adapter_specs: List[AdapterSpec] = list(adapter_to_runs.keys())
      if sort_by_model_order:
          # Sort models by the order defined in the schema.
          # Models not defined in the schema will be sorted alphabetically and
          # placed before models in defined the schema.
          model_order = [model.name for model in self.schema.models]
  
          def _adapter_spec_sort_key(spec):
              index = (
                  model_order.index(spec.model)
                  if spec.model in model_order
                  else -1
              )
              return (index, spec.model)
  
          adapter_specs = list(
              sorted(adapter_specs, key=_adapter_spec_sort_key)
          )
  
      # Pull out only the keys of the method adapter_spec that is needed to
      # uniquely identify the method.
      infos = unique_simplification(
          list(map(asdict_without_nones, adapter_specs)), ["model"]
      )
  
      assert len(adapter_specs) == len(infos), [adapter_specs, infos]
  
      # Populate the contents of the table
      rows = []
      for adapter_spec, info in zip(adapter_specs, infos):
          model_name: str = adapter_spec.model
  
          # Get the model display name from the schema.
          # Fall back to using the model name as the model display name if the model is not
          # defined in the schema.
          model_display_name = (
              self.schema.name_to_model[model_name].display_name
              if model_name in self.schema.name_to_model
              else model_name
          )
  
          runs = adapter_to_runs[adapter_spec]
          display_name = get_method_display_name(
              model_display_name, info
          )
  
          # Link to all the runs under this model
          if link_to_runs:
              run_spec_names = [run.run_spec.name for run in runs]
              href = run_spec_names_to_url(run_spec_names)
          else:
              href = None
  
          # Render contamination information
          point = self.contamination.get_point(
              model_name, columns[0][0].name
          )
          if (
              num_groups == 1 and point is not None
          ):  # display contamination information at the adapter level
              cells = [
                  Cell(
                      display_name + CONTAMINATION_SYMBOLS[point.level],
                      description=point.description,
                      href=href,
                  )
              ]
          else:
              cells = [Cell(display_name, description="", href=href)]
          assert len(group_names) == len(matchers)
          for group_name, matcher in zip(group_names, matchers):
              group_runs = [
                  run
                  for run in runs
                  if group_name in run.run_spec.groups
              ]
              # HACK: when looking at aggregate bAbi results (e.g., reasoning), we want to see only the `task: all`
              # version and not the default aggregation across a sparse set of tasks, e.g., `task: {all, 3, 15, 19}`
              if "babi" in group_name and "task:" not in name:
                  group_runs = [
                      run
                      for run in group_runs
                      if "task=all" in run.run_spec.name
                  ]
  
              point = self.contamination.get_point(
                  model_name, group_name
              )
              if point is not None:
                  description = (
                      CONTAMINATION_SYMBOLS[point.level]
                      + " "
                      + point.description
                  )
                  contamination_level = point.level
              else:
                  description = ""
                  contamination_level = None
  
              # HACK: we want to hide stats for the following model-metric combinations:
              # 1. Calibration metrics + AI21/Anthropic
              # 2. MSMARCO metrics + AI21/Anthropic
              hide_value: bool = (
                  model_name in Summarizer.LOGPROBS_ISSUE_MODELS
                  and matcher.name in Summarizer.LOGPROBS_ISSUE_METRICS
              )
              cells.append(
                  self.create_cell(
                      group_runs,
                      matcher,
                      contamination_level,
                      additional_info=description,
                      hide_value=hide_value,
                  )
              )
  
          rows.append(cells)
  
      # Link to a page to visualize all runs for comparison.
      # There could be a ton of runs, so only do this if there are 2-5
      # TODO: replace in frontend with a selector to choose which rows to visualize.
      links = []
      if link_to_runs:
          all_run_spec_names = []
          for adapter_spec, runs in adapter_to_runs.items():
              if len(runs) > 1:
                  hlog(
                      f"WARNING: table row corresponding to adapter spec {adapter_spec} has {len(runs)} > 1 runs:"
                      f" {[run.run_spec.name for run in runs]}"
                  )
              for run in runs:
                  all_run_spec_names.append(run.run_spec.name)
          if (
              len(all_run_spec_names) >= 2
              and len(all_run_spec_names) <= 5
          ):
              links.append(
                  Hyperlink(
                      text="compare all",
                      href=run_spec_names_to_url(all_run_spec_names),
                  )
              )
  
      table = Table(
          title=title, header=header, rows=rows, links=links, name=name
      )
  
      if add_win_rate:
          # add overall win rate as the second column
          WIN_RATE_AGGREGATION = "mean"
          win_rates = compute_aggregate_row_win_rates(
              table, aggregation=WIN_RATE_AGGREGATION
          )
          description = "How many models this model outperform on average (over columns)."
          table.header.insert(
              AGGREGATE_WIN_RATE_COLUMN,
              HeaderCell(
                  f"{WIN_RATE_AGGREGATION.capitalize()} win rate",
                  description=description,
                  lower_is_better=False,
              ),
          )
          for row, win_rate in zip(table.rows, win_rates):
              row.insert(AGGREGATE_WIN_RATE_COLUMN, Cell(win_rate))
  
      if bold_columns:
          for i, header_cell in enumerate(table.header):
              lower_is_better = header_cell.lower_is_better
              if lower_is_better is None:
                  continue
              values = [
                  float(row[i].value)
                  for row in rows
                  if row[i].value is not None
              ]
              if not values:
                  continue
              best = min(values) if lower_is_better else max(values)
              for row in table.rows:
                  cell = row[i]
                  if cell.value is not None and cell.value == best:
                      bold_style = (
                          cell.style.copy()
                          if cell.style is not None
                          else {}
                      )
                      bold_style["font-weight"] = "bold"
                      row[i] = replace(cell, style=bold_style)
      return table
  
imports: import argparse
  import os
  import datetime
  import urllib.parse
  import dacite
  import json
  from collections import defaultdict
  from dataclasses import dataclass, replace
  from statistics import mean, median
  from typing import List, Optional, Dict, Any, Tuple, Set
  from tqdm import tqdm
  from helm.common.general import write, ensure_directory_exists, asdict_without_nones, parallel_map, singleton, unique_simplification
  from helm.common.hierarchical_logger import hlog, htrack, htrack_block
  from helm.benchmark.scenarios.scenario import ScenarioSpec
  from helm.benchmark.adaptation.adapter_spec import AdapterSpec
  from helm.benchmark.metrics.metric_name import MetricName
  from helm.benchmark.metrics.metric import get_all_stats_by_name
  from helm.benchmark.metrics.statistic import Stat, merge_stat
  from helm.benchmark.runner import RunSpec, LATEST_SYMLINK
  from .table import Cell, HeaderCell, Table, Hyperlink, table_to_latex
  from .schema import MetricNameMatcher, RunGroup, read_schema, SCHEMA_YAML_FILENAME, BY_GROUP, THIS_GROUP_ONLY, NO_GROUPS
  from .contamination import read_contamination, validate_contamination, CONTAMINATION_SYMBOLS, CONTAMINATION_STYLES, CONTAMINATION_LEVEL_STRONG
  from .run_display import write_run_display_json
label_desc: Use image node_id 14 for calling the Summarizer obj's underlying member method code with example usage: obj.create_group_table(name, title, adapter_to_runs, link_to_runs, columns, sort_by_model_order, sub_split, bold_columns, add_win_rate) and returns: table, Table, get_benchmarking_url, index,  spec