0

I want to create a stacked horizontal bar plot with values of each stack displayed inside it and the total value of the stacks just after the bar. Using python matplotlib, I could create a simple barh. My dataframe looks like below:

import pandas as pd
df = pd.DataFrame({"single":[168,345,345,352],
                   "comp":[481,44,23,58],})
item = ["white_rice",
        "pork_and_salted_vegetables",
        "sausage_and_potato_in_tomato_sauce",
        "curry_vegetable",]
df.index = item

Expect to get bar plot like below except that it is not horizontal:

enter image description here

The code I tried is here...and i get AttributeError: 'DataFrame' object has no attribute 'rows'. Please help me with horizontal bar plot. Thanks.

fig, ax = plt.subplots(figsize=(10,4))
colors = ['c', 'y']
ypos = np.zeros(len(df))
for i, row in enumerate(df.index):    
    ax.barh(df.index, df[row], x=ypos, label=row, color=colors[i])
    bottom += np.array(df[row])

totals = df.sum(axis=0)
x_offset = 4
for i, total in enumerate(totals):
    ax.text(totals.index[i], total + x_offset, round(total), ha='center',) # weight='bold')

x_offset = -15
for bar in ax.patches:
    ax.text(
      # Put the text in the middle of each bar. get_x returns the start so we add half the width to get to the middle.
      bar.get_y() + bar.get_height() / 2,
      bar.get_width() + bar.get_x() + x_offset,
      # This is actual value we'll show.
      round(bar.get_width()),
      # Center the labels and style them a bit.
      ha='center',
      color='w',
      weight='bold',
      size=10)
    
labels = df.index
ax.set_title('Label Distribution Overview')
ax.set_yticklabels(labels, rotation=90)
ax.legend(fancybox=True)
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
sinG20
  • 143
  • 2
  • 15

1 Answers1

0

Consider the following approach to get something similar with matplotlib only (I use matplotlib 3.5.0). Basically the job is done with bar/barh and bar_label combination. You may change label_type and add padding to tweak plot appearance. Also you may use fmt to format values. Edited code with total values added.

import matplotlib.pyplot as plt
import pandas as pd
import random


def main(data):

    data['total'] = data['male'] + data['female']

    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.suptitle('Plot title')

    ax1.bar(x=data['year'].astype(str), height=data['female'], label='female')
    ax1.bar_label(ax1.containers[0], label_type='center')
    ax1.bar(x=data['year'].astype(str), height=data['male'], bottom=data['female'], label='male')
    ax1.bar_label(ax1.containers[1], label_type='center')
    ax1.bar_label(ax1.containers[1], labels=data['total'], label_type='edge')
    ax1.legend()

    ax2.barh(y=data['year'].astype(str), width=data['female'], label='female')
    ax2.bar_label(ax2.containers[0], label_type='center')
    ax2.barh(y=data['year'].astype(str), width=data['male'], left=data['female'], label='male')
    ax2.bar_label(ax2.containers[1], label_type='center')
    ax2.bar_label(ax2.containers[1], labels=data['total'], label_type='edge')
    ax2.legend()

    plt.show()


if __name__ == '__main__':

    N = 4
    main(pd.DataFrame({
        'year': [2010 + val for val in range(N)],
        'female': [int(10 + 100 * random.random()) for dummy in range(N)],
        'male': [int(10 + 100 * random.random()) for dummy in range(N)]}))

Result (with total values added): enter image description here

  • This is not the way. See my comment to the question. All the annotations can be done in 3 lines of code. – Trenton McKinney May 05 '22 at 21:30
  • @Poolka, the horizontal bar does not have ethe total count of the bars – sinG20 May 06 '22 at 03:35
  • @sinG20 Added total values. Pretty straigtforward correction. Using just another `bar_label`. Test it by yourself and you will see how easy it is to modify the code and get the desired result. Please be aware that edited version now modifies datarame passed to `main` function. –  May 06 '22 at 06:16
  • @Poolka Thanks. And yes, as Trenton McKinney answered, the tweaking dataframe actually does the trick. – sinG20 May 09 '22 at 01:25