code: def alluvian_diagram(
      source,
      value_cols,
      by_col,
      weight_col="weight",
      vertical_margin=0.1,
      horizontal_margin=0.2,
      width=0.05,
      alpha=0.4,
      ax=None,
  ):
      """Sankey layouting given a table of co-occurences.
  
      Actually an alluvian diagram.
  
      For a high-level API based on a dataframe of data, look at plot_sankey.
  
      Parameters
      ----------
      source : ndarray
          Count of co-occurences for variables.
  
      value_cols : list of column names
          List of columns to include as variables.
  
      by_col : string
          Column name for grouping. Usually the class name.
          This would be "hue" in seaborn.
  
      weight_col : string, default='weight'
          Name of column to use as weight / flow strength.
  
      vertical_margin : float, default=.1
          Margin between values within a column.
  
      horizontal_margin : float, default=.2
          Margin between columns corresponding to different variables.
  
      width : float, default=.05
          Width of column corresponding to a variable.
  
      alpha : float, default=.4
          Transparency for flows.
  
      ax : matplotlib Axes, default=None
          Axes to plot into.
      """
      source = source.copy()
      source[weight_col] = source[weight_col] / source[weight_col].sum()
      if ax is None:
          _, ax = plt.subplots(dpi=100)
      ax.set_ylim(-0.1, 1 + vertical_margin + 0.2)
      ax.set_xlim(
          -0.1,
          width * len(value_cols)
          + horizontal_margin * (len(value_cols) - 1)
          + 0.1,
      )
      left = 0
  
      coords = defaultdict(dict)
      for col in value_cols:
          top = 0
          # compute blocks per category for each feature/value column
          # weights is height of each individual category
          # coords stores left and top for each value
          weights = source.groupby(col)[weight_col].sum()
          n_values = len(weights)
          this_margin = vertical_margin / (n_values - 1)
          ax.text(
              left,
              0,
              col,
              rotation=90,
              horizontalalignment="right",
              verticalalignment="bottom",
          )
          for val, this_weight in weights.items():
              if this_weight == 0:
                  continue
              coords[col][val] = (left, top)
              patch = patches.Rectangle(
                  (left, top),
                  width=width,
                  height=this_weight,
                  ec="k",
                  fc="none",
              )
              ax.text(left, top, val)
              top += this_weight + this_margin
              ax.add_patch(patch)
          left += width + horizontal_margin
  
      colors = {
          val: col
          for val, col in zip(
              source[by_col].unique(),
              cycle(
                  matplotlib.rcParams["axes.prop_cycle"].by_key()[
                      "color"
                  ]
              ),
          )
      }
  
      coords_backwards = deepcopy(coords)
      # coords has "top" for each category
      # for each row in data, put in current position,
      # and increase top for that block.
      verts = defaultdict(list)
      # for first column, sort just by that column.
      sort_col = value_cols[0]
      for col in value_cols:
          coord_col_name = f"coord_{col}"
          source[coord_col_name] = 0
          for i, row in source.sort_values(
              [by_col, sort_col]
          ).iterrows():
              # start of block for value of row in thie columns
              coord = coords[col][row[col]]
              coords[col][row[col]] = (
                  coord[0],
                  coord[1] + row[weight_col],
              )
              verts[i].append(coord)
              source.loc[i, coord_col_name] = coord[1]
          sort_col = coord_col_name
  
      # now go backwards and change the "out" of each of the flows
      # in the previous column
      verts_backwards = defaultdict(list)
      sort_col = f"coord_{value_cols[-1]}"
      # start with second to last col, leave out first
      for col in value_cols[-2:0:-1]:
          for i, row in source.sort_values(
              [by_col, sort_col]
          ).iterrows():
              coord = coords_backwards[col][row[col]]
              coords_backwards[col][row[col]] = (
                  coord[0],
                  coord[1] + row[weight_col],
              )
              verts_backwards[i].append(coord)
          sort_col = f"coord_{col}"
  
      for i, row in source.iterrows():
          patch = _make_alluvial_curve(
              verts[i],
              verts_backwards[i][::-1],
              row[weight_col],
              width,
              colors[row[by_col]],
              alpha=alpha,
          )
          ax.add_patch(patch)
  
      ax.set_axis_off()
  
file_path: dabl/dabl/plot/sankey.py
imports: import matplotlib
  from collections import defaultdict
  import matplotlib.pyplot
  from matplotlib.path import Path
  import matplotlib.patches
  from itertools import cycle
  from sklearn.feature_selection import mutual_info_classif
  import numpy
  from copy import deepcopy
  from ..preprocessing import detect_types
  from .utils import _prune_categories
label_desc: Use image node_id 3 for calling a global function with example usage: alluvian_diagram(source, value_cols, by_col, weight_col, vertical_margin, horizontal_margin, width, alpha, ax) without return types