docs: def tabulate(g, ncol=1, headers=True, offset='', ndecimal=None):
      """ Tabulate contents of an array or dictionary of |GVar|\s.
  
      Given an array ``g`` of |GVar|\s or a dictionary whose values are
      |GVar|\s or arrays of |GVar|\s, ``gvar.tabulate(g)`` returns a
      string containing a table of the values of ``g``'s entries.
      For example, the code ::
  
          import collections
          import gvar as gv
  
          g = collections.OrderedDict()
          g['scalar'] = gv.gvar('10.3(1)')
          g['vector'] = gv.gvar(['0.52(3)', '0.09(10)', '1.2(1)'])
          g['tensor'] = gv.gvar([
              ['0.01(50)', '0.001(20)', '0.033(15)'],
              ['0.001(20)', '2.00(5)', '0.12(52)'],
              ['0.007(45)', '0.237(4)', '10.23(75)'],
              ])
          print(gv.tabulate(g, ncol=2))
  
      prints the following table::
  
             key/index          value     key/index          value
          ---------------------------  ---------------------------
                scalar     10.30 (10)           1,0     0.001 (20)
              vector 0     0.520 (30)           1,1     2.000 (50)
                     1      0.09 (10)           1,2      0.12 (52)
                     2      1.20 (10)           2,0     0.007 (45)
            tensor 0,0      0.01 (50)           2,1    0.2370 (40)
                   0,1     0.001 (20)           2,2     10.23 (75)
                   0,2     0.033 (15)
  
      Args:
          g: Array of |GVar|\s (any shape) or dictionary whose values are
              |GVar|\s or arrays of |GVar|\s (any shape).
          ncol: The table is split over ``ncol`` columns of key/index values
              plus |GVar| values. Default value is 1.
          headers: Prints standard header on table if ``True``; omits the
              header if ``False``. If ``headers`` is a 2-tuple, then
              ``headers[0]`` is used in the header over the indices/keys
              and ``headers[1]`` over the |GVar| values. (Default is ``True``.)
          offset (str): String inserted at the beginning of each line in
              the table. Default is ``''``.
          ndecimal: Number of digits displayed after the decimal point.
              Default is ``ndecimal=None`` which adjusts table entries to
              show 2 digits of error.
      """
      entries = []
      if hasattr(g, 'keys'):
          if headers is True:
              headers = ('key/index', 'value')
          g = BufferDict(g)
          for k in g:
              if g[k].shape == ():
                  entries.append((
                      str(k), fmt(g[k], sep=' ', ndecimal=ndecimal)
                      ))
              else:
                  prefix = str(k) + ' '
                  gk = g[k]
                  for idx in numpy.ndindex(gk.shape):
                      str_idx = str(idx)[1:-1]
                      str_idx = ''.join(str_idx.split(' '))
                      if str_idx[-1] == ',':
                          str_idx = str_idx[:-1]
                      entries.append((
                          prefix + str_idx,
                          fmt(gk[idx], sep=' ', ndecimal=ndecimal),
                          ))
                      if prefix != '':
                          prefix = ''
      else:
          if headers is True:
              headers = ('index', 'value')
          g = numpy.asarray(g)
          if g.shape == ():
              return fmt(g)
          for idx in numpy.ndindex(g.shape):
              str_idx = str(idx)[1:-1]
              str_idx = ''.join(str_idx.split(' '))
              if str_idx[-1] == ',':
                  str_idx = str_idx[:-1]
              entries.append((
                  str_idx, fmt(g[idx], sep=' ', ndecimal=ndecimal)
                  ))
      w0 = max(len(ei[0]) for ei in entries)
      w1 = max(len(ei[1]) for ei in entries)
      linefmt = '  {e0:>{w0}}    {e1:>{w1}}'
      table = ncol * [[]]
      # nl = length of long columns; ns = lenght of short columns
      nl = len(entries) // ncol
      if nl * ncol < len(entries):
          nl += 1
      ns = len(entries) - (ncol - 1) * nl
      ne = (ncol - 1) * [nl] + [ns]
      iter_entries = iter(entries)
      if headers is not False and len(headers) != 2:
          raise ValueError('headers must be True, False or a 2-tuple')
      for col in range(ncol):
          if headers is not False:
              e0, e1 = headers
              w0 = max(len(e0), w0)
              w1 = max(len(e1), w1)
              table[col] = [linefmt.format(e0=e0, w0=w0, e1=e1, w1=w1)]
              table[col].append(len(table[col][0]) * '-')
          else:
              table[col] = []
          for ii in range(ne[col]):
              e0, e1 = next(iter_entries)
              table[col].append(linefmt.format(e0=e0, w0=w0, e1=e1, w1=w1))
      mtable = []
      if headers is not False:
          ns += 2
          nl += 2
      for i in range(ns):
          mtable.append('  '.join([tabcol[i] for tabcol in table]))
      for i in range(ns, nl):
          mtable.append('  '.join([tabcol[i] for tabcol in table[:-1]]))
      return offset + ('\n' + offset).join(mtable)