Skip to content

Instantly share code, notes, and snippets.

@ctokheim
Last active February 13, 2023 12:06
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save ctokheim/6435202a1a880cfecd71 to your computer and use it in GitHub Desktop.
Save ctokheim/6435202a1a880cfecd71 to your computer and use it in GitHub Desktop.
Matplotlib: Stacked and Grouped Bar Plot

Stacked and Grouped Bar Plot

Oddly enough ggplot2 has no support for a stacked and grouped (position="dodge") bar plot. The seaborn python package, although excellent, also does not provide an alternative. However, I knew it was surely possible to make such a plot in regular matplotlib. Matplotlib, although sometimes clunky, gives you enough flexibility to precisely place plotting elements which is needed for a stacked and grouped bar plot.

Below is a working example of making a stacked and grouped bar plot.

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# make up some fake data
pos_mut_pcts = np.array([20, 10, 5, 7.5, 30, 50])
pos_cna_pcts = np.array([10, 0, 0, 7.5, 10, 0])
pos_both_pcts = np.array([10, 0, 0, 0, 0, 0])
neg_mut_pcts = np.array([10, 30, 5, 0, 10, 25])
neg_cna_pcts = np.array([5, 0, 7.5, 0, 0, 10])
neg_both_pcts = np.array([0, 0, 0, 0, 0, 10])
genes = ['PIK3CA', 'PTEN', 'CDKN2A', 'FBXW7', 'KRAS', 'TP53']

with sns.axes_style("white"):
    sns.set_style("ticks")
    sns.set_context("talk")
    
    # plot details
    bar_width = 0.35
    epsilon = .015
    line_width = 1
    opacity = 0.7
    pos_bar_positions = np.arange(len(pos_mut_pcts))
    neg_bar_positions = pos_bar_positions + bar_width

    # make bar plots
    hpv_pos_mut_bar = plt.bar(pos_bar_positions, pos_mut_pcts, bar_width,
                              color='#ED0020',
                              label='HPV+ Mutations')
    hpv_pos_cna_bar = plt.bar(pos_bar_positions, pos_cna_pcts, bar_width-epsilon,
                              bottom=pos_mut_pcts,
                              alpha=opacity,
                              color='white',
                              edgecolor='#ED0020',
                              linewidth=line_width,
                              hatch='//',
                              label='HPV+ CNA')
    hpv_pos_both_bar = plt.bar(pos_bar_positions, pos_both_pcts, bar_width-epsilon,
                               bottom=pos_cna_pcts+pos_mut_pcts,
                               alpha=opacity,
                               color='white',
                               edgecolor='#ED0020',
                               linewidth=line_width,
                               hatch='0',
                               label='HPV+ Both')
    hpv_neg_mut_bar = plt.bar(neg_bar_positions, neg_mut_pcts, bar_width,
                              color='#0000DD',
                              label='HPV- Mutations')
    hpv_neg_cna_bar = plt.bar(neg_bar_positions, neg_cna_pcts, bar_width-epsilon,
                              bottom=neg_mut_pcts,
                              color="white",
                              hatch='//',
                              edgecolor='#0000DD',
                              ecolor="#0000DD",
                              linewidth=line_width,
                              label='HPV- CNA')
    hpv_neg_both_bar = plt.bar(neg_bar_positions, neg_both_pcts, bar_width-epsilon,
                               bottom=neg_cna_pcts+neg_mut_pcts,
                               color="white",
                               hatch='0',
                               edgecolor='#0000DD',
                               ecolor="#0000DD",
                               linewidth=line_width,
                               label='HPV- Both')
    plt.xticks(neg_bar_positions, genes, rotation=45)
    plt.ylabel('Percentage of Samples')
    plt.legend(loc='best')
    sns.despine()

hpv_barplot.png

@merriam
Copy link

merriam commented Dec 16, 2017

FYI, the actual bar plot image is missing. :)

@alblaine
Copy link

I had to modify your code slightly so that the legend wasn't on top of the chart over the bars. I replaced the last two lines with the following three lines:

plt.legend(bbox_to_anchor=(1.1, 1.05))  
sns.despine()  
plt.show()  

plt

@simonada
Copy link

simonada commented Aug 17, 2018

Hey, thanks for sharing this example! I'm trying to switch the axis and show it as a horizontal bar char, but replacing all "bar" with "barh" leads nowhere... Any ideas?

-- Solved it by removing the "bottom" argument completely.

@JianpingZeng
Copy link

Appreciate your shared code!
Thanks!

@AMacumber
Copy link

I modified your code slightly so that xticks are centered between the positive and negative bars.

I changed neg_bar_positions to (neg_bar_positions+pos_bar_positions)/2

plt.xticks((neg_bar_positions+pos_bar_postions)/2, genes, rotation=45)

plt.legend(bbox_to_anchor=(1.1, 1.05))
sns.despine()
plt.show()

Capture

@mchun96
Copy link

mchun96 commented Jun 24, 2021

Thanks for this helpful code! By the way, if you add edgecolor and linewidth arguments to the solid filled bars, then that makes the width of all the stacked bars perfectly consistent without needing to hack with the epsilon value.

@ctokheim
Copy link
Author

@mchun96 Good point, that will make the plotting more robust to the data

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