Adding value labels on a matplotlib bar chart
Solution 1
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:
Solution 2
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:
And with logarithmic scale (and some adjustment to the input data to showcase logarithmic scaling), this is the result:
Solution 3
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")
Solution 4
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.
Solution 5
As of matplotlib v3.4.2
- Use
matplotlib.pyplot.bar_label
- The default label position, set with the parameter
label_type
, is'edge'
. To center the labels in the middle of the bar, use'center'
- Additional
kwargs
are passed toAxes.annotate
, which acceptsText
kwargs
.
- The default label position, set with the parameter
- See the matplotlib: Bar Label Demo page for additional formatting options.
- Tested with
pandas 1.3.3
, which is usingmatplotlib 3.4.3
as the plot engine. -
ax.containers
is alist
ofBarContainer artists
- With a single level bar plot, it's a list of len 1, hence
[0]
is used. - For grouped and stacked bar plots there will be more objects in the
list
- See How to annotate each segment of a stacked bar chart or Stacked Bar Chart with Centered Labels.
- For grouped bar examples
- With a single level bar plot, it's a list of len 1, hence
- Simple label formatting can be done with the
fmt
parameter, as shown in the Demo examples and at How to annotate a seaborn barplot with the aggregated value. - More sophisticated label formatting should use the
label
parameter, as shown in the Demo examples and the following- stack bar plot in matplotlib and add label to each section
- How to add multiple annotations to a barplot
- How to plot a horizontal stacked bar with annotations
- How to align annotations at the end of a horizontal bar plot
- How to annotate a stacked bar plot and add legend labels
- How to customize bar annotations to not show selected values
- How to annotate bar plots when adding error bars
import pandas as pd
# dataframe using frequencies and x_labels from the OP
df = pd.DataFrame({'Frequency': frequencies}, index=x_labels)
# display(df)
Frequency
108300.0 6
110540.0 16
112780.0 75
115020.0 160
117260.0 244
# plot
ax = df.plot(kind='bar', figsize=(12, 8), title='Amount Frequency',
xlabel='Amount ($)', ylabel='Frequency', legend=False)
# annotate
ax.bar_label(ax.containers[0], label_type='edge')
# pad the spacing between the number and the edge of the figure
ax.margins(y=0.1)
- Specify additional
kwargs
for additional customization- Accepts parameters from
matplotlib.axes.Axes.text
- Accepts parameters from
ax.bar_label(ax.containers[0], label_type='edge', color='red', rotation=90, fontsize=7, padding=3)
Examples with bar_label
- How to create and annotate a stacked proportional bar chart
- How to annotate bar plots when adding error bars
- How to calculate percent by row and annotate 100 percent stacked bars
- Stacked bars are unexpectedly annotated with the sum of bar heights
- How to plot and annotate grouped bars
- How to annotate bar chart with values different to those from get_height()
- Pandas bar how to label desired values
- How to display percentage above grouped bar chart
- How to customize bar annotations to not show selected values
- How to set ticklabel rotation and add bar annotations
- How to aggregate group metrics and plot data with pandas
- How to plot a stacked bar with annotations for multiple groups
- How to annotate a stackplot or area plot
- How to plot grouped bars
- How to add multiple data labels in a bar chart in matplotlib
- Python matplotlib multiple bars
- plt grid ALPHA parameter not working in matplotlib
- Matplotlib pie chart label does not match value
- How to wrap long tick labels in a seaborn figure-level plot
- How to annotate barplot with percent by hue/legend group
- How to add percentages on top of bars in seaborn
- How to plot percentage with seaborn distplot / histplot / displot
- How to annotate a stacked bar plot and add legend labels
- How to plot grouped bars in the correct order
- Problem with plotting two lists with different sizes using matplotlib
- How to annotate only one category of a stacked bar plot
- How to Increase subplot text size and add custom bar plot annotations
- How to get a grouped bar plot of categorical data
- How to create grouped bar plots in a single figure from a wide dataframe
- How to determine if the last value in all columns is greater than n
- How to plot element count and add annotations
- Seaborn Catplot set values over the bars
- Adding value labels on a matplotlib bar chart
- How to annotate a seaborn barplot with the aggregated value
- stack bar plot in matplotlib and add label to each section
- How to annotate each segment of a stacked bar chart
- How to plot and annotate a grouped bar chart
- How to align annotations at the end of a horizontal bar plot
- How to plot and annotate grouped bars in seaborn / matplotlib
- How to horizontally center a bar plot annotation
- How to plot a horizontal stacked bar with annotations
- How to add multiple annotations to a barplot
- Stacked Bar Chart with Centered Labels
Optimesh
Updated on December 30, 2021Comments
-
Optimesh over 2 years
I got stuck on something that feels like should be relatively easy. The code I bring below is a sample based on a larger project I'm working on. I saw no reason to post all the details, so please accept the data structures I bring as is.
Basically, I'm creating a bar chart, and I just can figure out how to add value labels on the bars (in the center of the bar, or just above it). Been looking at samples around the web but with no success implementing on my own code. 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. Would appreciate your help, my code below. Thanks in advance!
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.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)) fig = freq_series.plot(kind='bar') fig.set_title('Amount Frequency') fig.set_xlabel('Amount ($)') fig.set_ylabel('Frequency') fig.set_xticklabels(x_labels)
-
Dan over 6 yearsMatplotlib has a demo: matplotlib.org/examples/api/barchart_demo.html
-
Trenton McKinney over 2 yearsFor
matplotlib >= 3.4.2
use.bar_label
, as shown in this answer. Applies topandas
andseaborn
, which usematplotlib
.
-
-
Simon Gibbons about 9 yearsA figure is a collection of one or more axes, e.g. in this in this example matplotlib.org/examples/statistics/… it is one figure that is made up of 4 different axes.
-
Lindsey Kuper over 7 yearsNice solution. I wrote a blog post that builds on the solution here and gives a slightly more robust version that scales according to the height of the axis, so the same code works for different plots that have different axis heights: composition.al/blog/2015/11/29/…
-
Yılmaz Alpaslan over 2 yearsThank you so much! I've been trying to make the top answer's code work with horizontal bars but finally this one worked.