How to add value labels on a bar chart
Asked Answered
P

7

179

I'm creating a bar chart, and I can't figure out how to add value labels on the bars (in the center of the bar, or just above it).

I believe the solution is either with 'text' or 'annotate', but I: a) don't know which one to use (and generally speaking, haven't figured out when to use which). b) can't see to get either to present the value labels.

Here is my code:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
pd.set_option('display.mpl_style', 'default') 
%matplotlib inline

# Bring some raw data.
frequencies = [6, 16, 75, 160, 244, 260, 145, 73, 16, 4, 1]

# In my original code I create a series and run on that, 
# so for consistency I create a series from the list.
freq_series = pd.Series(frequencies)

x_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0, 
            121740.0, 123980.0, 126220.0, 128460.0, 130700.0]

# Plot the figure.
plt.figure(figsize=(12, 8))
fig = freq_series.plot(kind='bar')
fig.set_title('Amount Frequency')
fig.set_xlabel('Amount ($)')
fig.set_ylabel('Frequency')
fig.set_xticklabels(x_labels)

enter image description here

How can I add value labels on the bars (in the center of the bar, or just above it)?

Protozoal answered 8/3, 2015 at 20:0 Comment(2)
Matplotlib has a demo: matplotlib.org/examples/api/barchart_demo.htmlTiro
For matplotlib >= 3.4.2 use .bar_label, as shown in this answer. Applies to pandas and seaborn, which use matplotlib.Keeton
C
164

Firstly freq_series.plot returns an axis not a figure so to make my answer a little more clear I've changed your given code to refer to it as ax rather than fig to be more consistent with other code examples.

You can get the list of the bars produced in the plot from the ax.patches member. Then you can use the technique demonstrated in this matplotlib gallery example to add the labels using the ax.text method.

import pandas as pd
import matplotlib.pyplot as plt

# Bring some raw data.
frequencies = [6, 16, 75, 160, 244, 260, 145, 73, 16, 4, 1]
# In my original code I create a series and run on that,
# so for consistency I create a series from the list.
freq_series = pd.Series(frequencies)

x_labels = [
    108300.0,
    110540.0,
    112780.0,
    115020.0,
    117260.0,
    119500.0,
    121740.0,
    123980.0,
    126220.0,
    128460.0,
    130700.0,
]

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = freq_series.plot(kind="bar")
ax.set_title("Amount Frequency")
ax.set_xlabel("Amount ($)")
ax.set_ylabel("Frequency")
ax.set_xticklabels(x_labels)

rects = ax.patches

# Make some labels.
labels = [f"label{i}" for i in range(len(rects))]

for rect, label in zip(rects, labels):
    height = rect.get_height()
    ax.text(
        rect.get_x() + rect.get_width() / 2, height + 5, label, ha="center", va="bottom"
    )

plt.show()

This produces a labeled plot that looks like:

enter image description here

Crosier answered 8/3, 2015 at 20:52 Comment(0)
D
98

Based on a feature mentioned in this answer to another question I have found a very generally applicable solution for placing labels on a bar chart.

Other solutions unfortunately do not work in many cases, because the spacing between label and bar is either given in absolute units of the bars or is scaled by the height of the bar. The former only works for a narrow range of values and the latter gives inconsistent spacing within one plot. Neither works well with logarithmic axes.

The solution I propose works independent of scale (i.e. for small and large numbers) and even correctly places labels for negative values and with logarithmic scales because it uses the visual unit points for offsets.

I have added a negative number to showcase the correct placement of labels in such a case.

The value of the height of each bar is used as a label for it. Other labels can easily be used with Simon's for rect, label in zip(rects, labels) snippet.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Bring some raw data.
frequencies = [6, -16, 75, 160, 244, 260, 145, 73, 16, 4, 1]

# In my original code I create a series and run on that,
# so for consistency I create a series from the list.
freq_series = pd.Series.from_array(frequencies)

x_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0,
            121740.0, 123980.0, 126220.0, 128460.0, 130700.0]

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = freq_series.plot(kind='bar')
ax.set_title('Amount Frequency')
ax.set_xlabel('Amount ($)')
ax.set_ylabel('Frequency')
ax.set_xticklabels(x_labels)


def add_value_labels(ax, spacing=5):
    """Add labels to the end of each bar in a bar chart.

    Arguments:
        ax (matplotlib.axes.Axes): The matplotlib object containing the axes
            of the plot to annotate.
        spacing (int): The distance between the labels and the bars.
    """

    # For each bar: Place a label
    for rect in ax.patches:
        # Get X and Y placement of label from rect.
        y_value = rect.get_height()
        x_value = rect.get_x() + rect.get_width() / 2

        # Number of points between bar and label. Change to your liking.
        space = spacing
        # Vertical alignment for positive values
        va = 'bottom'

        # If value of bar is negative: Place label below bar
        if y_value < 0:
            # Invert space to place label below
            space *= -1
            # Vertically align label at top
            va = 'top'

        # Use Y value as label and format number with one decimal place
        label = "{:.1f}".format(y_value)

        # Create annotation
        ax.annotate(
            label,                      # Use `label` as label
            (x_value, y_value),         # Place label at end of the bar
            xytext=(0, space),          # Vertically shift label by `space`
            textcoords="offset points", # Interpret `xytext` as offset in points
            ha='center',                # Horizontally center label
            va=va)                      # Vertically align label differently for
                                        # positive and negative values.


# Call the function above. All the magic happens there.
add_value_labels(ax)

plt.savefig("image.png")

Edit: I have extracted the relevant functionality in a function, as suggested by barnhillec.

This produces the following output:

Bar chart with automatically placed labels on each bar

And with logarithmic scale (and some adjustment to the input data to showcase logarithmic scaling), this is the result:

Bar chart with logarithmic scale with automatically placed labels on each bar

Driftwood answered 21/1, 2018 at 23:6 Comment(0)
K
82

Enhanced Bar Chart Annotations with matplotlib.pyplot.bar_label

Introduction

The matplotlib.pyplot.bar_label function, introduced in matplotlib v3.4.0, simplifies the process of adding labels to bar charts. This guide explores how to use this feature to make your data visualizations more informative and easier to understand.

Key Features and Usage

  • Label Positioning: Labels are positioned at the bar 'edge' by default, with an option to place them 'center' via label_type.
  • Customization: Additional customization is possible by passing kwargs to Axes.annotate, allowing adjustments of text attributes like color, rotation, and fontsize.
  • Format Strings: The fmt argument now supports {}-style format strings, introduced in the matplotlib 3.7 Update, for dynamic label formatting.
  • Conditional Formatting: Labels can be conditionally formatted with the fmt parameter for greater data presentation flexibility.

Understanding ax.containers

ax.containers holds BarContainer artists, crucial for label placement in bar charts. It's simple for single-level plots but contains multiple objects for grouped or stacked plots, reflecting their structure.

Practical Examples

  1. Basic Annotation: Create and label a simple bar chart from a DataFrame.

    # Creating a DataFrame and plotting a bar chart
    import pandas as pd
    df = pd.DataFrame({'Frequency': frequencies}, index=x_labels)
    ax = df.plot(kind='bar', figsize=(12, 8), title='Amount Frequency', xlabel='Amount ($)', ylabel='Frequency', legend=False)
    # Adding labels to the chart
    ax.bar_label(ax.containers[0], label_type='edge')
    # Adjusting margins for clarity
    ax.margins(y=0.1)
    

    enter image description here

    The resulting plots from the seaborn and Axes.bar examples closely mirror the one demonstrated above.

  2. Custom Appearance: Customize the appearance of bar chart labels with parameters from matplotlib.axes.Axes.text.

    # Customizing label appearance
    ax.bar_label(ax.containers[0], label_type='edge', color='red', rotation=90, fontsize=7, padding=3)
    

    enter image description here

  3. Seaborn Axes-level Plot: Annotate a seaborn barplot.

    # Plotting and annotating using seaborn
    import seaborn as sns
    fig, ax = plt.subplots(figsize=(12, 8))
    sns.barplot(x=x_labels, y=frequencies, ax=ax)
    ax.bar_label(ax.containers[0], label_type='edge')
    ax.margins(y=0.1)
    
  4. Seaborn Figure-level Plot: Annotate seaborn's figure-level bar plots.

    df = pd.DataFrame({'Frequency': frequencies, 'amount': x_labels})
    # Annotating seaborn's figure-level plots
    g = sns.catplot(kind='bar', data=df, x='amount', y='Frequency', height=6, aspect=1.5)
    for ax in g.axes.flat:
        ax.bar_label(ax.containers[0], label_type='edge')
        ax.margins(y=0.1)
    
  5. Using matplotlib.axes.Axes.bar: A similar approach can be taken using matplotlib.pyplot.bar for direct plotting.

    # Plotting with matplotlib.axes.Axes.bar
    import matplotlib.pyplot as plt
    xticks = range(len(frequencies))  # Setting up xticks
    fig, ax = plt.subplots(figsize=(12, 8))
    ax.bar(x=xticks, height=frequencies)  # Creating the bar chart
    ax.set_xticks(xticks, x_labels)  # Labeling xticks
    ax.bar_label(ax.containers[0], label_type='edge')  # Annotating bars
    ax.margins(y=0.1)  # Padding for clarity
    

Conditional Formatting with fmt

  • Exclude zero or negative values, showing labels for positive values only.

    # Excluding zero or negative values from labels
    ax.bar_label(ax.containers[0], fmt=lambda x: x if x > 0 else '', label_type='edge')
    ax.bar_label(ax.containers[0], fmt=lambda x: f'{x:0.0f}' if x > 0 else '', label_type='edge')
    
  • Use np.where for more complex conditional formatting.

    # Using np.where for conditional label formatting
    import numpy as np
    ax.bar_label(ax.containers[0], fmt=lambda x: np.where(x > 0, x, ''), label_type='center')
    

Multiple Bar Containers

Handle complex charts, such as grouped or stacked bars, with multiple bar containers.

# Iterating through multiple containers for annotation in complex charts
for c in ax.containers:
    ax.bar_label(c, fmt=lambda x: np.where(x > 0, x, ''), label_type='center')

Extensive Label Customization

For scenarios demanding more intricate label customization beyond default capabilities, specifying manual labels using the labels parameter affords detailed control, as shown in this example, and the following code snippet:

# Generating custom labels for each bar, omitting labels for values less than 0
labels = [f'{h:.1f}%' if (h := v.get_height()) > 0 else '' for v in ax.containers[0]]
ax.bar_label(ax.containers[0], labels=labels, label_type='center')

Compatibility

This code has been tested with Python 3.12.0, pandas 2.2.1, matplotlib 3.8.1, and seaborn 0.13.2.


Additional Resources

For a deeper dive into formatting options and additional examples, the Bar Label Demo page, and the following answers, serve as valuable resources, offering additional guidance for enhancing bar charts.

Answers with label= Parameter

Stacked Bar Charts

Grouped Bar Charts

Other Answers with bar_label

Keeton answered 16/5, 2021 at 22:12 Comment(0)
W
58

Building off the above (great!) answer, we can also make a horizontal bar plot with just a few adjustments:

# Bring some raw data.
frequencies = [6, -16, 75, 160, 244, 260, 145, 73, 16, 4, 1]

freq_series = pd.Series(frequencies)

y_labels = [108300.0, 110540.0, 112780.0, 115020.0, 117260.0, 119500.0, 
            121740.0, 123980.0, 126220.0, 128460.0, 130700.0]

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = freq_series.plot(kind='barh')
ax.set_title('Amount Frequency')
ax.set_xlabel('Frequency')
ax.set_ylabel('Amount ($)')
ax.set_yticklabels(y_labels)
ax.set_xlim(-40, 300) # expand xlim to make labels easier to read

rects = ax.patches

# For each bar: Place a label
for rect in rects:
    # Get X and Y placement of label from rect.
    x_value = rect.get_width()
    y_value = rect.get_y() + rect.get_height() / 2

    # Number of points between bar and label. Change to your liking.
    space = 5
    # Vertical alignment for positive values
    ha = 'left'

    # If value of bar is negative: Place label left of bar
    if x_value < 0:
        # Invert space to place label to the left
        space *= -1
        # Horizontally align label at right
        ha = 'right'

    # Use X value as label and format number with one decimal place
    label = "{:.1f}".format(x_value)

    # Create annotation
    plt.annotate(
        label,                      # Use `label` as label
        (x_value, y_value),         # Place label at end of the bar
        xytext=(space, 0),          # Horizontally shift label by `space`
        textcoords="offset points", # Interpret `xytext` as offset in points
        va='center',                # Vertically center label
        ha=ha)                      # Horizontally align label differently for
                                    # positive and negative values.

plt.savefig("image.png")

horizontal bar plot with annotations

Workday answered 18/7, 2018 at 20:47 Comment(0)
C
53

If you want to just label the data points above the bar, you could use plt.annotate()

My code:

import numpy as np
import matplotlib.pyplot as plt

n = [1,2,3,4,5,]
s = [i**2 for i in n]
line = plt.bar(n,s)
plt.xlabel('Number')
plt.ylabel("Square")

for i in range(len(s)):
    plt.annotate(str(s[i]), xy=(n[i],s[i]), ha='center', va='bottom')

plt.show()

By specifying a horizontal and vertical alignment of 'center' and 'bottom' respectively one can get centered annotations.

a labelled bar chart

Cumuliform answered 3/2, 2020 at 13:13 Comment(0)
G
2

I needed the bar labels too, note that my y-axis is having a zoomed view using limits on y axis. The default calculations for putting the labels on top of the bar still works using height (use_global_coordinate=False in the example). But I wanted to show that the labels can be put in the bottom of the graph too in zoomed view using global coordinates in matplotlib 3.0.2. Hope it help someone.

def autolabel(rects,data):
    """
    Attach a text label above each bar displaying its height
    """
    c = 0
    initial = 0.091
    offset = 0.205
    use_global_coordinate = True
    
    if use_global_coordinate:
        for i in data:        
            ax.text(initial+offset*c, 0.05, str(i), horizontalalignment='center',
                    verticalalignment='center', transform=ax.transAxes,fontsize=8)
            c=c+1
    else:
        for rect,i in zip(rects,data):
            height = rect.get_height()
            ax.text(rect.get_x() + rect.get_width()/2., height,str(i),ha='center', va='bottom')

Example output

Grille answered 27/11, 2018 at 12:42 Comment(0)
M
1

If you only want to add Datapoints above the bars, you could easily do it with:

 for i in range(len(frequencies)): # your number of bars
    plt.text(x = x_values[i]-0.25, #takes your x values as horizontal positioning argument 
    y = y_values[i]+1, #takes your y values as vertical positioning argument 
    s = data_labels[i], # the labels you want to add to the data
    size = 9) # font size of datalabels
Mchenry answered 16/7, 2019 at 11:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.