Skip to content

Commit f386c0b

Browse files
butterfly with neutral column
1 parent fbc94a2 commit f386c0b

File tree

1 file changed

+103
-2
lines changed

1 file changed

+103
-2
lines changed

doc/python/horizontal-bar-charts.md

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,107 @@ for yd, xd in zip(y_data, x_data):
214214

215215
fig.update_layout(annotations=annotations)
216216

217+
fig.show()
218+
```
219+
### Diverging Bar (or Butterfly) Chart with Neutral Column
220+
221+
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_.
222+
223+
```
224+
import pandas as pd
225+
import plotly.graph_objects as go
226+
227+
data = {
228+
"Category": ["Content Quality", "Value for Money", "Ease of Use", "Customer Support", "Scale Fidelity"],
229+
"Neutral": [10, 15, 18, 15,20],
230+
"Somewhat Agree": [25, 25, 22, 20, 20],
231+
"Strongly Agree": [35, 35, 25, 40, 20],
232+
"Somewhat Disagree": [-20, -15, -20, -10, -20],
233+
"Strongly Disagree": [-10, -10, -15, -15,-20]
234+
}
235+
df = pd.DataFrame(data)
236+
237+
fig = go.Figure()
238+
# this color palette conveys meaning: blues for negative, reds for positive, gray for neutral
239+
color_by_category={
240+
"Strongly Agree":'darkblue',
241+
"Somewhat Agree":'lightblue',
242+
"Somewhat Disagree":'orange',
243+
"Strongly Disagree":'red',
244+
"Neutral":'gray',
245+
}
246+
247+
# We want the legend to be ordered in the same order that the categories appear, left to right --
248+
# which is different from the order in which we have to add the traces to the figure.
249+
# since we need to create the "somewhat" traces before the "strongly" traces to display
250+
# the segments in the desired order
251+
252+
legend_rank_by_category={
253+
"Strongly Disagree":1,
254+
"Somewhat Disagree":2,
255+
"Somewhat Agree":3,
256+
"Strongly Agree":4,
257+
"Neutral":5
258+
}
259+
260+
# Add bars
261+
for col in df[["Somewhat Disagree","Strongly Disagree","Somewhat Agree","Strongly Agree","Neutral"]]:
262+
fig.add_trace(go.Bar(
263+
y=df["Category"],
264+
x=df[col],
265+
name=col,
266+
orientation='h',
267+
marker=dict(color=color_by_category[col]),
268+
legendrank=legend_rank_by_category[col],
269+
xaxis=f"x{1+(col=="Neutral")}", # in this context, putting neutral on a secondary x-axis on a different domain
270+
# yields results equivalent to subplots with far less code
271+
272+
273+
)
274+
)
275+
276+
# make calculations to split the plot into two columns with a shared x axis scale
277+
# by setting the domain and range of the x axes appropriately
278+
279+
# Find the maximum width of the bars to the left and right sides of the origin; remember that the width of
280+
# the plot is the sum of the longest negative bar and the longest positive bar even if they are on separate rows
281+
max_left = min(df[["Somewhat Disagree","Strongly Disagree"]].sum(axis=1))
282+
max_right = max(df[["Somewhat Agree","Strongly Agree"]].sum(axis=1))
283+
284+
# we are working in percent, but coded the negative reactions as negative numbers; so we need to take the absolute value
285+
max_width_signed = abs(max_left)+max_right
286+
max_width_neutral = max(df["Neutral"])
287+
288+
fig.update_layout(
289+
title="Reactions to the statement, 'The service met your expectations for':",
290+
plot_bgcolor="white",
291+
barmode='relative', # Allows bars to diverge from the center
292+
)
293+
fig.update_xaxes(
294+
zeroline=True, #the zero line distinguishes between positive and negative segments
295+
zerolinecolor="black",
296+
#starting here, we set domain and range to create a shared x-axis scale
297+
# multiply by .98 to add space between the two columns
298+
range=[max_left, max_right],
299+
domain=[0, 0.98*(max_width_signed/(max_width_signed+max_width_neutral))]
300+
)
301+
fig.update_layout(
302+
xaxis2=dict(
303+
range=[0, max_width_neutral],
304+
domain=[(1-.98*(1-max_width_signed/(max_width_signed+max_width_neutral))), 1.0],
305+
)
306+
)
307+
fig.update_legends(
308+
orientation="h", # a horizontal legend matches the horizontal bars
309+
yref="container",
310+
yanchor="bottom",
311+
y=0.02,
312+
xanchor="center",
313+
x=0.5
314+
)
315+
316+
fig.update_yaxes(title="")
317+
217318
fig.show()
218319
```
219320

@@ -260,7 +361,7 @@ fig.append_trace(go.Scatter(
260361
), 1, 2)
261362

262363
fig.update_layout(
263-
title='Household savings & net worth for eight OECD countries',
364+
title=dict(text='Household savings & net worth for eight OECD countries'),
264365
yaxis=dict(
265366
showgrid=False,
266367
showline=False,
@@ -335,4 +436,4 @@ fig.show()
335436

336437
### Reference
337438

338-
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!
439+
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!

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy