docs: def _normalize_tabular_data(tabular_data, headers):
      """Transform a supported data type to a list of lists, and a list of headers.
  
      Supported tabular data types:
  
      * list-of-lists or another iterable of iterables
  
      * list of named tuples (usually used with headers="keys")
  
      * 2D NumPy arrays
  
      * NumPy record arrays (usually used with headers="keys")
  
      * dict of iterables (usually used with headers="keys")
  
      * pandas.DataFrame (usually used with headers="keys")
  
      The first row can be used as headers if headers="firstrow",
      column indices can be used as headers if headers="keys".
  
      """
  
      if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
          # dict-like and pandas.DataFrame?
          if hasattr(tabular_data.values, "__call__"):
              # likely a conventional dict
              keys = tabular_data.keys()
              rows = list(izip_longest(*tabular_data.values()))  # columns have to be transposed
          elif hasattr(tabular_data, "index"):
              # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
              keys = tabular_data.keys()
              vals = tabular_data.values  # values matrix doesn't need to be transposed
              names = tabular_data.index
              rows = [[v]+list(row) for v,row in zip(names, vals)]
          else:
              raise ValueError("tabular data doesn't appear to be a dict or a DataFrame")
  
          if headers == "keys":
              headers = list(map(_text_type,keys))  # headers should be strings
  
      else:  # it's a usual an iterable of iterables, or a NumPy array
          rows = list(tabular_data)
  
          if (headers == "keys" and
              hasattr(tabular_data, "dtype") and
              getattr(tabular_data.dtype, "names")):
              # numpy record array
              headers = tabular_data.dtype.names
          elif (headers == "keys"
                and len(rows) > 0
                and isinstance(rows[0], tuple)
                and hasattr(rows[0], "_fields")): # namedtuple
              headers = list(map(_text_type, rows[0]._fields))
          elif headers == "keys" and len(rows) > 0:  # keys are column indices
              headers = list(map(_text_type, range(len(rows[0]))))
  
      # take headers from the first row if necessary
      if headers == "firstrow" and len(rows) > 0:
          headers = list(map(_text_type, rows[0])) # headers should be strings
          rows = rows[1:]
  
      headers = list(headers)
      rows = list(map(list,rows))
  
      # pad with empty headers for initial columns if necessary
      if headers and len(rows) > 0:
         nhs = len(headers)
         ncols = len(rows[0])
         if nhs < ncols:
             headers = [""]*(ncols - nhs) + headers
  
      return rows, headers