Stacking just one of the bars on a pandas MultiIndex dataframe using matplotlib bar plot

Viewed 42

I'm trying to create a bar plot where just one of the bars is stacked. I'm trying it out with a MultiIndex, but not sure if this would be the best approach. Here's a quick example of how the dataframe looks like:

df = pd.DataFrame({'case 1': [-12, 0, 0, 0, 7, -5], 
                   'case 2': [0, -5, -3, -4, 9, -3]},
                   index=pd.MultiIndex.from_arrays([['investment', 'investment', 'investment', 
                                                     'investment', 'profit', 'difference'], 
                                                    list('gabc  ')],
                                                   names=['one', 'two']))

                    case 1  case 2
one        two                
investment g       -12       0
           a         0      -5
           b         0      -3
           c         0      -4
profit               7       9
difference          -5      -3

df.plot(kind='bar', stacked=True, rot=0)

I want just that the data on 'investments' 'a', 'b' and 'c' are combined on a stacked bar. I feel I may be close to a solution, but I'm completely stuck on this, and I'm not sure using a MultiIndex is the best way to do this.

Here's how I want it to look like: A bar plot where just one bar is stacked

Would someone have an idea on how to achieve this? Thanks!

1 Answers

It's tricky to stack only one bar using pandas. Here's a little bit of a hack, we'll reshape the DataFrame and then loop over the cases and plot each with a different edge alignment, due to the sign of width (works since there are only 2 cases). Since 'case 2' is the one that get's split, we'll supply a list of colors to cycle through as a kwarg when we plot that subset.

We need to manually construct the legend after because it's a mess otherwise.

import matplotlib.pyplot as plt
from matplotlib.patches import Patch

s = (df.unstack(-1).stack(0).rename_axis(index=[None, None], columns=None)
       .reindex(['investment', 'profit', 'difference'], level=0))
#                          a    b    c     g
#investment case 1  NaN  0.0  0.0  0.0 -12.0
#           case 2  NaN -5.0 -3.0 -4.0   0.0
#profit     case 1  7.0  NaN  NaN  NaN   NaN
#           case 2  9.0  NaN  NaN  NaN   NaN
#difference case 1 -5.0  NaN  NaN  NaN   NaN
#           case 2 -3.0  NaN  NaN  NaN   NaN  

kwargs = {'case 1': {'width': -0.2, 'color': 'white'}, 
          'case 2': {'width': 0.2, 'color': ['black', '#ffa500', '#00daff', 'red']}}

fig, ax = plt.subplots()
for c in ['case 1', 'case 2']:
    s.xs(c, level=1).plot(kind='bar', stacked=True, ec='k', ax=ax, align='edge',
                          legend=False, rot=0, **kwargs[c])

legend_elements = [Patch(facecolor='white', edgecolor='k', label='case 1'),
                   Patch(facecolor='black', edgecolor='k', label='case 2')]    

ax.legend(handles=legend_elements)
ax.set_ylim(ax.get_ylim()[0]*1.05, ax.get_ylim()[1]*1.05)
plt.show()

enter image description here

Related