-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Example 2 for Butterfly chart (version2) #4984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: doc-prod
Are you sure you want to change the base?
Changes from all commits
fbc94a2
f386c0b
911b993
40cd4c8
70b7f49
886078f
d5e2284
be52c35
2140af6
a14f4cb
8d0a2eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,10 @@ jupyter: | |
text_representation: | ||
extension: .md | ||
format_name: markdown | ||
format_version: '1.1' | ||
jupytext_version: 1.1.1 | ||
format_version: '1.3' | ||
jupytext_version: 1.16.4 | ||
kernelspec: | ||
display_name: Python 3 | ||
display_name: Python 3 (ipykernel) | ||
language: python | ||
name: python3 | ||
language_info: | ||
|
@@ -20,7 +20,7 @@ jupyter: | |
name: python | ||
nbconvert_exporter: python | ||
pygments_lexer: ipython3 | ||
version: 3.6.7 | ||
version: 3.11.10 | ||
plotly: | ||
description: How to make horizontal bar charts in Python with Plotly. | ||
display_as: basic | ||
|
@@ -217,6 +217,182 @@ fig.update_layout(annotations=annotations) | |
fig.show() | ||
``` | ||
|
||
### Diverging Bar (or Butterfly) Chart | ||
|
||
Diverging bar charts show counts of positive outcomes or sentiments to the right of zero and counts of negative outcomes to the left of zero, allowing the reader to easily spot areas of excellence and concern. This example allows the reader of the graph to infer the number of people offering a neutral response because the neutral category, which is left implicit, would make the responses add to 100%. | ||
|
||
```python | ||
import plotly.graph_objects as go | ||
import pandas as pd | ||
|
||
|
||
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/refs/heads/master/gss_2002_5_pt_likert.csv') | ||
|
||
df.rename(columns={'Unnamed: 0':"Category"}, inplace=True) | ||
|
||
#achieve the diverging effect by putting a negative sign on the "disagree" answers | ||
for v in ["Disagree","Strongly Disagree"]: | ||
df[v]=df[v]*-1 | ||
|
||
fig = go.Figure() | ||
# this color palette conveys meaning: blues for positive, red and orange for negative | ||
color_by_category={ | ||
"Strongly Agree":'darkblue', | ||
"Agree":'lightblue', | ||
"Disagree":'orange', | ||
"Strongly Disagree":'red', | ||
} | ||
|
||
|
||
# We want the legend to be ordered in the same order that the categories appear, left to right -- | ||
# which is different from the order in which we have to add the traces to the figure. | ||
# since we need to create the "somewhat" traces before the "strongly" traces to display | ||
# the segments in the desired order | ||
legend_rank_by_category={ | ||
"Strongly Disagree":1, | ||
"Disagree":2, | ||
"Agree":3, | ||
"Strongly Agree":4, | ||
} | ||
# Add bars for each category | ||
for col in ["Disagree","Strongly Disagree","Agree","Strongly Agree"]: | ||
fig.add_trace(go.Bar( | ||
y=df["Category"], | ||
x=df[col], | ||
name=col, | ||
orientation='h', | ||
marker=dict(color=color_by_category[col]), | ||
legendrank=legend_rank_by_category[col] | ||
)) | ||
|
||
fig.update_layout( | ||
title="Reactions to statements from the 2002 General Social Survey:", | ||
yaxis_title = "", | ||
barmode='relative', # Allows bars to diverge from the center | ||
plot_bgcolor="white", | ||
) | ||
|
||
fig.update_xaxes( | ||
title="Percent of Responses", | ||
zeroline=True, # Ensure there's a zero line for divergence | ||
zerolinecolor="black", | ||
# use array tick mode to show that the counts to the left of zero are still positive. | ||
# this is hard coded; generalize this if you plan to create a function that takes unknown or widely varying data | ||
tickmode = 'array', | ||
tickvals = [-50, 0, 50, 100], | ||
ticktext = [50, 0, 50, 100] | ||
) | ||
|
||
fig.show() | ||
|
||
``` | ||
|
||
|
||
### Diverging Bar (or Butterfly) Chart with Neutral Column | ||
|
||
The previous diverging bar chart example excluded neutral responses. This variation includes them in a separate column. Jonathan Schwabish discusses tradeoffs between these options on page 92-97 of _Better Data Visualizations_. | ||
|
||
```python | ||
import pandas as pd | ||
import plotly.graph_objects as go | ||
|
||
|
||
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/refs/heads/master/gss_2002_5_pt_likert.csv') | ||
df.rename(columns={'Unnamed: 0':"Category"}, inplace=True) | ||
Comment on lines
+300
to
+301
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to rename the column here rather in the dataset? Is that just how the dataset was? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a shortcoming of the data set. I just proposed a PR to label the column properly in the data set plotly/datasets#64 A search of github shows no uses of that data set other than in this PR and #4994, so it appears safe to accept that PR. (I uploaded this data set recently in plotly/datasets#62 ) As soon as you merge plotly/datasets#64 , we can remove the rename commands from this and from #4994 |
||
|
||
|
||
#achieve the diverging effect by putting a negative sign on the "disagree" answers | ||
for v in ["Disagree","Strongly Disagree"]: | ||
df[v]=df[v]*-1 | ||
|
||
fig = go.Figure(layout=go.Layout( | ||
title="Reactions to statements from the 2002 General Social Survey:", | ||
plot_bgcolor="white", | ||
barmode='relative', # Allows bars to diverge from the center | ||
# Put the legend at the bottom center of the figure | ||
legend=dict( | ||
orientation="h", # a horizontal legend matches the horizontal bars | ||
yref="container", | ||
yanchor="bottom", | ||
y=0.02, | ||
xanchor="center", | ||
x=0.5), | ||
# use an unlabeled Y axis, since we're going to list specific questions on the y-axis. | ||
yaxis=dict( | ||
title="" | ||
), | ||
) | ||
) | ||
|
||
|
||
# this color palette conveys meaning: blues for agreement, reds and oranges for disagreement, gray for Neither Agree nor Disagree | ||
color_by_category={ | ||
"Strongly Agree":'darkblue', | ||
"Agree":'lightblue', | ||
"Disagree":'orange', | ||
"Strongly Disagree":'red', | ||
"Neither Agree nor Disagree":'gray', | ||
} | ||
|
||
|
||
# We want the legend to be ordered in the same order that the categories appear, left to right -- | ||
# which is different from the order in which we have to add the traces to the figure. | ||
# since we need to create the "somewhat" traces before the "strongly" traces to display | ||
# the segments in the desired order | ||
|
||
legend_rank_by_category={ | ||
"Strongly Disagree":1, | ||
"Disagree":2, | ||
"Agree":3, | ||
"Strongly Agree":4, | ||
"Neither Agree nor Disagree":5 | ||
} | ||
|
||
# Add bars | ||
for col in ["Disagree","Strongly Disagree","Agree","Strongly Agree","Neither Agree nor Disagree"]: | ||
fig.add_trace(go.Bar( | ||
y=df["Category"], | ||
x=df[col], | ||
name=col, | ||
orientation='h', | ||
marker=dict(color=color_by_category[col]), | ||
legendrank=legend_rank_by_category[col], | ||
xaxis=f"x{1+(col=='Neither Agree nor Disagree')}", # in this context, putting "Neither Agree nor Disagree" on a secondary x-axis on a different domain | ||
# yields results equivalent to subplots with far less code | ||
) | ||
) | ||
|
||
# make calculations to split the plot into two columns with a shared x axis scale | ||
# by setting the domain and range of the x axes appropriately | ||
|
||
# Find the maximum width of the bars to the left and right sides of the origin; remember that the width of | ||
# the plot is the sum of the longest negative bar and the longest positive bar even if they are on separate rows | ||
max_left = min(df[["Disagree","Strongly Disagree"]].sum(axis=1)) | ||
max_right = max(df[["Agree","Strongly Agree"]].sum(axis=1)) | ||
|
||
# we are working in percent, but coded the negative reactions as negative numbers; so we need to take the absolute value | ||
max_width_signed = abs(max_left)+max_right | ||
max_width_neither = max(df["Neither Agree nor Disagree"]) | ||
|
||
fig.update_xaxes( | ||
zeroline=True, #the zero line distinguishes between positive and negative segments | ||
zerolinecolor="black", | ||
#starting here, we set domain and range to create a shared x-axis scale | ||
# multiply by .98 to add space between the two columns | ||
range=[max_left, max_right], | ||
domain=[0, 0.98*(max_width_signed/(max_width_signed+max_width_neither))] | ||
) | ||
|
||
fig.update_layout( | ||
xaxis2=dict( | ||
range=[0, max_width_neither], | ||
domain=[(1-.98*(1-max_width_signed/(max_width_signed+max_width_neither))), 1.0], | ||
) | ||
) | ||
|
||
fig.show() | ||
``` | ||
|
||
### Bar Chart with Line Plot | ||
|
||
```python | ||
|
@@ -260,7 +436,7 @@ fig.append_trace(go.Scatter( | |
), 1, 2) | ||
|
||
fig.update_layout( | ||
title='Household savings & net worth for eight OECD countries', | ||
title=dict(text='Household savings & net worth for eight OECD countries'), | ||
yaxis=dict( | ||
showgrid=False, | ||
showline=False, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LiamConnors: I looked for the right way to merge our other recent contribution into this thread. I could not convince git to do the cross repo merge of 1 file I needed, so I ended up just pasting of #4994 -- and now it's misleadingly marked as a large addition on lines 220-286 (it's entirely approved). Is that confusing to you?
I could roll back the change that pasted it in. if we do so, I think this PR, which is meant as the 2nd diverging bar example will appear first, before the simpler diverging bar example. I can fix that with a separate, future PR. Please advise. Also, I've responded to all your points and look forward to your feedback on my responses.