Skip to content

Instantly share code, notes, and snippets.

@kdoroschak
Last active June 15, 2019 21:10
Show Gist options
  • Save kdoroschak/21506be2c2774b22073c93fd63001abe to your computer and use it in GitHub Desktop.
Save kdoroschak/21506be2c2774b22073c93fd63001abe to your computer and use it in GitHub Desktop.
Diverging barplot in python. Can flexibly include the center bar or not depending on how many categories you give it.
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
import matplotlib.patches as mpatches
sns.set(style="whitegrid", font_scale=1.8)
def diverging_stacked_bar(results, category_names=[], colors=[], symmetrical=False,
bar_width = 0.35):
if len(category_names) % 2 == 0: # even
print(len(category_names))
mid = False
else: # odd
mid = True
mid_ix = len(category_names) / 2
ind = range(len(results))
fig, ax = plt.subplots(figsize=(12, 5))
patches = []
max_width = 0
max_left_width = 0
max_right_width = 0
for i, (ix, row) in enumerate(results.iterrows()):
b = i - bar_width / 2. # bottom_bar_edge
if mid:
mid_count = row.loc[category_names[mid_ix]]
l = -1. * mid_count / 2 # left_bar_edge
r = mpatches.Rectangle(
xy=(l, b), width=mid_count, height=bar_width,
facecolor=colors[mid_ix],
edgecolor=colors[mid_ix],
linewidth=1,
label='_nolegend_',
)
ax.add_patch(r)
left_edge = l
right_edge = -l
else:
left_edge = right_edge = 0
if mid:
right_start = mid_ix + 1
left_end = mid_ix
else:
right_start = len(category_names) / 2
left_end = right_start
# left bars
for bar in range(0, left_end):
bar_count = row.loc[category_names[bar]]
l = left_edge - bar_count
r = mpatches.Rectangle(
xy=(l, b), width=bar_count, height=bar_width,
facecolor=colors[bar],
edgecolor=colors[bar],
linewidth=1,
label='_nolegend_',)
ax.add_patch(r)
left_edge = l
# right bars
for bar in range(right_start, len(category_names)):
bar_count = row.loc[category_names[bar]]
l = right_edge
r = mpatches.Rectangle(
xy=(l, b), width=bar_count, height=bar_width,
facecolor=colors[bar],
edgecolor=colors[bar],
linewidth=1,
label='_nolegend_',)
ax.add_patch(r)
right_edge += bar_count
max_width = max([max_width, -left_edge, right_edge])
max_left_width = min([max_left_width, left_edge])
max_right_width = max([max_right_width, right_edge])
if symmetrical:
ax.set_xlim([-max_width, max_width])
else:
ax.set_xlim([max_left_width, max_right_width])
ax.set_ylim([-bar_width, len(results)])
ax.set_yticks([i for i in range(len(results))])
ax.set_yticklabels([plt.Text(0, i - bar_width / 2., str(x)) for i, x in enumerate(results.index)])
legend_items = []
for cat, color in zip(category_names, colors):
legend_items.append(mpatches.Patch(facecolor=color, edgecolor=color, label=cat))
ax.legend(handles=legend_items)
return ax
# Example
results = {"strongly disagree" : [3, 4, 1, 0, 5, 7],
"disagree" : [10, 12, 3, 3, 3, 2],
"neutral": [4, 4, 6, 8, 1, 0],
"agree": [2, 1, 7, 4, 2, 5],
"strongly agree": [7, 6, 5, 4, 3, 2]}
results = pd.DataFrame(results, index=["q%d" % i for i in range(6)])
columns_with_neutral = [u'agree', u'disagree', u'neutral', u'strongly agree', u'strongly disagree']
colors_with_neutral = ['firebrick', 'lightcoral', 'gainsboro', 'cornflowerblue', 'darkblue']
ax = diverging_stacked_bar(results, columns_with_neutral,
colors=colors_with_neutral, symmetrical=True)
columns_without_neutral = [u'agree', u'disagree', u'strongly agree', u'strongly disagree']
colors_without_neutral = ['firebrick', 'lightcoral', 'cornflowerblue', 'darkblue']
ax = diverging_stacked_bar(results, columns_without_neutral,
colors=colors_without_neutral, symmetrical=False)
@kdoroschak
Copy link
Author

Output:

diverging_barplot_center_symmetrical

diverging_barplot_nocenter_asymmetrical

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment