-
-
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 6 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 |
---|---|---|
|
@@ -214,6 +214,108 @@ for yd, xd in zip(y_data, x_data): | |
|
||
fig.update_layout(annotations=annotations) | ||
|
||
fig.show() | ||
``` | ||
### Diverging Bar (or Butterfly) Chart with Neutral Column | ||
|
||
Diverging bar charts offer two imperfect options for responses that are neither positive nor negative: put them in a separate column, as in this example or omit them as in the example above. That leaves the unreported neutral value implicit when the categories add to 100%, Jonathan Schwabish discusses this 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() | ||
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. Would it be possible to put more of the layout and data in the go.Figure object. I think 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. Sure! I moved one of the two update_layout calls into go.figure and also moved the update_legend there and the update_yaxes call there. If you do not like those, they're in atomic commits and can be rolled back. I do not see a clarifying way to eliminate the other update_layout call -- which sets up the secondary x axis and contains some variables not defined when we issue the fig = go.Figure() command. If it would be clearer to issue an update_layout that sets up both xaxis1 and xaxis2 in one call, that is straightforward. I welcome feedback about this and more specifics about other potential improvements to the structure and clarity of the code. Is there style guidance I should consult to understand this request better? |
||
# 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 df[["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 | ||
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. The example doesn't run for me, and I believe it's related to setting of the xaxis here. 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. Good point. I had " where I needed ' . Fixed. Thank you! |
||
) | ||
) | ||
|
||
# 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_layout( | ||
title="Reactions to statements from the 2002 General Social Survey:", | ||
plot_bgcolor="white", | ||
barmode='relative', # Allows bars to diverge from the center | ||
) | ||
|
||
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.update_legends( | ||
orientation="h", # a horizontal legend matches the horizontal bars | ||
yref="container", | ||
yanchor="bottom", | ||
y=0.02, | ||
xanchor="center", | ||
x=0.5 | ||
) | ||
|
||
fig.update_yaxes(title="") | ||
|
||
fig.show() | ||
``` | ||
|
||
|
@@ -260,7 +362,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, | ||
|
@@ -335,4 +437,4 @@ fig.show() | |
|
||
### Reference | ||
|
||
See more examples of bar charts and styling options [here](https://plotly.com/python/bar-charts/).<br> See https://plotly.com/python/reference/bar/ for more information and chart attribute options! | ||
See more examples of bar charts and styling options [here](https://plotly.com/python/bar-charts/).<br> See https://plotly.com/python/reference/bar/ for more information and chart attribute options! |
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.
I think the content around "unreported neutral values" refers the previous example? If so, I think that would be more relevant there. Also, from documentation, I'd prefer not to direct users to additional resources that they may not have access to.
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.
Good point about the previous example. I moved the language there.
I think cites to work by leading thinkers on how to design effective graphs that is available from public libraries and online booksellers is a great complement to this documentation about how to implement those ideas. Also, I'd like to give Jon Schwabish's book credit for ideas that contributed to these examples. If it's better to move this cite to e.g. a comment, I'm open to that. Perhaps "Jonathan Schwabish discusses tradeoffs between these options on page 92-97 of Better Data Visualizations."