docs: def hist_from_values_list(values_list, fillers=(None,), normalize=False,
                            cumulative=False, to_str=False, sep=',', min_bin=None, max_bin=None):
      """Compute an emprical histogram, PMF or CDF in a list of lists or a csv string
  
      Only works for discrete (integer) values (doesn't bin real values).
      `fillers`: list or tuple of values to ignore in computing the histogram
  
      >>> hist_from_values_list([1,1,2,1,1,1,2,3,2,4,4,5,7,7,9])  # doctest: +NORMALIZE_WHITESPACE
      [(1, 5), (2, 3), (3, 1), (4, 2), (5, 1), (6, 0), (7, 2), (8, 0), (9, 1)]
      >>> hist_from_values_list([(1,9),(1,8),(2,),(1,),(1,4),(2,5),(3,3),(5,0),(2,2)])  # doctest: +NORMALIZE_WHITESPACE
      [[(1, 4), (2, 3), (3, 1), (4, 0), (5, 1)], [(0, 1), (1, 0), ... (6, 0), (7, 0), (8, 1), (9, 1)]]
      >>> hist_from_values_list(transposed_matrix([(8,),(1,3,5),(2,),(3,4,5,8)]))  # doctest: +NORMALIZE_WHITESPACE
      [[(8, 1)], [(1, 1), (2, 0), (3, 1), (4, 0), (5, 1)], [(2, 1)], [(3, 1), (4, 1), (5, 1), (6, 0), (7, 0), (8, 1)]]
      """
      value_types = tuple([int, float] + [type(filler) for filler in fillers])
  
      if all(isinstance(value, value_types) for value in values_list):
          # ignore all fillers and convert all floats to ints when doing counting
          counters = [Counter(int(value) for value in values_list if isinstance(value, (int, float)))]
      elif all(len(row) == 1 for row in values_list) and all(isinstance(row[0], value_types) for row in values_list):
          return hist_from_values_list([values[0] for values in values_list], fillers=fillers,
                                       normalize=normalize, cumulative=cumulative,
                                       to_str=to_str, sep=sep, min_bin=min_bin, max_bin=max_bin)
      else:  # assume it's a row-wise table (list of rows)
          return [
              hist_from_values_list(col, fillers=fillers, normalize=normalize, cumulative=cumulative, to_str=to_str, sep=sep,
                                    min_bin=min_bin, max_bin=max_bin)
              for col in transposed_matrix(values_list)
          ]
  
      if not values_list:
          return []
  
      intkeys_list = [[c for c in counts if (isinstance(c, int) or (isinstance(c, float) and int(c) == c))]
                      for counts in counters]
      try:
          min_bin = int(min_bin)
      except (IndexError, ValueError, AttributeError, TypeError):
          min_bin = min(min(intkeys) for intkeys in intkeys_list)
      try:
          max_bin = int(max_bin)
      except (IndexError, ValueError, AttributeError, TypeError):
          max_bin = max(max(intkeys) for intkeys in intkeys_list)
  
      # FIXME: this looks slow and hazardous (like it's ignore min/max bin):
      # TODO: reuse min(intkeys)
      min_bin = max(min_bin, min((min(intkeys) if intkeys else 0) for intkeys in intkeys_list))
      max_bin = min(max_bin, max((max(intkeys) if intkeys else 0) for intkeys in intkeys_list))
  
      histograms = []
      for intkeys, counts in zip(intkeys_list, counters):
          histograms += [OrderedDict()]
          if not intkeys:
              continue
          if normalize:
              N = sum(counts[c] for c in intkeys)
              for c in intkeys:
                  counts[c] = float(counts[c]) / N
          if cumulative:
              for i in range(min_bin, max_bin + 1):
                  histograms[-1][i] = counts.get(i, 0) + histograms[-1].get(i - 1, 0)
          else:
              for i in range(min_bin, max_bin + 1):
                  histograms[-1][i] = counts.get(i, 0)
      if not histograms:
          histograms = [OrderedDict()]
  
      # fill in the zero counts between the integer bins of the histogram
      aligned_histograms = []
  
      for i in range(min_bin, max_bin + 1):
          aligned_histograms += [tuple([i] + [hist.get(i, 0) for hist in histograms])]
  
      if to_str:
          # FIXME: add header row
          return str_from_table(aligned_histograms, sep=sep, max_rows=365 * 2 + 1)
  
      return aligned_histograms