rnv86
rnv86

Reputation: 880

How to label bars with multiple custom values

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

Upvotes: 2

Views: 1896

Answers (1)

Trenton McKinney
Trenton McKinney

Reputation: 62373

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

Upvotes: 4

Related Questions