Plotly go.Bar : Add custom legend labels based on values - python

I have a dataframe with positive and negative values in one column. I am using plotly barplot, and I'd like customize legend labels based on the value.
Here's a mock pandas DataFrame:
df = pd.DataFrame({'Date': [07-2020, 08-2020, 09-2020, 10-2020],
'Value': [3, -2, 4, -1] })
df["Color"] = np.where(df["Value"]<0, 'rgb(0,0,255)', 'rgb(255,0,0)')
df["Name"] = np.where(df["Value"]<0, 'Low', 'High')
fig = go.Figure(
data=[
go.Bar(
x=df["Date"],
y=df["Value"],
color=df['Name'],
marker_color=df['Color']
),
],
layout=go.Layout(
xaxis=dict(
tickangle=60,
tickfont=dict(family="Rockwell", color="crimson", size=14)
),
yaxis=dict(
title="Net Change",
showticklabels=True
),
barmode="stack",
)
)
How do I add legend labels Low when value is negative and High when positive?

I wasn't sure if your legend label was a legend or an annotation label, so I added support for both. To annotate a bar chart, you can specify it in the text The display position will automatically determine the location. To add high and low to the legend, I created a high data frame and a low data frame and gave each a name. As a layout, we specify the tick positions and display names in order to arrange them in data frame order.
import pandas as pd
import plotly.graph_objects as go
import numpy as np
df = pd.DataFrame({'Date': ['07-2020', '08-2020', '09-2020', '10-2020'], 'Value': [3, -2, 4, -1] })
df["Color"] = np.where(df["Value"]<0, 'rgb(0,0,255)', 'rgb(255,0,0)')
df["Name"] = np.where(df["Value"]<0, 'Low', 'High')
df_high = df[df['Name'] == 'High']
df_Low = df[df['Name'] == 'Low']
fig = go.Figure(data=[
go.Bar(
x=[0,2],
y=df_high["Value"],
text=df_high["Name"],
textposition='auto',
name='High',
marker_color=df_high['Color']
),],)
fig.add_trace(
go.Bar(
x=[1,3],
y=df_Low["Value"],
text=df_Low["Name"],
textposition='auto',
name='Low',
marker_color=df_Low['Color'])
)
fig.update_layout(
xaxis=dict(
tickangle=60,
tickfont=dict(family="Rockwell", color="crimson", size=14),
tickvals=[0,1,2,3],
ticktext=df['Date']
),
yaxis=dict(
title="Net Change",
showticklabels=True
),
barmode="stack",
)
fig.show()

Related

Plotly timeline with objects

In the below example, I would like to group the elements of y axis by continent, and to display the name of the continent at the top of each group. I can't figure out in the layout where we can set it. the example come from this plotly page
import pandas as pd
import plotly.graph_objects as go
from plotly import data
df = data.gapminder()
df = df.loc[ (df.year.isin([1987, 2007]))]
countries = (
df.loc[ (df.year.isin([2007]))]
.sort_values(by=["pop"], ascending=True)["country"]
.unique()
)[5:-10]
data = {"x": [], "y": [], "colors": [], "years": []}
for country in countries:
data["x"].extend(
[
df.loc[(df.year == 1987) & (df.country == country)]["pop"].values[0],
df.loc[(df.year == 2007) & (df.country == country)]["pop"].values[0],
None,
]
)
data["y"].extend([country, country, None]),
data["colors"].extend(["cyan", "darkblue", "white"]),
data["years"].extend(["1987", "2007", None])
fig = go.Figure(
data=[
go.Scatter(
x=data["x"],
y=data["y"],
mode="lines",
marker=dict(
color="grey",
)),
go.Scatter(
x=data["x"],
y=data["y"],
text=data["years"],
mode="markers",
marker=dict(
color=data["colors"],
symbol=["square","circle","circle"]*10,
size=16
),
hovertemplate="""Country: %{y} <br> Population: %{x} <br> Year: %{text} <br><extra></extra>"""
)
]
)
To show grouping by continent instead of the code you showed would require looping through the data structure from dictionary format to data frame. y-axis by continent by specifying a multi-index for the y-axis.
I have limited myself to the top 5 countries by continent because the large number of categorical variables on the y-axis creates a situation that is difficult to see for visualization. You can rewrite/not set here according to your needs. Furthermore, in terms of visualization, I have set the x-axis type to log format because the large discrepancies in the numbers make the visualization weaker. This is also something I added on my own and you can edit it yourself.
import pandas as pd
import plotly.graph_objects as go
from plotly import data
df = data.gapminder()
df = df.loc[(df.year.isin([1987, 2007]))]
# top5 by continent
countries = (df.loc[df.year.isin([2007])]
.groupby(['continent',], as_index=False, sort=[True])[['country','pop']].head()['country']
)
df = df[df['country'].isin(countries.tolist())]
fig = go.Figure()
for c in df['continent'].unique():
dff = df.query('continent == #c')
#print(dff)
for cc in dff['country'].unique():
dfc = dff.query('country == #cc')
fig.add_trace(go.Scatter(x=dfc['pop'].tolist(),
y=[dfc['continent'],dfc['country']],
mode='lines+markers',
marker=dict(
color='grey',
))
)
fig.add_trace(go.Scatter(x=dfc['pop'].tolist(),
y=[dfc['continent'],dfc['country']],
text=dfc["year"],
mode="markers",
marker=dict(
color=["cyan", "darkblue", "white"],
size=16,
))
)
fig.update_layout(autosize=False, height=800, width=800, showlegend=False)
fig.update_xaxes(type='log')
fig.show()

Adding a secondary axis in Plotly Python

I'm working with a Dash graph object and I'm fairly new to it. I'm attempting to pass in a graph that has 2 scatter charts and a bar chart on the same figure but I'd like the bar chart (green) to be on it's own secondary y axis so it looks better than it does here:
Now from what I understand about Dash, I have to pass a go.Figure() object so I have a function which defines the data and the layout. I saw in the plotly documentation that you can use plotly express add secondary axis but I'm not sure how to do that within my frame work here. Any help would be greatly appreciated!
Here's my code:
def update_running_graph(n_intervals):
df = pd.read_csv(filename)
trace1 = go.Scatter(x=df['Timestamp'],
y=df['CLE'],
name='Crude',
mode='lines+markers')
trace2 = go.Scatter(x=df['Timestamp'],
y=df['y_pred'],
name='Model',
mode='lines+markers')
trace3 = go.Bar(x=df['Timestamp'],
y=df['ModelDiff'],
name='Diff',
)
data = [trace1, trace2,trace3]
layout = go.Layout(title='CLE vs Model')
return go.Figure(data=data, layout=layout)
To add a secondary y-axis in dash you could do the following:
def update_running_graph(n_intervals):
df = pd.read_csv(filename)
trace1 = go.Scatter(x=df['Timestamp'],
y=df['CLE'],
name='Crude',
mode='lines+markers',
yaxis='y1')
trace2 = go.Scatter(x=df['Timestamp'],
y=df['y_pred'],
name='Model',
mode='lines+markers',
yaxis='y1')
trace3 = go.Bar(x=df['Timestamp'],
y=df['ModelDiff'],
name='Diff',
yaxis='y2'
)
data = [trace1, trace2,trace3]
layout = go.Layout(title='CLE vs Model',
yaxis=dict(title='Crude and Model'),
yaxis2=dict(title='Moddel Difference',
overlaying='y',
side='right'))
return go.Figure(data=data, layout=layout)
you can add more y-axis they always need to have the form of yi with i the i-th axis. Then in the layout you can specify the layout of the i-th axis with yaxisi=dict(...).
This documentation page should be of use. Just modify to fit your code, since trace1 and trace2 appear to be on the same scale, just set trace3 to the secondary axis scale and you should be set. Below is an example with just only 2 but adding a third should not be too difficult.
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Create figure with secondary y-axis
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add traces
fig.add_trace(
go.Scatter(x=[1, 2, 3], y=[40, 50, 60], name="yaxis data"),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=[2, 3, 4], y=[4, 5, 6], name="yaxis2 data"),
secondary_y=True,
)
# Add figure title
fig.update_layout(
title_text="Double Y Axis Example"
)
# Set x-axis title
fig.update_xaxes(title_text="xaxis title")
# Set y-axes titles
fig.update_yaxes(title_text="<b>primary</b> yaxis title", secondary_y=False)
fig.update_yaxes(title_text="<b>secondary</b> yaxis title", secondary_y=True)
fig.show()
Cheers!

how to set the vertical scale of pyplot linear figure

I have a code that displays geologic well log in python using plotly,
the scale is linear and I set the range of it based on the dataframe index
I need to keep the scale as linear but want to set the vertical scale or the y-axis scale for example to be 1:100 how to achieve that
layout = go.Layout(
title='VSH',
autosize=False,
width=500,
height=1500,
yaxis=dict(
title='DEPT',
showgrid=True,
showticklabels=True,
gridcolor='#bdbdbd',
gridwidth=2
),
xaxis=dict(
title='Vsh',
showgrid=True,
showticklabels=True,
gridcolor='#bdbdbd',
gridwidth=2
)
)
df = df.loc[(df.index >= int(top)) & (df.index <= int(base))]
trace1 = go.Scatter(x = df['Vsh']/10 , y = df.index , mode='lines')
fig = go.Figure(data=[trace1] , layout = layout)
iplot(fig)
Just create a list (for example, from 1 to 100):
list1 = [i for i in range(0, 101)]
and added two parameters to yaxis in layout:
yaxis=dict(tickvals=[i for i in range(len(list1))],
ticktext=list1)
You can read more about ticktext and tickvals in plotly docs: 1 and 2.

Plotly deactivate x axis sorting

I want to plot a bar chart. On the x-axis are IDs of consultants. They range between 1000 and 2000. Each consultant has a specific number of customers (y-axis).
Now I want to plot a bar chart in plotly. But plotly orders the consultant IDs ascending and interprets them as integer, but they are not. They shall be ordered like the list I give plotly.
By the way in matplotlib the order is right.
trace1 = go.Bar(
x=consultants,
y=info[0,:]
)
trace2 = go.Bar(
x=consultants,
y=info[1,:],
)
trace3 = go.Bar(
x=consultants,
y=info[2,:]
)
trace4 = go.Bar(
x=consultants,
y=info[3,:]
)
data = [trace1, trace2, trace3, trace4]
layout = go.Layout(
barmode='stack',
xaxis=dict(
categoryorder='array',
categoryarray=consultants,
titlefont=dict(
size=18,
color='black'),
showticklabels=True,
tickfont=dict(
size=16,
color='black',
),
tickangle=20
),
yaxis=dict(
title='Number of customers',
titlefont=dict(
size=18,
color='black'),
showgrid=True,
showline=False,
showticklabels=True,
tickfont=dict(
size=16,
color='black')
),
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='stacked-bar')
The lastest version of Plotly now has a variable in the layout options to specify a categorical layout for the X axis:
fig.update_layout(
xaxis_type = 'category'
)
Interestingly Plotly seems to ignore categoryorder for integers but disabling of sorting can be achieved by passing type='category in xaxis in layout.
type ( enumerated : "-" | "linear" | "log" | "date" | "category" )
default: "-"
Sets the axis type. By default, plotly attempts to
determined the axis type by looking into the data of the traces that
referenced the axis in question.
import plotly
import plotly.graph_objs as go
import numpy as np
plotly.offline.init_notebook_mode()
consultants = [1, 3, 2, 5, 4]
info = np.random.randint(100, size=(5,5))
data = []
for i in range(len(info)):
data.append(go.Bar(x=consultants,
y=info[i,:]))
layout = go.Layout(barmode='stack',
xaxis=dict(type='category'),
yaxis=dict(title='Number of customers'))
fig = go.Figure(data=data, layout=layout)
plotly.offline.iplot(fig, filename='stacked-bar')

Subplots with two y axes (each) - plotly and python/pandas

Are there guidelines on how to set up secondary Y-axes in python for plotly?
I am assinging axis style through an iterative loop, as follows:
all_plots = ['plot1','plot2'...'plot20']
fig = tools.make_subplots(rows=nrow, cols=ncol, shared_xaxes=False, shared_yaxes=False, subplot_titles=all_plots)
for i in all_plots:
fig['layout']['yaxis'+str(j)].update()
How does the assignment of y axes work?
If my subplot included, say, 4 rows and 5 columns for a total of 20 subplots, do I have to assume that plotly needs to receive odd and even numbers, meaning:
yaxis1 and yaxis2 for plot1
....
yaxis39 and yaxis40 for plot20
It is possible, to do this, but its not particularly intuitive. Take this example where I create a plot 2x2 subplots, and add a secondary y axis to the plot in position 2,2.
When you create a subplots, they are assigned y axes: "y1","y2","y3","y4" on the left side of each subplot. To a secondary y axes, you need to use fig['layout'].updateto create new axes "y5", "y6", "y7", "y8" which correspond to "y1","y2","y3","y4". So the bottom right subplot would have axes y4(right) and y8(left). In the example below, I only create a secondary y axis for the last plot, but expanding it to more/all the subplots is pretty straightforward.
It is important to note, that creating the secondary axes, and assigning it in trace5 doesn't automatically place it on the proper axes. You still have to manually assign it with fig['data'][4].update(yaxis='y'+str(8)) to plot it relative to the left axis.
fig = tools.make_subplots(rows=2, cols=2,subplot_titles=('Air Temperature', 'Photon Flux Density',
'Ground Temps','Water Table & Precip'))
fig['layout']['xaxis1'].update( range=[174, 256])
fig['layout']['xaxis3'].update(title='Day of Year', range=[174, 256])
fig['layout']['yaxis1'].update(title='Degrees C',range=[-5,30])
fig['layout']['yaxis2'].update(title='mmol m<sup>-2</sup> m<sup>-d</sup>', range=[0, 35])
fig['layout']['yaxis3'].update(title='Ground Temps', range=[0, 11])
fig['layout']['yaxis4'].update(title='depth cm', range=[-20, 0])
fig['layout']['yaxis8'].update(title='rainfall cm', range=[0, 1.6])
fig['layout'].update(showlegend=False, title='Climate Conditions')
# In this example, I am only doing it for the last subplot, but if you wanted to do if for all,
# Just change to range(1,5)
for k in range(4,5):
fig['layout'].update({'yaxis{}'.format(k+4): dict(anchor='x'+str(k),
overlaying='y'+str(k),
side='right',
)
})
trace1 = go.Scatter(
y=Daily['AirTC_Avg'],
x=Daily.index,
marker = dict(
size = 10,
color = 'rgba(160, 0, 0, .8)',),
error_y=dict(
type='data',
array=Daily_Max['AirTC_Avg']-Daily_Min['AirTC_Avg'],
visible=True,
color = 'rgba(100, 0, 0, .5)',
),
name = 'Air Temp'
)
trace2 = go.Bar(
y=Daily['PPFD']/1000,
x=Daily.index,
name='Photon Flux',
marker=dict(
color='rgb(180, 180, 0)'
),
yaxis='y2',
)
trace3 = go.Scatter(
y=Daily['Temp_2_5_1'],
x=Daily.index,
name='Soil Temp',
marker=dict(
color='rgb(180, 0, 0)'
),
yaxis='y3',
)
trace4 = go.Scatter(
y=Daily['Table_1']*100,
x=Daily.index,
name='Water Table',
marker=dict(
color='rgb(0, 0, 180)'
),
yaxis='y4',
)
trace5 = go.Bar(
y=Daily['Rain']/10,
x=Daily.index,
name='Rain',
marker=dict(
color='rgb(0, 100, 180)'
),
yaxis='y8',
)
fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)
fig.append_trace(trace5, 2, 2)
## This part is important!!! you have to manually assing the data to the axis even
# though you do it when defining trace 5
fig['data'][4].update(yaxis='y'+str(8))
plot(fig, filename='FI_Climate')
Not an exact answer but I thought it might help...
I like to use pandas and cufflinks. Here is an example of how to plot two sets of data from one dataframe (df) on a graph using a secondary y axis. The data from each axis is displayed in different formats in this example (scatter and bar). The data is arranged into columns beforehand.
import pandas as pd
import cufflinks as cf
from plotly.offline import download_plotlyjs, init_notebook_mode,plot,iplot
fig1 = df.iplot(kind='scatter', mode='lines+markers', x=['col1', 'col2'],
y=['col3', 'col4',],
asFigure=True)
fig2 = df.iplot(kind='bar', x=['col1', 'col2'],
y=['col3', 'col4', ],
secondary_y=['col5','col6'],asFigure=True)
fig2['data'].extend(fig1['data'])
The naming convention is y, y2, y3... y40, and you make the reference to the axis in the trace dict.
So your traces should be like...
trace0 = dict(
x = xvals,
y = yvals,
yaxis = 'y'
)
trace1 = dict(
x = x2vals,
y = y2vals,
yaxis = 'y2'
)
....
trace40 = dict(
x = x40vals,
y = y40vals,
yaxis = 'y40'
)

Categories

Resources