code: def assemble_table(data, padding=2, decimals=2):
      """Construct data table from input dict
  
      Given a nested dictionary formed by :func:`assemble_data`,
      :func:`assemble_table` returns a string that prints the contents of
      the input in tabular format. The input dictionary is
      expected to have two levels: the first level gives the columns and the
      second level the rows. Rows names are parsed as
      ``[OUTER]/[MIDDLE].[INNER]--[IDX]``, where IDX must be an integer. All
      entries are optional.
  
      .. seealso::
          :class:`Data`, :func:`assemble_data`
  
      Examples
      --------
      >>> from mlens.metrics import assemble_data, assemble_table
      >>> d = [('row-idx-1.row-idx-2.a.b', {'column-1': 0.1, 'column-2': 0.1})]
      >>> print(assemble_table(assemble_data(d)))
                              column-2-m  column-2-s  column-1-m  column-1-s
      row-idx-1  row-idx-2          0.10        0.00        0.10        0.00
      """
      buffer = 0
      row_glossary = ["layer", "case", "est", "part"]
  
      cols = list()
      rows = list()
      row_keys = list()
      max_col_len = dict()
      max_row_len = {r: 0 for r in row_glossary}
  
      # First, measure the maximum length of each column in table
      for key, val in data.items():
          cols.append(key)
          max_col_len[key] = len(key)
  
          # dat_key is the estimators. Number of columns is not fixed so need
          # to assume all exist and purge empty columns
          for dat_key, v in sorted(val.items()):
              if not v:
                  # Safety: no data
                  continue
  
              v_ = len(_get_string(v, decimals))
              if v_ > max_col_len[key]:
                  max_col_len[key] = v_
  
              if dat_key in row_keys:
                  # Already mapped row entry name
                  continue
  
              layer, k = _split(dat_key, "/")
              case, k = _split(k, ".")
              est, part = _split(k, "--", reverse=True)
  
              # Header space before column headings
              items = [i for i in [layer, case, est, part] if i != ""]
              buffer = max(buffer, len("  ".join(items)))
  
              for k, v in zip(row_glossary, [layer, case, est, part]):
                  v_ = len(v)
                  if v_ > max_row_len[k]:
                      max_row_len[k] = v_
  
              dat = _dict()
              dat["layer"] = layer
              dat["case"] = case
              dat["est"] = est
              dat["part"] = part
              row_keys.append(dat_key)
              rows.append(dat)
  
      # Check which row name columns we can drop (ex partition number)
      drop = list()
      for k, v in max_row_len.items():
          if v == 0:
              drop.append(k)
  
      # Header
      out = " " * (buffer + padding)
      for col in cols:
          adj = max_col_len[col] - len(col) + padding
          out += " " * adj + col
      out += "\n"
  
      # Entries
      for dat_key, dat in zip(row_keys, rows):
          # Estimator name
          for key, val in dat.items():
              if key in drop:
                  continue
              adj = max_row_len[key] - len(val) + padding
              out += val + " " * adj
  
          # Data
          for col in cols:
              item = data[col][dat_key]
              if not item and item != 0:
                  out += " " * (max_col_len[col] + padding)
                  continue
              item_ = _get_string(item, decimals)
              adj = max_col_len[col] - len(item_) + padding
              out += " " * adj + item_
          out += "\n"
      return out
  
file_path: mlens/mlens/metrics/utils.py
imports: from __future__ import division
  import warnings
  import numpy
  from ..utils.exceptions import MetricWarning
label_desc: Use image node_id 4 for calling a global function with example usage: assemble_table(data, padding, decimals) and returns: out