Skip to content

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

Open
wants to merge 13 commits into
base: doc-prod
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
butterfly with neutral column
  • Loading branch information
rl-utility-man committed Feb 6, 2025
commit f386c0baeaa1cf40bed3bad4158254b69de41bbf
105 changes: 103 additions & 2 deletions doc/python/horizontal-bar-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,107 @@ 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: omit them, leaving them implicit when the categories add to 100%, as we did above or put them in a separate column, as we do in this example. Jonathan Schwabish discusses this on page 92-97 of _Better Data Visualizations_.

```
import pandas as pd
import plotly.graph_objects as go

data = {
"Category": ["Content Quality", "Value for Money", "Ease of Use", "Customer Support", "Scale Fidelity"],
"Neutral": [10, 15, 18, 15,20],
"Somewhat Agree": [25, 25, 22, 20, 20],
"Strongly Agree": [35, 35, 25, 40, 20],
"Somewhat Disagree": [-20, -15, -20, -10, -20],
"Strongly Disagree": [-10, -10, -15, -15,-20]
}
df = pd.DataFrame(data)

fig = go.Figure()
Copy link
Member

Choose a reason for hiding this comment

The 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 update_layout and other can be a bit more challenging to understand for a user.

Copy link
Contributor

Choose a reason for hiding this comment

The 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 negative, reds for positive, gray for neutral
color_by_category={
"Strongly Agree":'darkblue',
"Somewhat Agree":'lightblue',
"Somewhat Disagree":'orange',
"Strongly Disagree":'red',
"Neutral":'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,
"Somewhat Disagree":2,
"Somewhat Agree":3,
"Strongly Agree":4,
"Neutral":5
}

# Add bars
for col in df[["Somewhat Disagree","Strongly Disagree","Somewhat Agree","Strongly Agree","Neutral"]]:
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=="Neutral")}", # in this context, putting neutral 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[["Somewhat Disagree","Strongly Disagree"]].sum(axis=1))
max_right = max(df[["Somewhat 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_neutral = max(df["Neutral"])

fig.update_layout(
title="Reactions to the statement, 'The service met your expectations for':",
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_neutral))]
)
fig.update_layout(
xaxis2=dict(
range=[0, max_width_neutral],
domain=[(1-.98*(1-max_width_signed/(max_width_signed+max_width_neutral))), 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()
```

Expand Down Expand Up @@ -260,7 +361,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,
Expand Down Expand Up @@ -335,4 +436,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!