Is there a way to format plotly Sankey Diagram display? - python

I am creating a Sankey diagram with plotly as follows:
import plotly.graph_objects as go
fig = go.Figure(data=[go.Sankey(
valueformat = ".0f",
valuesuffix = " %",
orientation = "h",
node = dict(
pad = 20,
thickness = 20,
line = dict(color = "red", width = 1),
label = ['Equity',
'Global Equity',
'Tier 1',
'A looooooooooong',
'Tier 2',
'B looooooooooong',
'C looooooooooong',
'Tier 3',
'D looooooooooong',
'E looooooooooong',
'F looooooooooong',
'G looooooooooong',
'H looooooooooong'],
color = ['aqua',
'aqua',
'yellow',
'orange',
'yellow',
'orange',
'orange',
'yellow',
'orange',
'orange',
'orange',
'orange',
'orange'],
),
link = dict(
source = [0, 2, 1, 4, 4, 2, 7, 7, 7, 7, 7, 4],
target = [1, 3, 2, 5, 6, 4, 8, 9, 10, 11, 12, 7],
value = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
color = ['aqua',
'yellow',
'aqua',
'yellow',
'yellow',
'aqua',
'yellow',
'yellow',
'yellow',
'yellow',
'yellow',
'aqua'],
hovertemplate='This link has total value %{value}<extra></extra>'
))])
fig.update_layout(title_text="Waterfall Diagram",
font_size=16,
plot_bgcolor='white',
paper_bgcolor='white')
fig.show()
Output looks like this:
Is there a way:
to make sure links in color aqua are always below the yellow ones? to visually separate them better - I am not sure why current set up show them in that order
to give more space between the links, spreading them out more? Especially I would need links and nodes not to overlap each other
spread out the aqua links even further? I.e. visually dissociate them from the others
to control where and how node labels are shown? I.e. to the right or below node, and also controlling the font for each node

For 1, 2, & 3, you can set node locations explicitly as follows:
import plotly.graph_objects as go
fig = go.Figure(go.Sankey(
arrangement = "snap",
node = {
"label": ["A", "B", "C", "D", "E", "F"],
"x": [0.2, 0.1, 0.5, 0.7, 0.3, 0.5], #these are fractions of the domain (0,1)
"y": [0.7, 0.5, 0.2, 0.4, 0.2, 0.3],
'pad':10}, # 10 Pixels
link = {
"source": [0, 0, 1, 2, 5, 4, 3, 5],
"target": [5, 3, 4, 3, 0, 2, 2, 3],
"value": [1, 2, 1, 1, 1, 1, 1, 2]}))
fig.show()
Example from the plotly docs.

Related

How to remove spaces between multiple colorbars in one figure

I am trying to plot three colorbars horizontally. I would like to remove the white spaces between the three colorbars. Is there a way to do this and/or to gradually adjust the space?
Code for reproduction:
import matplotlib as mpl
import matplotlib.pyplot as plt
fig, axes = plt.subplots(figsize=(8, 2), nrows=3, ncols=1, sharex=True, sharey=True)
fig.suptitle('Bar comparison')
# upper colorbar
bar1 = [['a', 0, 0.6], ['b', 0.6, 1.2], ['a', 1.2, 1.8], ['b', 1.8, 4]]
colors1 = ['yellow', 'blue', 'yellow', 'blue']
cmap1 = mpl.colors.ListedColormap(colors1)
bounds1 = [0] + [i[2] for i in bar1]
norm1 = mpl.colors.BoundaryNorm(bounds1, len(colors1))
plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap1, norm=norm1),
cax=axes[0],
ticks=[[0], [bar1[-1][2]]],
spacing='proportional',
orientation='horizontal')
# middle colorbar
bar2 = [['a', 0, 0.5], ['b', 0.5, 1], ['a', 1, 2], ['b', 2, 3.8], ['a', 3.8, 4]]
colors2 = ['yellow', 'blue', 'yellow', 'blue', 'yellow']
cmap2 = mpl.colors.ListedColormap(colors2)
bounds2 = [0] + [i[2] for i in bar2]
norm2 = mpl.colors.BoundaryNorm(bounds2, len(colors2))
plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap2, norm=norm2),
cax=axes[1],
ticks=[[0], [bar2[-1][2]]],
spacing='proportional',
orientation='horizontal')
# lower colorbar
bar3 = [['a', 0, 0.5], ['b', 0.5, 1], ['a', 1, 2], ['b', 2, 3.8], ['a', 3.8, 4]]
colors3 = ['green', 'green', 'green', 'green', 'red']
cmap3 = mpl.colors.ListedColormap(colors3)
bounds3 = [0] + [i[2] for i in bar3]
norm3 = mpl.colors.BoundaryNorm(bounds3, len(colors3))
plt.colorbar(mpl.cm.ScalarMappable(cmap=cmap3, norm=norm3),
cax=axes[2],
ticks=[[0], [bar3[-1][2]]],
spacing='proportional',
orientation='horizontal')
# Figure settings
# Hide x labels and tick labels for all but bottom plot.
for ax in axes:
ax.label_outer()
plt.show()
To remove the space between the color bars, you need to use hspace=0 using subplots_adjust(). Add this line to the code, just before plotting...
plt.subplots_adjust(hspace=0)

Seaborn Barplot - Inconsistence when displaying values

For a multi-group bar plot in Seaborn, I would like to add text which is reffered from the int_txt on top each of the bar plot.
However, the text is not placed as intended.
For example, the code below
import seaborn as sns
import pandas as pd
from matplotlib import pyplot as plt
# Create an example dataframe
data = {'pdvalue': [1, 1, 1, 1, 4, 4, 4, 4, 2, 2, 2, 2, 8, 8, 8, 8],
'xval': [0, 0, 0.5, 0.5, 0.2, 0, 0.2, 0.2, 0.3, 0.3, 0.4, 0.1, 1, 1.1, 3, 1],
'int_txt': [11, 14, 4, 5.1, 1, 2, 5.1, 1, 2, 4, 1, 3, 6, 6, 2, 3],
'group': ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd']}
df = pd.DataFrame(data)
df['int_txt'] = df['int_txt'].round(0).astype(int)
df=df.sort_values(by='pdvalue', ascending=True)
g = sns.barplot (data=df,x="pdvalue",y="xval",hue="group",)
for idx,p in enumerate(g.patches):
if p.get_height()!=0:
val_me=df['int_txt'][idx]
g.annotate(format(val_me, '.1f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 9),
textcoords = 'offset points')
plt.show()
will produced
Whereas, the expected output shall be something like
The appended text is based on the look-up table
and for any xval equal to zero, no text will be appended.
May I know where did I do wrong?
You didn't do anything wrong really. It's just sns plots the bars by hue first. To see this do:
for idx,p in enumerate(g.patches):
# annotate the enumeration
g.annotate(format(idx, '.1f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 9),
textcoords = 'offset points')
And you see (notice the enumeration on top)
One way around is to sort your data by hue column, then access with .iloc:
# sort by group first
df=df.sort_values(by=['group','pdvalue'], ascending=True)
g = sns.barplot (data=df,x="pdvalue",y="xval",hue="group",)
for idx,p in enumerate(g.patches):
if p.get_height()!=0:
# access with `iloc`, not `loc`
val_me=df['int_txt'].iloc[idx]
g.annotate(format(val_me, '.1f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 9),
textcoords = 'offset points')
And you would get the expected annotation:

Sharing Y-axis in a matplotlib subplots

I have been trying to create a matplotlib subplot (1 x 3) with horizontal bar plots on either side of a lineplot.
It looks like this:
The code for generating the above plot -
u_list = [2, 0, 0, 0, 1, 5, 0, 4, 0, 0]
n_list = [0, 0, 1, 0, 4, 3, 1, 1, 0, 6]
arr_ = list(np.arange(10, 11, 0.1))
data_ = pd.DataFrame({
'points': list(np.arange(0, 10, 1)),
'value': [10.4, 10.5, 10.3, 10.7, 10.9, 10.5, 10.6, 10.3, 10.2, 10.4][::-1]
})
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(20, 8))
ax1 = plt.subplot(1, 3, 1)
sns.barplot(u_list, arr_, orient="h", ax=ax1)
ax2 = plt.subplot(1, 3, 2)
x = data_['points'].tolist()
y = data_['value'].tolist()
ax2.plot(x, y)
ax2.set_yticks(arr_)
plt.gca().invert_yaxis()
ax3 = plt.subplot(1, 3, 3, sharey=ax1, sharex=ax1)
sns.barplot(n_list, arr_, orient="h", ax=ax3)
fig.tight_layout()
plt.show()
Edit
How do I share the y-axis of the central line plot with the other horizontal bar plots?
I would set the limits of all y-axes to the same range, set the ticks in all axes and than set the ticks/tick-labels of all but the most left axis to be empty. Here is what I mean:
from matplotlib import pyplot as plt
import numpy as np
u_list = [2, 0, 0, 0, 1, 5, 0, 4, 0, 0]
n_list = [0, 0, 1, 0, 4, 3, 1, 1, 0, 6]
arr_ = list(np.arange(10, 11, 0.1))
x = list(np.arange(0, 10, 1))
y = [10.4, 10.5, 10.3, 10.7, 10.9, 10.5, 10.6, 10.3, 10.2, 10.4]
fig, axs = plt.subplots(1, 3, figsize=(20, 8))
axs[0].barh(arr_,u_list,height=0.1)
axs[0].invert_yaxis()
axs[1].plot(x, y)
axs[1].invert_yaxis()
axs[2].barh(arr_,n_list,height=0.1)
axs[2].invert_yaxis()
for i in range(1,len(axs)):
axs[i].set_ylim( axs[0].get_ylim() ) # align axes
axs[i].set_yticks([]) # set ticks to be empty (no ticks, no tick-labels)
fig.tight_layout()
plt.show()
This is a minimal example and for the sake of conciseness, I refrained from mixing matplotlib and searborn. Since seaborn uses matplotlib under the hood, you can reproduce the same output there (but with nicer bars).

Drop down menu for Plotly graph

Here is my dataframe:
df = pd.DataFrame({"Date":["2020-01-27","2020-02-27","2020-03-27","2020-04-27", "2020-05-27", "2020-06-27", "2020-07-27",
"2020-01-27","2020-02-27","2020-03-27","2020-04-27", "2020-05-27", "2020-06-27", "2020-07-27"],
"A_item":[2, 8, 0, 1, 8, 10, 4, 7, 2, 15, 5, 12, 10, 7],
"B_item":[1, 7, 10, 6, 5, 9, 2, 5, 6, 1, 2, 6, 15, 8],
"C_item":[9, 2, 9, 3, 9, 18, 7, 2, 8, 1, 2, 8, 1, 3],
"Channel_type":["Chanel_1", "Chanel_1", "Chanel_1", "Chanel_1", "Chanel_1", "Chanel_1", "Chanel_1",
"Chanel_2", "Chanel_2", "Chanel_2", "Chanel_2", "Chanel_2", "Chanel_2", "Chanel_2"]
})
I want to plot a group Bar chart with the dropdown filter on the Channel_type col. That's what I am trying:
trace2 = go.Bar(x=df["Date"], y=df[["B_item"]])
trace3 = go.Bar(x=df["Date"], y=df[["C_item"]])
list_updatemenus = [{'label': 'All',
'method': 'update',
'args': [{'visible': [True, True]}, {'title': 'All'}]},
{'label': 'Chanel_1',
'method': 'update',
'args': [{'visible': [True, False]}, {'title': 'Chanel_1'}]},
{'label': 'Chanel_2',
'method': 'update',
'args': [{'visible': [False, True]}, {'title': 'Chanel_2'}]}]
data = [trace1,trace2,trace3]
layout=go.Layout(title='Distribution of Sales by Region',updatemenus=list([dict(buttons= list_updatemenus)]),width=1000,height=800,barmode='group')
fig = go.Figure(data,layout)
fig.show()
And not getting the desired output:Plot 1
As it filters the graph by the "A_item", "B_item" and "C_item" while I would like to filter it by the Channel_type col as mentioned.
So the ideal result would be the below graph, but with the dropdown menu that changes the graph based on Channel_type :
Plot 2
I am able to solve the problem with Ipywidgets in the Jupyter notebook, but it’s not really working for my particular task. Here is the code:
from plotly import graph_objs as go
import ipywidgets as w
from IPython.display import display
x = 'Date'
y1 = 'A_item'
y2 = 'B_item'
y3 = 'C_item'
trace1 = {
'x': df[x],
'y': df[y1],
'type': 'bar',
'name':'A_item'
}
trace2={
'x': df[x],
'y': df[y2],
'type': 'bar',
'name':'B_item'
}
trace3 = {
'x': df[x],
'y': df[y3],
'type': 'bar',
'name':'C_item',
}
data = [trace1, trace2, trace3]
# Create layout for the plot
layout=dict(
title='Channels',
width=1200, height=700, title_x=0.5,
paper_bgcolor='#fff',
plot_bgcolor="#fff",
xaxis=dict(
title='Date',
type='date',
tickformat='%Y-%m-%d',
gridcolor='rgb(255,255,255)',
zeroline= False,
),
yaxis=dict(
title='My Y-axis',
zeroline= False
)
)
fig = go.FigureWidget(data=data, layout=layout)
def update_fig(change):
aux_df = df[df.Channel_type.isin(change['new'])]
with fig.batch_update():
for trace, column in zip(fig.data, [y1, y2, y3]):
trace.x = aux_df[x]
trace.y = aux_df[column]
drop = w.Dropdown(options=[
('All', ['Chanel_1', 'Chanel_2']),
('Chanel_1', ['Chanel_1']),
('Chanel_2', ['Chanel_2']),
])
drop.observe(update_fig, names='value')
display(w.VBox([drop, fig]))
And here is the output:
The problem is that I am not able to wrap the VBox into an HTML file and save the dropdown menu. Also, it isn’t working in the Python shell as it is intended for the Jupyter notebook, and I need to share it.
So the ideal result would be to wrap the last figure within the Plotly fig only without the ipywidgets.
Any help be really appreciated!
Thank you!
The most important thing to note is that for go.Bar, if you have n dates in the x parameter and you pass a 2D array of dimension (m, n) to the y parameter of go.Bar, Plotly understands to create a grouped bar chart with each date n having m bars.
For your DataFrame, something like df[df['Channel_type'] == "Channel_1"][items].T.values will reshape it as needed. So we can apply this to the y field of args that we pass the to the buttons we make.
Credit to #vestland for the portion of the code making adjustments to the buttons to make it a dropdown.
import pandas as pd
import plotly.graph_objects as go
df = pd.DataFrame({"Date":["2020-01-27","2020-02-27","2020-03-27","2020-04-27", "2020-05-27", "2020-06-27", "2020-07-27",
"2020-01-27","2020-02-27","2020-03-27","2020-04-27", "2020-05-27", "2020-06-27", "2020-07-27"],
"A_item":[2, 8, 0, 1, 8, 10, 4, 7, 2, 15, 5, 12, 10, 7],
"B_item":[1, 7, 10, 6, 5, 9, 2, 5, 6, 1, 2, 6, 15, 8],
"C_item":[9, 2, 9, 3, 9, 18, 7, 2, 8, 1, 2, 8, 1, 3],
"Channel_type":["Channel_1", "Channel_1", "Channel_1", "Channel_1", "Channel_1", "Channel_1", "Channel_1",
"Channel_2", "Channel_2", "Channel_2", "Channel_2", "Channel_2", "Channel_2", "Channel_2"]
})
fig = go.Figure()
colors = ['#636efa','#ef553b','#00cc96']
items = ["A_item","B_item","C_item"]
for item, color in zip(items, colors):
fig.add_trace(go.Bar(
x=df["Date"], y=df[item], marker_color=color
))
# one button for each df column
# slice the DataFrame and apply transpose to reshape it correctly
updatemenu= []
buttons=[]
for channel in df['Channel_type'].unique():
buttons.append(dict(method='update',
label=channel,
args=[{
'y': df[df['Channel_type'] == channel][items].T.values
}])
)
## add a button for both channels
buttons.append(dict(
method='update',
label='Both Channels',
args=[{
'y': df[items].T.values
}])
)
# some adjustments to the updatemenu
# from code by vestland
updatemenu=[]
your_menu=dict()
updatemenu.append(your_menu)
updatemenu[0]['buttons']=buttons
updatemenu[0]['direction']='down'
updatemenu[0]['showactive']=True
fig.update_layout(updatemenus=updatemenu)
fig.show()

Remove font's shadow in Sankey

Is it possible to remove the white shadow of the font in the following sankey diagram?
import plotly.graph_objects as go
fig = go.Figure(go.Sankey(
arrangement = "snap",
node = {
"label": ["A", "B", "C", "D", "E", "F"],
"x": [0.2, 0.1, 0.5, 0.7, 0.3, 0.5],
"y": [0.7, 0.5, 0.2, 0.4, 0.2, 0.3],
'pad':10}, # 10 Pixels
link = {
"source": [0, 0, 1, 2, 5, 4, 3, 5],
"target": [5, 3, 4, 3, 0, 2, 2, 3],
"value": [1, 2, 1, 1, 1, 1, 1, 2]}))
fig.show()
It certainly seems to not be possible. You can edit some text attributes through f['data'][0]['textfont'] like:
sankey.Textfont({
'color': '#2a3f5f', 'family': '"Open Sans", verdana, arial, sans-serif', 'size': 10
})
And as you can see sankey.Textfont has no attribute that can edit the properties of the "shadow". I've tried setting other values for 'family' but the shadow persists no matter what. Another peculiar detail here seems to be that the color can't be changed directly either. Only 'size' and 'family'

Categories

Resources