How to create secondary y-axes from a plotly express facetted figure? - python

I would like to modify a facetted plotly.express figure so that each trace has its own secondary y-axis. I don't want to re-create the figure from scratch using the standard Plotly-python api if possible. See exmaple below.
import plotly.express as px
input_df = px.data.tips()
fig = px.scatter(input_df,
x = 'total_bill',
y = 'tip',
color = 'day',
facet_row = 'smoker',
facet_col = 'sex',
)
fig.layout.width = 800
fig.show()
I would like to convert the above so each trace (or color) has its own secondary y-axis. So in this case, I would like 3 additional y-axes for each facet. This is my attempt but it doesn't work. There must be a better way. I would appreciate any ideas.
import plotly.graph_objects as go
yaxes = []
for trace in fig.data:
yaxisLabel = trace['yaxis']
if trace['yaxis'] in yaxes:
if yaxisLabel == 'y':
axisnumber = 0
else:
axisnumber = int(trace['yaxis'][1:])
newAxis_num = axisnumber + 100 * yaxes.count(yaxisLabel)
exec(f"fig.layout.update(yaxis{newAxis_num} = go.layout.YAxis(overlaying='y', side='right'))")
trace.update({'yaxis': f'y{newAxis_num}'})
yaxes.append(yaxisLabel)

Related

Connecting data points with lines in a Plotly boxplot in Python

I am working on some boxplots. I found this code very helpful and I managed to replicate it for my needs:
import plotly.express as px
import numpy as np
import pandas as pd
np.random.seed(1)
y0 = np.random.randn(50) - 1
y1 = np.random.randn(50) + 1
df = pd.DataFrame({'graph_name':['trace 0']*len(y0)+['trace 1']*len(y1),
'value': np.concatenate([y0,y1],0),
'color':np.random.choice([0,1,2,3,4,5,6,7,8,9], size=100, replace=True)}
)
fig = px.strip(df,
x='graph_name',
y='value',
color='color',
stripmode='overlay')
fig.add_trace(go.Box(y=df.query('graph_name == "trace 0"')['value'], name='trace 0'))
fig.add_trace(go.Box(y=df.query('graph_name == "trace 1"')['value'], name='trace 1'))
fig.update_layout(autosize=False,
width=600,
height=600,
legend={'traceorder':'normal'})
fig.show()
I am now trying to put some lines connecting the datapoints with the same colors, but I am lost. Any idea?
Something similar to this:
My first idea was to add lines to your figure by using plotly shapes and specifying the start and end points in x- and y-axis coordinates. However, when you use px.strip, plotly implements jittering (adding randomly generated small values, say between -0.1 and 0.1, to the x-coordinates under the hood to avoid points overlapping), but as far as I know, there is no way to retrieve the exact x-coordinates of each point.
However we can get around this by using go.Scatter to plot all the paired points individually, adding jittering as needed to the x-values and connecting each pair of points with a line. We are basically implementing px.strip ourselves but with full control of the exact coordinates of each point.
In order to toggle colors the same way that px.strip allows you to, we need to assign all points of the same color to the same legendgroup, and also only show the legend entry the first time a color is plotted (as we don't want an legend entry for each point)
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import pandas as pd
np.random.seed(1)
y0 = np.random.randn(50) - 1
y1 = np.random.randn(50) + 1
## sort both sets of data so we can easily connect them with line annotations
y0.sort()
y1.sort()
df = pd.DataFrame({'graph_name':['trace 0']*len(y0)+['trace 1']*len(y1),
'value': np.concatenate([y0,y1],0)}
# 'color':np.random.choice([0,1,2,3,4,5,6,7,8,9], size=100, replace=True)}
)
fig = go.Figure()
## i will set jittering to 0.1
x0 = np.array([0]*len(y0)) + np.random.uniform(-0.1,0.1,len(y0))
x1 = np.array([1]*len(y0)) + np.random.uniform(-0.1,0.1,len(y0))
## px.colors.sequential.Plasma contains 10 distinct colors
## colors_list = np.random.choice(px.colors.qualitative.D3, size=50)
## for simplicity, we repeat it 5 times instead of selecting randomly
## this guarantees the colors appear in order in the legend
colors_list = px.colors.qualitative.D3*5
color_number = {i:color for color,i in enumerate(px.colors.qualitative.D3)}
## keep track of whether the color is showing up for the first time as we build out the legend
colors_legend = {color:False for color in colors_list}
for x_start,x_end,y_start,y_end,color in zip(x0,x1,y0,y1,colors_list):
## if the color hasn't been added to the legend yet, add a legend entry
if colors_legend[color] == False:
fig.add_trace(
go.Scatter(
x=[x_start,x_end],
y=[y_start,y_end],
mode='lines+markers',
marker=dict(color=color),
line=dict(color="rgba(100,100,100,0.5)"),
legendgroup=color_number[color],
name=color_number[color],
showlegend=True,
hoverinfo='skip'
)
)
colors_legend[color] = True
## otherwise omit the legend entry, but add it to the same legend group
else:
fig.add_trace(
go.Scatter(
x=[x_start,x_end],
y=[y_start,y_end],
mode='lines+markers',
marker=dict(color=color),
line=dict(color="rgba(100,100,100,0.5)"),
legendgroup=color_number[color],
showlegend=False,
hoverinfo='skip'
)
)
fig.add_trace(go.Box(y=df.query('graph_name == "trace 0"')['value'], name='trace 0'))
fig.add_trace(go.Box(y=df.query('graph_name == "trace 1"')['value'], name='trace 1'))
fig.update_layout(autosize=False,
width=600,
height=600,
legend={'traceorder':'normal'})
fig.show()

How to add labels to plotly Box chart like Scatter chart?

I couldn't find the way to add text labels to plotly/dash box plot like you could add it to a scatterplot. In the example below, for ScatterPlot x=qty, y=price and you can then add Salesperson to the graph when the cursor is on Marker. For adding this I use the 'text' argument.
In the second example for BoxPlot when x=date, y=price I want to add salesperson in the same way. It would be very useful in case of outliers to see immediately who was the salesperson for that purchase. I looked in the documentation, but there is no clue. I assume it's not possible but still decided to try my luck here.
scatterplot:
import plotly.offline as pyo
import plotly.graph_objs as go
purchase={'date':['11/03/2021','12/03/2021','14/03/2021','11/03/2021'],
'price':[300, 400,200, 200],
'currency':['eur', 'usd','usd','usd'],
'qty':[200, 300, 400, 500],
'salesman':['AC', 'BC', "CC", 'DC']}
pur=pd.DataFrame(purchase)
pur
data = [go.Scatter(
x = pur['qty'],
y = pur['price'],
mode = 'markers',
text=pur['salesman'],
marker = dict(
size = 12,
color = 'rgb(51,204,153)',
symbol = 'pentagon',
line = dict(
width = 2,
)
)
)]
layout = go.Layout(
title = 'Random Data Scatterplot',
xaxis = dict(title = 'Some random x-values'),
yaxis = dict(title = 'Some random y-values'),
hovermode ='closest'
)
fig = go.Figure(data=data, layout=layout)
fig.show()
boxplot:
import plotly.offline as pyo
import plotly.graph_objs as go
x = pur['date']
y = pur['price']
data = [
go.Box(
y=y,
x=x,
text=pur['salesman']
)
]
layout = go.Layout(
title = 'box_plot'
)
fig = go.Figure(data=data, layout=layout)
fig.show()
The data you currently have is not suitable for boxplot. If you try to plot a boxplot with your data, the list [300, 400,200, 200] is used only once for the first date. For the other dates, there is no data.
I will show a simpler example with my own data.
dataset.csv
salesman,sales
alan,1.8
bary,2.3
copa,4.2
dac,1.19
eila,2.3
foo,2.5
gary,0.1
holland,10
code
import plotly.graph_objs as go
import pandas as pd
import plotly.io as pio
pio.renderers.default = 'browser'
df = pd.read_csv("deletelater")
fig = go.Figure()
fig.add_trace(go.Box(
y=df["sales"],
name='12/12/22',
customdata=df["salesman"],
hovertemplate='<b>sales: %{y}</b><br>salesperson: %{customdata}'
))
fig.show()
Diagram
As you can see, the name of the outlier salesperson is displayed on the hover label.

How to highlight a single data point on a scatter plot using plotly express

In a scatter plot created using px.scatter, how do I mark one data point with a red star?
fig = px.scatter(df, x="sepal_width", y="sepal_length")
# Now set a single data point to color="red", symbol="star".
This isn't really highlighting an already existing data point within a trace you've already produced, but rather adding another one with a different visual appearance. But it does exactly what you're looking for:
fig.add_trace(go.Scatter(x=[3.5], y=[6.5], mode = 'markers',
marker_symbol = 'star',
marker_size = 15))
Plot:
Complete code:
import plotly.express as px
import pandas as pd
import plotly.graph_objects as go
df = px.data.iris() # iris is a pandas DataFrame
fig = px.scatter(df, x="sepal_width", y="sepal_length")
fig.add_trace(go.Scatter(x=[3.5], y=[6.5], mode = 'markers',
marker_symbol = 'star',
marker_size = 15))
fig.show()
This directly modifies the Scatter trace's Marker itself:
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length")
trace = next(fig.select_traces())
# Modify kth point.
n = len(trace.x)
k = 136
color = [trace.marker.color] * n
color[k] = "red"
size = [8] * n
size[k] = 15
symbol = [trace.marker.symbol] * n
symbol[k] = "star"
# Update trace.
trace.marker.color = color
trace.marker.size = size
trace.marker.symbol = symbol
# Alternatively, call:
# fig.update_traces(marker=dict(color=color, size=size, symbol=symbol))
fig.show()

How to format plotly legend when using marker color?

I want to follow up on this post: Plotly: How to colorcode plotly graph objects bar chart using Python?.
When using plotly express, and specifying 'color', the legend is correctly produced as seen in the post by vestland.
This is my plotly express code:
data = {'x_data': np.random.random_sample((5,)),
'y_data': ['A', 'B', 'C', 'D', 'E'],
'c_data': np.random.randint(1, 100, size=5)
}
df = pd.DataFrame(data=data)
fig = px.bar(df,
x='x_data',
y='y_data',
orientation='h',
color='c_data',
color_continuous_scale='YlOrRd'
)
fig.show()
But when using go.Bar, the legend is incorrectly displayed as illustrated here:
This is my code using graph objects:
bar_trace = go.Bar(name='bar_trace',
x=df['x_data'],
y=df['y_data'],
marker={'color': df['c_data'], 'colorscale': 'YlOrRd'},
orientation='h'
)
layout = go.Layout(showlegend=True)
fig = go.FigureWidget(data=[bar_trace], layout=layout)
fig.show()
I'm learning how to use FigureWidget and it seems it can't use plotly express so I have to learn how to use graph objects to plot. How do I link the legend to the data such that it works like the plotly express example in vestland's post.
This really comes down to understanding what a high level API (plotly express) does. When you specify color in px if it is categorical it creates a trace per value of categorical. Hence the below two ways of creating a figure are mostly equivalent. The legend shows an item for each trace, not for each color.
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
df = pd.DataFrame({"x":np.linspace(0,10,10), "y":np.linspace(5,15,10), "color":np.random.choice(list("ABCD"),10)})
px.bar(df, x="x", y="y", color="color", orientation="h").show()
fig = go.Figure()
for g in df.groupby("color"):
fig.add_trace(go.Bar(x=g[1]["x"], y=g[1]["y"], name=g[0], orientation="h"))
fig
supplementary based on comments
you do not have to use graph objects if you are using FigureWidget() as demonstrated by second figure, create with plotly express and then generate FigureWidget()
for continuous data normal pattern is to use a single trace and a colorbar (also demonstrated in second figure). However if you want a discrete legend, create a trace per value in c_data and use https://plotly.com/python-api-reference/generated/plotly.colors.html sample_colorscale()
import plotly.express as px
import plotly.colors
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# simulate data frame...
df = pd.DataFrame(
{
"x_data": np.linspace(0, 10, 10),
"y_data": np.linspace(5, 15, 10),
"c_data": np.random.randint(0, 4, 10),
}
)
# build a trace per value in c_data using graph objects ... correct legend !!??
bar_traces = [
go.Bar(
name="bar_trace",
x=d["x_data"],
y=d["y_data"],
marker={
"color": plotly.colors.sample_colorscale(
"YlOrRd",
d["c_data"] / df["c_data"].max(),
)
},
orientation="h",
)
for c, d in df.groupby("c_data")
]
layout = go.Layout(showlegend=True)
fig = go.FigureWidget(data=bar_traces, layout=layout)
fig.show()
fig = px.bar(
df,
x="x_data",
y="y_data",
color="c_data",
orientation="h",
color_continuous_scale="YlOrRd",
)
fig = go.FigureWidget(data=fig.data, layout=fig.layout)
fig.show()

Generate random Colors in Bar chart with Plotly Python

I am using Plotly for Python to generate some stacked bar charts. Since I have 17 objects which are getting stacked, the colour of the bars has started repeating as seen in the image below.
Can someone tell me how to get unique colours for each stack?
Please find my code to generate the bar chart below:
import plotly
plotly.tools.set_credentials_file(username='xxxxxxxx',
api_key='********')
dd = []
import plotly.plotly as py
import plotly.graph_objs as go
import numpy as np
for k,v in new_dict.items():
trace = go.Bar(x = x['unique_days'],
y = v,
name = k,
text=v,
textposition = 'auto',
)
dd.append(trace)
layout= go.Layout(
title= 'Daily Cumulative Spend per campaign',
hovermode= 'closest',
autosize= True,
width =5000,
barmode='stack',
xaxis= dict(
title= 'Date',
zeroline= False,
gridwidth= 0,
showticklabels=True,
tickangle=-45,
nticks = 60,
ticklen = 5
),
yaxis=dict(
title= 'Cumulative Spend($)',
ticklen= 5,
gridwidth= 2,
),
showlegend= True
)
fig = dict(data=dd, layout = layout)
py.iplot(fig)
It was the issue which I have been facing in this week and I solved with Matplotlib module. Here is my code:
import matplotlib, random
hex_colors_dic = {}
rgb_colors_dic = {}
hex_colors_only = []
for name, hex in matplotlib.colors.cnames.items():
hex_colors_only.append(hex)
hex_colors_dic[name] = hex
rgb_colors_dic[name] = matplotlib.colors.to_rgb(hex)
print(hex_colors_only)
# getting random color from list of hex colors
print(random.choice(hex_colors_only))
There are 148 colors in the list and you can integrate this list with your wish. Hopefully it is useful for someone :)
the same as above, short version:
import matplotlib, random
colors = dict(matplotlib.colors.cnames.items())
hex_colors = tuple(colors.values())
print(hex_colors)
#getting a random color from the dict
print(random.choice(hex_colors))

Categories