Is there a way to merge two stacked plotly.express.bar figures - python

I want to merge two stacked bar plot in plotly.express.
The code of the first figure is:
import pandas as pd
import plotly.express as px
from plotly.subplots import make_subplots
rscu1 = pd.read_csv("JQ038231.1_RSCU_stack.csv")
rscu2 = pd.read_csv("MG970255.1_RSCU_stack.csv")
rscu = pd.concat([rscu1, rscu2], keys=["JQ038231", "MG970255"])
rscu["species"] = rscu.index.get_level_values(0)
rscu = rscu.astype({"Fill": "category"})
rscu = rscu.astype({"aaRatio": "object"})
fig = px.bar(rscu, x="AA", y="RSCU", color="Fill", barmode = 'stack', text="aaRatio",
hover_data=['AA', "RSCU"], facet_row="species",
color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig.update_xaxes(tickangle=0, title=None, ticks="outside")
fig.update_layout(
autosize=False,
width=950,
height=450,
showlegend=False)
for data in fig.data:
data["width"] = 0.9
fig.update_traces(textposition='outside')
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='show')
These codes generate this figure:
The code of the second figure is:
fig_bottom = px.bar(rscu1, x="AA", y="Equality", color="Fill", barmode = 'stack', text="AA",
hover_data=['AA'], height=220, width=950,
color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig_bottom.update_traces(textposition='inside', textfont_size=14)
fig_bottom.update_layout(uniformtext_minsize=9, uniformtext_mode='show', showlegend=False,)
fig_bottom.update_layout({"plot_bgcolor": "rgba(0, 0, 0, 0)",
"paper_bgcolor": "rgba(0, 0, 0, 0)"})
fig_bottom.update_yaxes(title=None, showticklabels=False)
fig_bottom.update_xaxes(title=None, showticklabels=False)
for data in fig_bottom.data:
data["width"] = 0.9
These codes generate this figure:
Is there a way to merge them into one figure, the final figure (made by ggplot) is:
The data used here can be found in https://github.com/dongzhang0725/sample_data

Updated
As per previous answer you can make_subplots(), add_trace() and finally update_layout()
source from GitHub
import requests
import io
rscu1 = pd.read_csv(io.StringIO(requests.get("https://raw.githubusercontent.com/dongzhang0725/sample_data/main/JQ038231.1_RSCU_stack.csv").text))
rscu2 = pd.read_csv(io.StringIO(requests.get("https://raw.githubusercontent.com/dongzhang0725/sample_data/main/MG970255.1_RSCU_stack.csv").text))
solution
# want "bottom" figure to use it's own axis to add to subplots
fig_bottom.data[0]["xaxis"]="x3"
fig_bottom.data[0]["yaxis"]="y3"
# subplots figure...
figall = make_subplots(rows=3, row_heights=[.4,.4,.2])
# add all the traces to appropriate subplot
for f in fig.data:
if f["yaxis"] == "y":
figall.add_trace(f, row=1, col=1)
else:
figall.add_trace(f, row=2, col=1)
figall.add_trace(fig_bottom.data[0], row=3, col=1)
# copy / modify layout of subplots figure
figall.update_layout(fig.layout)
fh = .38 # height of first two charts
figall.update_layout(
yaxis={"domain":[(1-2*fh)-.03,(1-fh)-.03]},
yaxis2={"domain":[1-fh,1]},
xaxis3={"title": None, "showticklabels": False},
yaxis3={"title": None, "showticklabels": False},
height=fig.layout["height"] + fig_bottom.layout["height"],
)
figall.update_traces(marker_coloraxis=None)
# recenter annotations....
for i, a in enumerate(figall.layout["annotations"]):
a["y"] = (1-i*(fh+.03))-fh/2
figall

Thanks to Rob, according to his answer, I resolved my problem with the following codes:
import pandas as pd
import plotly.express as px
from plotly.subplots import make_subplots
# parameters
files = ["JQ038231.1_RSCU_stack.csv", "MG970255.1_RSCU_stack.csv"]
fig_n = len(files)
space_figs = 0.07
y_offset = 0.8
text_size = 12
width_ = 900
height_ = 700
bottom2fig_ratio = 0.4
row_heights_ = [1/(fig_n+bottom2fig_ratio)]*fig_n + [1/(fig_n+bottom2fig_ratio)*bottom2fig_ratio] # [1/2.5]*2 + [1/2.5*0.5] = [0.4, 0.4, 0.2]
# subplots figure...
figall = make_subplots(rows=fig_n+1, row_heights=row_heights_, vertical_spacing=space_figs, subplot_titles=files)
for num, file in enumerate(files):
row_ = num + 1
rscu = pd.read_csv(file)
rscu = rscu.astype({"Fill": "category"})
rscu = rscu.astype({"aaRatio": "object"})
max_rscu = rscu.groupby(by=["AA"]).sum().max()["RSCU"]
fig = px.bar(rscu, x="AA", y="RSCU", color="Fill", barmode = 'stack', text="aaRatio",
hover_data=['AA', "RSCU"], color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig.update_traces(textposition='outside') # show text to outside
# set xaxis style
if row_ != fig_n:
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", ticks="outside",
tickangle=0, title=None, showticklabels=False)
else:
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", ticks="outside",
title=None)
# set y range to show annotation text
fig.update_yaxes(showline=True, linewidth=1, linecolor="black", ticks="outside", range=[0, max_rscu + y_offset])
# add all the traces to appropriate subplot
for f in fig.data:
figall.add_trace(f, row=row_, col=1)
# to make fig's layout works in figall
fig.for_each_trace(lambda trace_: trace_.update(xaxis=f"x{row_}", yaxis=f"y{row_}"))
fig.layout[f"xaxis{row_}"] = fig.layout.pop("xaxis")
fig.layout[f"yaxis{row_}"] = fig.layout.pop("yaxis")
fig.layout[f"xaxis{row_}"]["anchor"] = f"y{row_}"
fig.layout[f"yaxis{row_}"]["anchor"] = f"x{row_}"
fig.layout[f"yaxis{row_}"].pop("domain") # otherwise it will affect figall's domain
figall.update_layout(fig.layout)
fig_bottom = px.bar(rscu, x="AA", y="Equality", color="Fill", barmode = 'stack', text="Codon",
hover_data=['AA'], color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig_bottom.update_traces(textposition='inside') # show text to inside
bottom_row = fig_n+1
fig_bottom.for_each_trace(lambda trace_: trace_.update(xaxis=f"x{bottom_row}", yaxis=f"y{bottom_row}"))
# add all the traces of bottom figure to appropriate subplot
for f in fig_bottom.data:
figall.add_trace(f, row=bottom_row, col=1)
dict_layout = {"barmode": "stack",
"autosize": False,
"showlegend": False,
"plot_bgcolor": "rgba(0, 0, 0, 0)",
"paper_bgcolor": "rgba(0, 0, 0, 0)",
"uniformtext_minsize": text_size,
"uniformtext_mode": "show",
"width": width_,
"height": height_}
# for bottom figure
dict_layout[f"yaxis{fig_n+1}"] = {"title": None, "showticklabels": False}
dict_layout[f"xaxis{fig_n+1}"] = {"title": None, "showticklabels": False}
figall.update_layout(dict_layout)
figall.for_each_annotation(lambda x: x.update(x=0.12, font={"size": 13, "family": "Arial", "color": "black"})) # adjust title of each sub-figure
figall.for_each_trace(lambda trace_: trace_.update(width=0.9)) # set bar width
figall.show()

Related

Hiding Duplicate Legend in Plotly

I'm new in using plotly and I'm trying to make a 2 different graph and show them individually through button; however, when I make it, the legends duplicated, resulting to a bad visualization of the data. Here's the code that I'm running right now:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly as ply
import plotly.express as px
import plotly.graph_objects as go
url = "https://raw.githubusercontent.com/m23chaffee/DS100-Repository/main/Aluminum%20Alloy%20Data%20Set.csv"
alloy = pd.read_csv('https://raw.githubusercontent.com/m23chaffee/DS100-Repository/main/Aluminum%20Alloy%20Data%20Set.csv')
del alloy['temper']
alloy = alloy.rename(columns={'aluminum_alloy':'Alloy Number',
'modulus_elastic': 'Elastic Modulus',
'modulus_shear': 'Shear Modulus',
'strength_yield': 'Yield Strength',
'strength_tensile': 'Tensile Strength'
})
bar1 = px.bar(alloy,
x = "Alloy Number",
y = ["Elastic Modulus", "Shear Modulus","Yield Strength","Tensile Strength"],
barmode = 'group',
width = 1100,
height =500,
orientation = 'v',
color_discrete_sequence = px.colors.qualitative.Pastel,
labels={"value": "Data Values"},
template = 'seaborn').update_traces(legendgroup="group").update_layout(showlegend=False)
line1 = px.line(alloy,
x = "Alloy Number",
y = ["Elastic Modulus", "Shear Modulus","Yield Strength","Tensile Strength"],
width = 1100,
height =500,
orientation = 'v',
color_discrete_sequence = px.colors.qualitative.Pastel,
labels={"value": "Data Values"},
template = 'seaborn').update_traces(legendgroup="group", visible = 'legendonly').update_layout(showlegend=False)
# Add buttom
fig.update_layout(
updatemenus=[
dict(
type = "buttons",
direction = "left",
buttons=list([
dict(
args=['type', 'bar'],
label="Bar Graph",
method="restyle",
),
dict(
args=["type", "line"],
label="Line Graph",
method="restyle"
)
]),
pad={"r": 10, "t": 10},
showactive=True,
x=0.11,
xanchor="left",
y=1.1,
yanchor="middle"
),
]
)
fig.show()
and the result of the image would look like this:
Result of the code above
Attempted Solution
I tried to hide it using traces and in the documentation but it seems it didn't work out for me. I also found a similar stackoverflow post 8 years ago, tried it, and it didn't make any changes in my graph.

how to add range to the secondary_y axis in plotly?

I want to create a graph where I want to add range to the secondary y-axis. The graph which I have right now is like this.
The code for this graph is like this:
import plotly.offline as pyo
import plotly.graph_objs as go
from plotly import tools
from plotly.subplots import make_subplots
import pandas as pd
import xlwings as xw
import logging
fileName = 'solar data.xlsx'
app = xw.App(visible=False)
try:
wb = app.books.open(fileName)
sheet = wb.sheets[4]
lastCell = sheet.range('A1').end('down').last_cell.row
solarOne = sheet.range('B2:B'+ str(lastCell)).value
bessOne = sheet.range('D2:D'+ str(lastCell)).value
socOne = sheet.range('E2:E'+ str(lastCell)).value
solarOne_Plus_bessOne = [i+j for i,j in zip(solarOne,bessOne)]
# solarTwo = sheet.range('F2:F' + str(lastCell)).value
# bessTwo = sheet.range('H2:H' + str(lastCell)).value
# socTwo = sheet.range('I2:I' + str(lastCell)).value
# solarTwo_Plus_bessTwo = [i + j for i, j in zip(solarTwo, bessTwo)]
except Exception as e:
logging.exception("Something awful happened!")
print(e)
finally:
app.quit()
app.kill()
fig = go.Figure()
projectTime = pd.date_range("2020-10-01 00:00:00", "2020-10-01 23:59:00", freq="1T")
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x = projectTime, y = solarOne, name = 'Solar', fill='tozeroy', line=dict(width=0.5, color='rgb(255,167,0)')), secondary_y=False)
fig.add_trace(go.Scatter(x = projectTime, y = bessOne, name = 'Storage', fill='tozeroy', line=dict(width=0.5, color='rgb(43, 201, 34)')), secondary_y=False)
fig.add_trace(go.Scatter(x = projectTime, y = socOne, name = 'SoC', fill='tozeroy', line=dict(width=0.5, color='rgb(250, 67, 67)')), secondary_y=True)
fig.add_trace(go.Scatter(x = projectTime, y = solarOne_Plus_bessOne, name = 'Solar + BESS', fill='tozeroy',), secondary_y=False)
# Add figure title
fig.update_layout(
title_text="Solar with 0.5MW 0.5Hour storage"
)
# Set x-axis title
fig.update_xaxes(title_text="Time")
# Set y-axes titles
fig.update_yaxes(title_text="Dispatch(MW) and SoC(MWh)")
pyo.plot(fig, filename='Solar with 0.5MW 0.5Hour storage example 1.html')
I have tried to add layout_yaxis_range=[-0.6, 0.7] in secondary y-axis but it throws an error
TypeError: add_trace() got an unexpected keyword argument 'layout_yaxis_range'
I want this secondary y-axis with the same x-axis. Can anyone please help?
This works for me in similar cases:
fig.update_yaxes(range=[-0.6, 0.7], secondary_y=True)

Not able to view US-states heatmap

I have written the following code to heat heatmap of US-States. But I am unable to get the output image in Google Colab.
State codes are two alphabet codes for a particular state of the US.
temp = pd.DataFrame(project_data.groupby("school_state")["project_is_approved"].apply(np.mean)).reset_index()
temp.columns = ['state_code', 'num_proposals']
scl = [[0.0, 'rgb(242,240,247)'],[0.2, 'rgb(218,218,235)'],[0.4, 'rgb(188,189,220)'],\
[0.6, 'rgb(158,154,200)'],[0.8, 'rgb(117,107,177)'],[1.0, 'rgb(84,39,143)']]
data = [ dict(
type='choropleth',
colorscale = scl,
autocolorscale = False,
locations = temp['state_code'],
z = temp['num_proposals'].astype(float),
locationmode = 'USA-states',
text = temp['state_code'],
marker = dict(line = dict (color = 'rgb(255,255,255)',width = 2)),
colorbar = dict(title = "% of pro")
) ]
layout = dict(
title = 'Project Proposals % of Acceptance Rate by US States',
geo = dict(
scope='usa',
projection=dict( type='albers usa' ),
showlakes = True,
lakecolor = 'rgb(255, 255, 255)',
),
)
fig = dict(data=data, layout=layout)
offline.iplot(fig, filename='us-map-heat-map')
I have imported following libraries:
from chart_studio import plotly
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
from collections import Counter
import chart_studio.plotly as py
Try the following code with your data:
(I tried putting your variables in the correct spots)
choropleth = go.Choropleth(
locations=temp['state_code'],
locationmode='USA-states',
z = temp['num_proposals'].astype(float),
zmin = 0,
zmax = max(temp['num_proposals'].astype(float)),
colorscale=scl,
autocolorscale=False,
text='Proposals',
marker_line_color='white',
colorbar_title="% Acceptance Rate"
)
fig = go.Figure(data=choropleth)
fig.update_layout(
title_text='Project Proposals % of Acceptance Rate by US States',
geo = dict(
scope='usa',
projection=go.layout.geo.Projection(type = 'albers usa'),
showlakes=True,
lakecolor='rgb(255, 255, 255)'),
)
fig.show()
This code works by creating the Plotly Choropleth Graph Object with your data, then loading that object into a Plotly Figure Graph Object, then updating the layout (for proper titles and zooms), and finally displaying the figure.

Switch-case statement to show plotted data

I've developed a software that shows plotted data from csv file.
Now I've created something that allows to the user to choose which plot to see.
I was looking how to do switch-case and I try it but something is wrong because actually my program opens wrong charts.
How can I solve it?
My code:
from Signals_Plotter import Plot_data
FILE_NAME = 'Signals_informations.csv'
plot_csv_obj = Plot_data()
user_choice = input("Which chart do you want?:\n"
"1: Line plot (all signals in same chart)\n"
"2: Bar subplots (signals in sublotted charts)\n"
"3: Markers plot (all signals in same chart)\n"
"4: Line subplots (signals in sublotted chart)\n"
"5: Stacked plot (all signals in same chart with stacked Y axes)\n"
)
def switch_demo(user_choice):
switcher = {
1: plot_csv_obj.line_plot_from_csv(FILE_NAME),
2: plot_csv_obj.bar_plot_from_csv(FILE_NAME),
3: plot_csv_obj.scatter_plot_from_csv(FILE_NAME),
4: plot_csv_obj.subplots_from_csv(FILE_NAME),
5: plot_csv_obj.stacked_plot_from_csv(FILE_NAME)
}
return switcher.get(user_choice, "Invalid choice")
switch_demo(user_choice)
Plot_Data class:
from plotly.offline import plot
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import random
class Plot_data:
def line_plot_from_csv(self, file_name):
df = pd.read_csv(file_name, delimiter = ';')
fig = go.Figure()
for i in range(1, len(df.columns)):
fig.add_trace(go.Scatter(x=df.iloc[:,0], y=df.iloc[:,i], mode='lines', name=df.columns[i]))
fig.update_layout(title_text="Line plots", yaxis={'title':'Values [unit]'}, xaxis={'title':'DateTime'})
plot(fig)
def bar_plot_from_csv(self, file_name):
df = pd.read_csv(file_name, delimiter = ';')
fig = make_subplots(rows=1, cols=df.shape[1]-1)
for i in range(1, len(df.columns)):
fig.add_trace(go.Bar(x=df.iloc[:,0], y=df.iloc[:,i], name=df.columns[i]),row=1, col=i)
fig.update_layout(height=600, width=1000, title_text="Bar Subplots", yaxis={'title':'Values [unit]'}, xaxis={'title':'DateTime'})
plot(fig)
def scatter_plot_from_csv(self, file_name):
df = pd.read_csv(file_name, delimiter = ';')
fig = go.Figure()
for i in range(1, len(df.columns)):
fig.add_trace(go.Scatter(x=df.iloc[:,0], y=df.iloc[:,i], mode='markers', name=df.columns[i]))
fig.update_layout(title_text="Markers plot", yaxis={'title':'Values [unit]'}, xaxis={'title':'DateTime'})
plot(fig)
def subplots_from_csv(self, file_name):
df = pd.read_csv(file_name, delimiter = ';')
fig = make_subplots(rows=1, cols=df.shape[1]-1)
for i in range(1, len(df.columns)):
fig.add_trace(go.Scatter(x=df.iloc[:,0], y=df.iloc[:,i], name=df.columns[i]),row=1, col=i)
fig.update_layout(height=600, width=1000, title_text="Scatter line Subplots", yaxis={'title':'Values [unit]'}, xaxis={'title':'DateTime'})
plot(fig)
def stacked_plot_from_csv(self, file_name):
df = pd.read_csv(file_name, delimiter = ';')
fig = go.Figure()
colors=[]
axes = {
'xaxis': dict(domain=[0.3, 0.7])
}
for i in range(1, len(df.columns)):
fig.add_trace(go.Scatter(x=df.iloc[:,0], y=df.iloc[:,i], name=df.columns[i], yaxis='y'+str(i)))
s = 'yaxis'
colors.append("#{:06x}".format(random.randint(0, 0xFFFFFF)))
if i > 1:
s = s + str(i)
axes[s] = dict(
title=f"S{i}",
titlefont=dict(color=colors[i-1]),
tickfont=dict(color=colors[i-1])
)
if i > 1:
axes[s]['anchor'] = 'free'
axes[s]['overlaying'] = 'y'
if i > len(df.columns) / 2:
axes[s]['side'] = 'right'
axes[s]['position'] = 0.3 + 0.1*i
else:
axes[s]['side'] = 'left'
axes[s]['position'] = 0.1*i
fig.update_layout(**axes)
fig.update_layout(
title_text="Stacked y-axes",
yaxis={'title':'Values [unit]'},
xaxis={'title':'DateTime'},
height=600,
width=1500
)
plot(fig)
Either cast your input to an integer or cast your dict keys to strings:
def switch_demo(user_choice):
switcher = {
"1": plot_csv_obj.line_plot_from_csv(FILE_NAME),
"2": plot_csv_obj.bar_plot_from_csv(FILE_NAME),
"3": plot_csv_obj.scatter_plot_from_csv(FILE_NAME),
"4": plot_csv_obj.subplots_from_csv(FILE_NAME),
"5": plot_csv_obj.stacked_plot_from_csv(FILE_NAME)
}
return switcher.get(user_choice, "Invalid choice")

Adding hover tool tip to bokeh histogram

I have created a histogram in bokeh using the following code:
TOOLS="pan,wheel_zoom,box_zoom,reset,hover"
for column in valid_columns:
output_file_name = str( file_name + column + ".html" )
data_values = stats[ column ].tolist()
output_file( output_file_name )
histogram, edges = np.histogram( data_values, bins=50 )
source = ColumnDataSource(
data = dict( data_value = data_values ) )
p1 = figure( title = column, background_fill="#E8DDCB", tools=TOOLS )
p1.quad( top = histogram, bottom = 0, left = edges[ :-1 ], right = edges[ 1: ],
fill_color = "#036564", line_color = "#033649" )
hover = p1.select(dict(type=HoverTool))
hover.tooltips = [ ( "Value", "#data_value" ) ]
show( p1 )
print( "Saved Figure to ", output_file_name )
where valid columns are a list of all columns I want examined within a pandas dataframe. I am trying to add a hover tool tip which will display the number of items stored in each bin but I have not be able to do so. Any help would be appreciated.
If you prefer to not use a ColumnDataSource, you can replace #data_value with #top and it should work with minimal editing:
hover = HoverTool(tooltips = [('Value', '#top')])
p.add_tools(hover)
i.e. editing the example histogram.py in this way also works:
from bokeh.models import HoverTool
def make_plot(title, hist, edges, x, pdf, cdf):
p = figure(title=title, tools='', background_fill_color="#fafafa")
p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:],
fill_color="navy", line_color="white", alpha=0.5)
p.line(x, pdf, line_color="#ff8888", line_width=4, alpha=0.7, legend_label="PDF")
p.line(x, cdf, line_color="orange", line_width=2, alpha=0.7, legend_label="CDF")
p.y_range.start = 0
p.legend.location = "center_right"
p.legend.background_fill_color = "#fefefe"
p.xaxis.axis_label = 'x'
p.yaxis.axis_label = 'Pr(x)'
p.grid.grid_line_color="white"
hover = HoverTool(tooltips = [('Density', '#top')])
p.add_tools(hover)
return p
It looks like you are missing a couple of things:
Have a source of the same length as your histogram, not your data_values. To be more concrete, I think you want your source to be:
source = ColumnDataSource( data = dict( data_value = histogram ) )
Add the source to your p1.quad call, i.e.
p1.quad( top = histogram, bottom = 0, left = edges[ :-1 ], right = edges[ 1: ],
fill_color = "#036564", line_color = "#033649", source = source )

Categories