How to label bars with multiple custom values

Question:

I have this dataframe

rules count    percentage    groups   weight

A      15      24%            1       10
B      5       2%             2       30
C      25      50%            3       50

I have the following code:

sns.set(rc={'figure.figsize':(18,9.5)})
plots = sns.barplot(x="rules", y="count", data=df, hue=df['groups'], dodge=False)
percentage = df['percentage'].tolist()
weight = df['weight'].tolist()
patches = plots.patches
for i in range(len(patches)):
   x = patches[i].get_x() + patches[i].get_width()/2
   y = patches[i].get_height()+.09
   plots.annotate('{:.1f}%'.format(percentage[i]), (x, y), ha='center',  va='bottom', size=14)
   plots.annotate('{:.0f}'.format(weight[i]), (x, y), ha='center', va='top', color='white', size=15, fontweight="bold")

and I get the following error when trying to annotate the barchart with the percentage.

Inside the barchart is another number corresponding to the weight column in the df.

IndexError                                Traceback (most recent call last)
<ipython-input-120-0ef14f891711> in <module>()
      7    x = patches[i].get_x() + patches[i].get_width()/2
      8    y = patches[i].get_height()+.09
----> 9    plots.annotate('{:.1f}%'.format(percentage[i]), (x, y), ha='center',  va='bottom', size=14)
     10    plots.annotate('{:.0f}'.format(weight[i]), (x, y), ha='center', va='top', color='white', size=15, fontweight="bold")
     11 plots.set_xticklabels(plots.get_xticklabels(), rotation=90, fontsize=15)

IndexError: list index out of range
Asked By: rnv86

||

Answers:

import pandas as pd
import seaborn as sns

# test data
data = {'rules': ['A', 'B', 'C'], 'count': [15, 5, 25],
        'percentage': ['24%', '2%', '50%'], 'groups': [1, 2, 3], 'weight': [10, 30, 50]}
df = pd.DataFrame(data)

# plot
ax = sns.barplot(x="rules", y="count", data=df, hue='groups', dodge=False)

# since you are using hue, there are multiple containers
for c in ax.containers:
    # set the bar label based on the y-axis
    ax.bar_label(c, label_type='center', padding=1)
    # add an annotations with custom labels
    ax.bar_label(c, labels=df.percentage, label_type='edge', padding=1)

# pad the spacing between the number and the edge of the figure 
ax.margins(y=0.1)

enter image description here


  • Annotation with two custom labels ('weight' and 'percentage').
ax = sns.barplot(x="rules", y="count", data=df, hue='groups', dodge=False)

# since you are using hue, there are multiple containers
for c in ax.containers:
    # add an annotations with custom labels
    ax.bar_label(c, labels=df.weight, label_type='center')
    # add an annotations with custom labels
    ax.bar_label(c, labels=df.percentage, label_type='edge', padding=1)
    
ax.margins(y=0.1)

enter image description here


  • The issue with the existing code is there are 9 patches, as can be seen with print(patches) or list(patches), which can be resolved by selecting only the patches with a height greater than 0.
    • This occurs because hue is being used, but there is only one value in 'groups' for each value in 'rules'.
plots = sns.barplot(x="rules", y="count", data=df, hue=df['groups'], dodge=False)
percentage = df['percentage'].tolist()
weight = df['weight'].tolist()
patches = plots.patches

# select patches with a height > 0
patches = [p for p in patches if p.get_height() > 0]

for i, p in enumerate(patches):
    
    x = p.get_x() + patches[i].get_width()/2
    y = p.get_height()+.09
    
    plots.annotate(f'{percentage[i]}', (x, y), ha='center',  va='bottom', size=14)
    plots.annotate(f'{weight[i]:.0f}', (x, y), ha='center', va='top', color='white', size=15, fontweight="bold")

enter image description here

Answered By: Trenton McKinney