Search code examples
pythonpandasmatplotlibbar-chartstacked

How to to add stacked bar plot hatching in pandas? (...or how to get BarContainer vs AxisSubplot in pandas plot vs. matplotlib?)


I have a code example using matplotlib.pyplot.plot() that works and that that I want to replicate to make hatched bar segments on a stacked bar chart. However, I have been making all of my other graphics using pandas.DataFrame.plot() instead of matplotlib.pyplot.plot() and would like to continue this here as well. The example code returns a tuple of BarContainer objects and pandas.DataFrame.plot() returns an AxisSubplot obect, and I don't know how to navigate between the two.

Any suggestions for how to get BarContainer objects out of pandas.DataFrame.plot() so that I can use those to replicate the example?

If not, any suggestions for how to achieve my aim of adding hatching per colored bar segment on a stacked bar chart using pandas.DataFrame.plot()?

With my data, the hatching will be useful to help differentiate between similar colors since I have both a lot of categories and items and the results otherwise can look visually similar. (I know I could also find a way to plot simpler things, but doing this helpful for my exploratory data analysis.) Thank you!


Example working code to hatch each colored bar segment of a stacked bar chart (from here: https://matplotlib.org/examples/pylab_examples/hatch_demo.html ):

import pandas as pd
import numpy as np  
import matplotlib.pyplot as plt

fig = plt.figure()
ax2 = fig.add_subplot(111)
bars = ax2.bar(range(1, 5), range(1, 5), color='yellow', ecolor='black') + \
    ax2.bar(range(1, 5), [6] * 4, bottom=range(1, 5), color='green', ecolor='black')

patterns = ('-', '+', 'x', '\\', '*', 'o', 'O', '.')
for bar, pattern in zip(bars, patterns):
    bar.set_hatch(pattern)

enter image description here


My code (with simplified data) that I wish had hatches per colored bar segments:

df = pd.DataFrame(np.random.uniform(0,10, size=(5,26)))
df.columns=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
ax = df.plot.barh(stacked=True, width=0.98, figsize=(10,5), cmap='gist_ncar')

enter image description here


Solution

  • how to get BarContainer objects out of pandas.DataFrame.plot()

    Filter them out of the Axes' containers attribute

    >>> import matplotlib as mpl
    >>> ax = df.plot.barh(stacked=True, width=0.98, figsize=(10,5), cmap='gist_ncar')
    >>> ax.containers[:4]
    [<BarContainer object of 5 artists>, <BarContainer object of 5 artists>, <BarContainer object of 5 artists>, <BarContainer object of 5 artists>]
    >>> bars = [thing for thing in ax.containers if isinstance(thing,mpl.container.BarContainer)]
    

    Set the hatches on the Rectangles in each BarContainer.

    import itertools
    patterns = itertools.cycle(('-', '+', 'x', '\\', '*', 'o', 'O', '.'))
    for bar in bars:
        for patch in bar:
            patch.set_hatch(next(patterns))
    L = ax.legend()    # so hatches will show up in the legend
    

    Doing it this way will ensure you don't grab a Patch that isn't part of a bar.


    how to get the same patches to show up on the same colors across different items

    Maybe while iterating over the patches in each bar, inspect its facecolor (patch.get_facecolor) and maintain a dictionary of {facecolor:hatchsymbol} - if the facecolor is in the dictionary set that hatch otherwise get a new hatch symbol, add it and the color to the dictionary the set the hatch.

    d = {}
    for bar in bars:
        for patch in bar:
            pat = d.setdefault(patch.get_facecolor(), next(patterns))
            patch.set_hatch(pat)
    L = ax.legend()    
    

    Matplotlib Tutorials are worth the effort.