matplotlibbar-chart

How to selectively label bars using plt.bar_label()?


When drawing a bar plot in matplotlib using bars = plt.bar(...), the returned object can be passed to plt.bar_label(bars, labels) for easy labelling.

plt.bar_label(bars, labels) labels all of the bars. I want to only label a subset (the first three), ideally using plt.bar_label(). I don't want to invoke plt.text() or plt.annotate().

Is there a way of slicing out the bars I want from bars, and only having those labelled using plt.bar_label()?


Example below shows the default behaviour where all bars get labelled.

enter image description here

#Imports
import matplotlib.pyplot as plt
import pandas as pd

# Data for testing
values = [10, 9, 10.5, 4, 3.1, 2]
labels = [
    'sensor calibration', 'sensor response', 'noise floor estimate',
    'noise floor theta', 'weighted response coef', 'linear estimate'
]

#Bar plot with labels
ax = plt.figure(figsize=(5, 2)).add_subplot()
bars = ax.bar(range(6), values, color='lightgray')
ax.set(xlabel='x', ylabel='value', title='Data')
ax.spines[['top', 'right']].set_visible(False)

#Add all bar labels
label_kwargs = dict(rotation=90, weight='bold', fontsize=8, label_type='center')
ax.bar_label(bars, labels, **label_kwargs)

Solution

  • An easy option would be to provide empty labels for the bars that shouldn't be labeled:

    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    
    # Data for testing
    values = [10, 9, 10.5, 4, 3.1, 2]
    labels = [
        'sensor calibration', 'sensor response', 'noise floor estimate',
        'noise floor theta', 'weighted response coef', 'linear estimate'
    ]
    
    # Bar plot with labels
    fig, ax = plt.subplots(figsize=(5, 3))
    bars = ax.bar(range(6), values, color='lightgray')
    ax.set(xlabel='x', ylabel='value', title='Data')
    ax.spines[['top', 'right']].set_visible(False)
    
    # Add centered bar labels
    k = 3
    label_kwargs = dict(rotation=90, weight='bold', fontsize=8, label_type='center')
    ax.bar_label(bars, labels[:k] + [''] * (len(bars) - k), **label_kwargs)
    
    plt.show()
    

    bar_label for selected bars

    Here is a variant that places centered labels for the long bars, and top labels for the shorter bars:

    # Bar plot with labels
    fig, ax = plt.subplots(figsize=(5, 3))
    bars = ax.bar(range(6), values, color='lightgray')
    ax.set(xlabel='x', ylabel='value', title='Data')
    ax.spines[['top', 'right']].set_visible(False)
    
    cutoff = 5  # bars higher will get centered labels, lower bars get labels on top
    # Add the centered bar labels
    label_kwargs = dict(rotation=90, weight='bold', fontsize=8, label_type='center')
    ax.bar_label(bars, ['' if val <= cutoff else lbl for val, lbl in zip(values, labels)], **label_kwargs)
    # Add bar labels at the top of the other bars
    label_kwargs['label_type'] = 'edge'
    label_kwargs['padding'] = 2
    ax.bar_label(bars, ['' if val > cutoff else lbl for val, lbl in zip(values, labels)], **label_kwargs)
    
    plt.show()
    

    bar_label centered vs on top depending on bar heights