Sankey Diagram Basics with Python’s Plotly | by Thiago Carvalho | Towards Data Science

In this article, I’ll go through the basics of using Plotly and Python for drawing Sankey Diagrams.
They’re a convenient chart for visualizing any kind of measurable flow — Some examples are the flow of travelers, spellers, and money.
The creation of this diagram is credited to the Irish Captain Matthew H. P. R. Sankey, who used it to visualize the energy efficiency of steam engines.
The idea of Sankey’s diagram is similar to a network chart, where links connect nodes.
The main difference being that in Sankey’s model, the links have different widths that encode a measurable variable they have in common.
For the following examples, I’ll use Plotly with Jupyter Lab to explore how to create a Sankey.
import plotly.graph_objects as go
There are different ways of building a chart with Plotly; I’ll use Graphical Objects, visualize the graphs with the Jupiter widgets, and export an HTML for the final visualization.
Building a Sankey can be quite a struggle especially if you have too many nodes and connections, I’ll use lists for the examples to make it simpler, but you can adapt this logic with a JSON file or a Pandas Dataframe.
We will use go.Sankey to build the chart, which requires a link.
That link is a dictionary containing data about the connections we want to draw.
source = [0, 0, 1, 1, 0]target = [2, 3, 4, 5, 4]value = [8, 2, 2, 8, 4]
The source and target are lists of indexes for the nodes Plotly will connect, and the value is a list of numbers that will define the width of these connections.
link = dict(source = source, target = target, value = value)data = go.Sankey(link = link)print(data)
We saved the Sankey object to a variable called data, and now we can pass that data to a Figure;
fig = go.Figure(data)
And display the chart.
fig.show()
That’s the idea. Let’s try identifying those nodes so we can have a more unobstructed view of what is connecting what.
We’ll need a dictionary with the node’s data, which should contain a list with the labels. We can also add more parameters such as pad to customize the distance between the nodes, or thickness to define the size of their handles.
# datalabel = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE"]source = [0, 0, 1, 1, 0]target = [2, 3, 4, 5, 4]value = [8, 2, 2, 8, 4]# data to dict, dict to sankeylink = dict(source = source, target = target, value = value)node = dict(label = label, pad=50, thickness=5)data = go.Sankey(link = link, node=node)# plotfig = go.Figure(data)fig.show()
That’s easier to understand. We can read the lists, and visualize how they connect.
Now let’s try this with some real data, I’ll try to change this visualization from Vancouver’s 2020 Budget.
I want to see the proportion of money flowing from the operational expenditures, to areas, to services.
I will start with a node for the total expenditures, that will connect to the four areas, which will then link to their subdivisions — Let’s make a quick sketch of the idea.
I’ve used the percentages to find the values, and build the following lists for the links.
source = [0, 0, 0, 0,       # Op Expeditures          1, 1,             # Public Safety          2, 2,             # Eng n Util          3, 3, 3, 3, 3, 3, # Community Serv          4, 4, 4]          # Corp Supporttarget = [1, 2, 3, 4,           5, 6,          7, 8,           9, 10, 11, 12, 13, 14,           15, 16, 17]value = [484500, 468350, 355300, 306850,          339150, 145350,          371450, 96900,          129200, 80750, 48450, 48450, 32300, 16150,          113050, 129200, 64600]
Let’s also define the labels for the nodes.
label = ['Operating Expeditures',          'Public Safety',          'Engineering and Utilities',          'Community-Related Services',          'Corporate Support',         'Police',          'Fire',         'Utilities',          'Engineering Public Works',         'Parks and Recreation',          'Arts, Culture, and Community Services',         'Library',          'Development, Buildings, and Licensing',         'Planning, Urban Design, and Sustainability',          'Other',         'Corporate Support',          'Debt and Capital (Non-Utility)',          'Contingencies and Transfers']
Once we have the data ready, all that’s left to do is define our figure and plot.
Nice, it’s getting better.
There’s another encoding we can use in our diagram, the color.
We can define the colors of both the node and the links; to do so, we need a list with one color per element.
color_node = ['#808B96', '#EC7063', '#F7DC6F', '#48C9B0', '#AF7AC5','#EC7063', '#EC7063','#F7DC6F', '#F7DC6F','#48C9B0', '#48C9B0', '#48C9B0', '#48C9B0', '#48C9B0', '#48C9B0','#AF7AC5', '#AF7AC5', '#AF7AC5']color_link = ['#EBBAB5', '#FEF3C7', '#A6E3D7', '#CBB4D5','#EBBAB5', '#EBBAB5','#FEF3C7', '#FEF3C7','#A6E3D7', '#A6E3D7', '#A6E3D7', '#A6E3D7', '#A6E3D7', '#A6E3D7','#CBB4D5', '#CBB4D5', '#CBB4D5']
Here I’m trying to highlight the area of each expenditure, I’m using similar colors for the nodes and links, with one being a little brighter than the other.
Almost ready, all that’s left to do is add a title, adjust the details, and export the HTML file.
We can update the layout of our figure before plotting, and this allows us to change the fonts, background colors, define the information on our tooltip, and much more.
fig.update_layout(    hovermode = 'x',    title="Vancouver Operating Expeditures by Area of Service",    font=dict(size = 10, color = 'white'),    paper_bgcolor='#5B5958')
Once the visualization is ready, we can export it to HTML. That is maybe the most natural part, all we have to do is call .write_html(‘filename.html’).
Here you can see another example, where I added the operating revenues.
I’ve tried to remove the white text-shadow, but I couldn’t find a solution within Plotly, the best answer I found was to override the CSS.
You can find the code for this article here, and the webpage with the interactive Sankey here.
Thanks for taking the time to read my article, I hope you enjoyed it.
More Resources:Plotly — Graph Objects Plotly — Sankey Diagrams