3.9 Adding Labels to a Bar Graph

3.9.1 Problem

You want to add labels to the bars in a bar graph.

3.9.2 Solution

Add geom_text() to your graph. It requires a mapping for x, y, and the text itself. By setting vjust (the vertical justification), it is possible to move the text above or below the tops of the bars, as shown in Figure 3.22:

library(gcookbook) # Load gcookbook for the cabbage_exp data set

# Below the top
ggplot(cabbage_exp, aes(x = interaction(Date, Cultivar), y = Weight)) +
  geom_col() +
  geom_text(aes(label = Weight), vjust = 1.5, colour = "white")
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'interaction(Date, Cultivar)' with labels d16.c39, d20.c39, d21.c39, d16.c52, d20.c52 and d21.c52.
#> It has y-axis 'Weight' with labels 0, 1, 2 and 3.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at d16.c39, and spans vertically from 0 to 3.18.
#> Bar 2 is centered horizontally at d20.c39, and spans vertically from 0 to 2.8.
#> Bar 3 is centered horizontally at d21.c39, and spans vertically from 0 to 2.74.
#> Bar 4 is centered horizontally at d16.c52, and spans vertically from 0 to 2.26.
#> Bar 5 is centered horizontally at d20.c52, and spans vertically from 0 to 3.11.
#> Bar 6 is centered horizontally at d21.c52, and spans vertically from 0 to 1.47.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has vjust set to 1.5.
#> Layer 2 has colour set to white.

# Above the top
ggplot(cabbage_exp, aes(x = interaction(Date, Cultivar), y = Weight)) +
  geom_col() +
  geom_text(aes(label = Weight), vjust = -0.2)
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'interaction(Date, Cultivar)' with labels d16.c39, d20.c39, d21.c39, d16.c52, d20.c52 and d21.c52.
#> It has y-axis 'Weight' with labels 0, 1, 2 and 3.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at d16.c39, and spans vertically from 0 to 3.18.
#> Bar 2 is centered horizontally at d20.c39, and spans vertically from 0 to 2.8.
#> Bar 3 is centered horizontally at d21.c39, and spans vertically from 0 to 2.74.
#> Bar 4 is centered horizontally at d16.c52, and spans vertically from 0 to 2.26.
#> Bar 5 is centered horizontally at d20.c52, and spans vertically from 0 to 3.11.
#> Bar 6 is centered horizontally at d21.c52, and spans vertically from 0 to 1.47.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has vjust set to -0.2.
Labels under the tops of bars (left); Labels above bars (right)Labels under the tops of bars (left); Labels above bars (right)

Figure 3.22: Labels under the tops of bars (left); Labels above bars (right)

Notice that when the labels are placed atop the bars, they may be clipped. To remedy this, see Recipe ??.

Another common scenario is to add labels for a bar graph of counts instead of values. To do this, use geom_bar(), which adds bars whose height is proportional to the number of rows, and then use geom_text() with counts:

ggplot(mtcars, aes(x = factor(cyl))) +
  geom_bar() +
  geom_text(aes(label = ..count..), stat = "count", vjust = 1.5, colour = "white")
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'factor(cyl)' with labels 4, 6 and 8.
#> It has y-axis 'count' with labels 0, 5 and 10.
#> It has 2 layers.
#> Layer 1 is a bar chart with 3 vertical bars.
#> Bar 1 is centered horizontally at 4, and spans vertically from 0 to 11.
#> Bar 2 is centered horizontally at 6, and spans vertically from 0 to 7.
#> Bar 3 is centered horizontally at 8, and spans vertically from 0 to 14.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has vjust set to 1.5.
#> Layer 2 has colour set to white.
Bar graph of counts with labels under the tops of bars

Figure 3.23: Bar graph of counts with labels under the tops of bars

We needed to tell geom_text() to use the "count" statistic to compute the number of rows for each x value, and then, to use those computed counts as the labels, we told it to use the aesthetic mapping aes(label = ..count..).

3.9.3 Discussion

In Figure 3.22, the y coordinates of the labels are centered at the top of each bar; by setting the vertical justification (vjust), they appear below or above the bar tops. One drawback of this is that when the label is above the top of the bar, it can go off the top of the plotting area. To fix this, you can manually set the y limits, or you can set the y positions of the text above the bars and not change the vertical justification. One drawback to changing the text’s y position is that if you want to place the text fully above or below the bar top, the value to add will depend on the y range of the data; in contrast, changing vjust to a different value will always move the text the same distance relative to the height of the bar:

# Adjust y limits to be a little higher
ggplot(cabbage_exp, aes(x = interaction(Date, Cultivar), y = Weight)) +
  geom_col() +
  geom_text(aes(label = Weight), vjust = -0.2) +
  ylim(0, max(cabbage_exp$Weight) * 1.05)

# Map y positions slightly above bar top - y range of plot will auto-adjust
ggplot(cabbage_exp, aes(x = interaction(Date, Cultivar), y = Weight)) +
  geom_col() +
  geom_text(aes(y = Weight + 0.1, label = Weight))

For grouped bar graphs, you also need to specify position=position_dodge() and give it a value for the dodging width. The default dodge width is 0.9. Because the bars are narrower, you might need to use size to specify a smaller font to make the labels fit. The default value of size is 5, so we’ll make it smaller by using 3 (Figure 3.24):

ggplot(cabbage_exp, aes(x = Date, y = Weight, fill = Cultivar)) +
  geom_col(position = "dodge") +
  geom_text(
    aes(label = Weight),
    colour = "white", size = 3,
    vjust = 1.5, position = position_dodge(.9)
  )
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'Date' with labels d16, d20 and d21.
#> It has y-axis 'Weight' with labels 0, 1, 2 and 3.
#> There is a legend indicating fill is used to show Cultivar, with 2 levels:
#> c39 shown as strong reddish orange fill and 
#> c52 shown as brilliant bluish green fill.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at 0.78, and spans vertically from 0 to 3.18 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 2 is centered horizontally at 1.77, and spans vertically from 0 to 2.8 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 3 is centered horizontally at 2.78, and spans vertically from 0 to 2.74 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 4 is centered horizontally at 1.23, and spans vertically from 0 to 2.26 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 5 is centered horizontally at 2.22, and spans vertically from 0 to 3.11 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 6 is centered horizontally at 3.22, and spans vertically from 0 to 1.47 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has colour set to white.
#> Layer 2 has size set to 3.
#> Layer 2 has vjust set to 1.5.
Labels on grouped bars

Figure 3.24: Labels on grouped bars

Putting labels on stacked bar graphs requires finding the cumulative sum for each stack. To do this, first make sure the data is sorted properly – if it isn’t, the cumulative sum might be calculated in the wrong order. We’ll use the arrange() function from the dplyr package. Note that we have to use the rev() function to reverse the order of Cultivar:

library(dplyr)

# Sort by the Date and Cultivar columns
ce <- cabbage_exp %>%
  arrange(Date, rev(Cultivar))

Once we make sure the data is sorted properly, we’ll use group_by() to chunk it into groups by Date, then calculate a cumulative sum of Weight within each chunk:

# Get the cumulative sum
ce <- ce %>%
  group_by(Date) %>%
  mutate(label_y = cumsum(Weight))

ce
#> # A tibble: 6 x 7
#> # Groups:   Date [3]
#>   Cultivar Date  Weight    sd     n     se label_y
#>   <fct>    <fct>  <dbl> <dbl> <int>  <dbl>   <dbl>
#> 1 c52      d16     2.26 0.445    10 0.141     2.26
#> 2 c39      d16     3.18 0.957    10 0.303     5.44
#> 3 c52      d20     3.11 0.791    10 0.250     3.11
#> 4 c39      d20     2.8  0.279    10 0.0882    5.91
#> 5 c52      d21     1.47 0.211    10 0.0667    1.47
#> 6 c39      d21     2.74 0.983    10 0.311     4.21

ggplot(ce, aes(x = Date, y = Weight, fill = Cultivar)) +
  geom_col() +
  geom_text(aes(y = label_y, label = Weight), vjust = 1.5, colour = "white")
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'Date' with labels d16, d20 and d21.
#> It has y-axis 'Weight' with labels 0, 2, 4 and 6.
#> There is a legend indicating fill is used to show Cultivar, with 2 levels:
#> c39 shown as strong reddish orange fill and 
#> c52 shown as brilliant bluish green fill.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at d16, and spans vertically from 0 to 2.26 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 2 is centered horizontally at d16, and spans vertically from 2.26 to 5.44 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 3 is centered horizontally at d20, and spans vertically from 0 to 3.11 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 4 is centered horizontally at d20, and spans vertically from 3.11 to 5.91 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 5 is centered horizontally at d21, and spans vertically from 0 to 1.47 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 6 is centered horizontally at d21, and spans vertically from 1.47 to 4.21 with fill colour strong reddish orange which maps to Cultivar = c39.
#> These are stacked, as sorted by Cultivar.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has vjust set to 1.5.
#> Layer 2 has colour set to white.
Labels on stacked bars

Figure 3.25: Labels on stacked bars

The result is shown in Figure 3.25.

When using labels, changes to the stacking order are best done by modifying the order of levels in the factor (see Recipe ??) before taking the cumulative sum. The other method of changing stacking order, by specifying breaks in a scale, won’t work properly, because the order of the cumulative sum won’t be the same as the stacking order.

To put the labels in the middle of each bar (Figure 3.26), there must be an adjustment to the cumulative sum, and the y offset in geom_bar() can be removed:

ce <- cabbage_exp %>%
  arrange(Date, rev(Cultivar))

# Calculate y position, placing it in the middle
ce <- ce %>%
  group_by(Date) %>%
  mutate(label_y = cumsum(Weight) - 0.5 * Weight)

ggplot(ce, aes(x = Date, y = Weight, fill = Cultivar)) +
  geom_col() +
  geom_text(aes(y = label_y, label = Weight), colour = "white")
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'Date' with labels d16, d20 and d21.
#> It has y-axis 'Weight' with labels 0, 2, 4 and 6.
#> There is a legend indicating fill is used to show Cultivar, with 2 levels:
#> c39 shown as strong reddish orange fill and 
#> c52 shown as brilliant bluish green fill.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at d16, and spans vertically from 0 to 2.26 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 2 is centered horizontally at d16, and spans vertically from 2.26 to 5.44 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 3 is centered horizontally at d20, and spans vertically from 0 to 3.11 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 4 is centered horizontally at d20, and spans vertically from 3.11 to 5.91 with fill colour strong reddish orange which maps to Cultivar = c39.
#> Bar 5 is centered horizontally at d21, and spans vertically from 0 to 1.47 with fill colour brilliant bluish green which maps to Cultivar = c52.
#> Bar 6 is centered horizontally at d21, and spans vertically from 1.47 to 4.21 with fill colour strong reddish orange which maps to Cultivar = c39.
#> These are stacked, as sorted by Cultivar.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has colour set to white.
Labels in the middle of stacked bars

Figure 3.26: Labels in the middle of stacked bars

For a more polished graph (Figure 3.27), we’ll change the colors, add labels in the middle with a smaller font using size, add a “kg” suffix using paste, and make sure there are always two digits after the decimal point by using format():

ggplot(ce, aes(x = Date, y = Weight, fill = Cultivar)) +
  geom_col(colour = "black") +
  geom_text(aes(y = label_y, label = paste(format(Weight, nsmall = 2), "kg")), size = 4) +
  scale_fill_brewer(palette = "Pastel1")
#> This is an untitled chart with no subtitle or caption.
#> It has x-axis 'Date' with labels d16, d20 and d21.
#> It has y-axis 'Weight' with labels 0, 2, 4 and 6.
#> There is a legend indicating fill is used to show Cultivar, with 2 levels:
#> c39 shown as vivid pink fill and 
#> c52 shown as very pale blue fill.
#> It has 2 layers.
#> Layer 1 is a bar chart with 6 vertical bars.
#> Bar 1 is centered horizontally at d16, and spans vertically from 0 to 2.26 with fill colour very pale blue which maps to Cultivar = c52.
#> Bar 2 is centered horizontally at d16, and spans vertically from 2.26 to 5.44 with fill colour vivid pink which maps to Cultivar = c39.
#> Bar 3 is centered horizontally at d20, and spans vertically from 0 to 3.11 with fill colour very pale blue which maps to Cultivar = c52.
#> Bar 4 is centered horizontally at d20, and spans vertically from 3.11 to 5.91 with fill colour vivid pink which maps to Cultivar = c39.
#> Bar 5 is centered horizontally at d21, and spans vertically from 0 to 1.47 with fill colour very pale blue which maps to Cultivar = c52.
#> Bar 6 is centered horizontally at d21, and spans vertically from 1.47 to 4.21 with fill colour vivid pink which maps to Cultivar = c39.
#> These are stacked, as sorted by Cultivar.
#> Layer 1 has colour set to black.
#> Layer 2 is a text graph that VI can not process.
#> Layer 2 has size set to 4.
Customized stacked bar graph with labels

Figure 3.27: Customized stacked bar graph with labels

3.9.4 See Also

To control the appearance of the text, see Recipe ??.

For more on transforming data by groups, see Recipe 7.3.