@@ -216,88 +216,107 @@ fig.update_layout(annotations=annotations)
216
216
217
217
fig.show()
218
218
```
219
- # * Example2 for Butterfly chart version 2
219
+ ### Diverging Bar (or Butterfly) Chart with Neutral Column
220
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 93 of _ Better Data Visualizations_ .
222
+
223
+ ```
221
224
import pandas as pd
222
225
import plotly.graph_objects as go
223
226
224
227
data = {
225
- "State ": [ "California", "Texas" , "Florida", "New York ", "Illinois", "Pennsylvania", "Ohio" , "Georgia", "North Carolina" , "Michigan "] ,
226
- "Strongly Disagree ": [ -12, - 15, -10, -8, -7, -9, -10, -6, -7, -8 ] ,
227
- "Disagree ": [ -22, - 25, -18, - 20, -19, -21, -18, -16, -17, -15 ] ,
228
- "Agree": [ 32, 30, 35, 33, 36, 34, 38, 40, 37, 38 ] ,
229
- "Strongly Agree ": [ 28, 30, 33, 31, 34, 36, 35, 38, 36, 37 ] ,
230
- "Neutral ": [ 18, 20, 22, 19, 20, 21, 25, 23, 22, 24 ]
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 ]
231
234
}
232
-
233
235
df = pd.DataFrame(data)
234
236
235
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
+ }
236
246
237
- # Define diverging categories and their colors
238
- categories = [ "Strongly Disagree", "Disagree", "Agree", "Strongly Agree"]
239
- color_map = {
240
- "Strongly Disagree": "darkblue",
241
- "Disagree": "lightblue",
242
- "Agree": "orange",
243
- "Strongly Agree": "red",
244
-
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
245
258
}
246
259
247
- # Add diverging bars for each category
248
- for category in categories :
260
+ # Add bars
261
+ for col in df[["Somewhat Disagree","Strongly Disagree","Somewhat Agree","Strongly Agree","Neutral"]] :
249
262
fig.add_trace(go.Bar(
250
- y=df[ "State"] ,
251
- x=df[ category] ,
252
- name=category,
253
- orientation="h",
254
- marker=dict(color=color_map[ category] )
255
- ))
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
256
271
257
272
258
- fig.add_trace(go.Bar(
259
- y=df[ "State"] ,
260
- x=df[ "Neutral"] ,
261
- name="Neutral",
262
- orientation="h",
263
- marker=dict(color="gray"),
264
- xaxis="x2" # Assign Neutral to a second x-axis
265
- ))
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"])
266
287
267
- # Update layout
268
288
fig.update_layout(
269
- title="It is the responsibility of government to reduce income differences (U.S. States)",
270
- xaxis=dict(
271
- title="Percentage of Responses (Diverging Categories)",
272
- zeroline=True,
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
273
295
zerolinecolor="black",
274
- range=[ -50, 50] ,
275
- domain=[ 0, 0.8]
276
- ),
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(
277
302
xaxis2=dict(
278
- title="Neutral Responses (%)",
279
- range=[ 0, 30] ,
280
- domain=[ 0.85, 1.0]
281
- ),
282
- yaxis=dict(
283
- title="State",
284
- autorange="reversed"
285
- ),
286
- barmode="relative",
287
- plot_bgcolor="white",
288
- height=600,
289
- width=1000,
290
- legend=dict(
291
- orientation="h",
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",
292
310
yanchor="bottom",
293
- y=1 .02,
311
+ y=0 .02,
294
312
xanchor="center",
295
313
x=0.5
296
- )
297
314
)
298
315
316
+ fig.update_yaxes(title="")
299
317
300
318
fig.show()
319
+ ```
301
320
302
321
### Bar Chart with Line Plot
303
322
0 commit comments