Search code examples
pythonpandasbar-chart

How to make a barplot with a double grouped axis using Pandas


I am working on a plot where I want to show two groups on one axis and a third group as fill-value. The problem is, that when I plot it, the y-axis shows values in tuples:

data_dict = {'major_group': list(np.array([['A']*10, ['B']*10]).flat), 
'minor_group': ['q ','r ','s ','t ']*5,
'legend_group':np.repeat(['d','e','f','g','h','i'],[7,3,1,5,1,3])}
(pd.DataFrame(data= data_dict)
.groupby(['major_group', 'minor_group','legend_group'], observed = True)
.size()
.unstack()
.plot(kind='barh', stacked=True))

Result:

plot result of the code included

However, I'm looking for something like this:

Desired result

How can this be achieved? Is there some major and minor axis label that can be set?


Solution

  • This code will create horizontal stacked bars, grouped hierarchically in the y-axis label:

    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    from pandas import DataFrame
    
    def create_data() -> DataFrame:
        data_dict = {
            'major_group': list(np.array([['A'] * 10, ['B'] * 10]).flat), 
            'minor_group': ['q', 'r', 's', 't'] * 5,
            'legend_group': np.repeat(['d', 'e', 'f', 'g', 'h', 'i'], [7, 3, 1, 5, 1, 3])
        }
        return pd.DataFrame(data=data_dict).groupby(['major_group', 'minor_group', 'legend_group'], observed=True).size().unstack()
    
    def plot_stacked_barh(df: DataFrame) -> None:
        fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True)
    
        for i, major_group in enumerate(df.index.levels[0]):
            ax = axes[i]
            df.loc[major_group].plot(kind='barh', stacked=True, ax=ax, width=.8)
    
            if i == 0:
                handles, labels = ax.get_legend_handles_labels()
    
            ax.legend_.remove()
            ax.set_ylabel(major_group, weight='bold')
            ax.xaxis.grid(visible=True, which='major', color='black', linestyle='--', alpha=.4)
            ax.set_axisbelow(True)
    
        fig.legend(handles, labels, title='Legend Group')
    
        plt.tight_layout()
        fig.subplots_adjust(hspace=0)
        plt.show()
    
    def main() -> None:
        df = create_data()
        print(df)
        plot_stacked_barh(df)
    
    if __name__ == "__main__":
        main()
    

    Plot of hierarchical stacked barchart

    For a similar vertical equivalent, look here.