Skip to content

Labeling bar charts with ggplot2

Prerequisites

You need to have the tidyverse package installed and loaded to run the code in this post.

Here is the theme we use for the plots
custom_theme <- ggplot2::theme_minimal() %+replace%
ggplot2::theme(
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.background = element_blank(),
    axis.ticks.y = element_blank(),
    axis.text.y = element_blank(),
    axis.title.y = element_blank(),
    legend.position = "none",
    text = element_text(size = 16)
)

ggplot2::theme_set(custom_theme)

Suppose we want to create a bar chart with labels on top of the bars. This can be achieved using the geom_text function. However, the labels are not aligned with the bars. To fix this we need to adjust the labels using the vjust argument. The correct adjustment is vjust = -0.5. Now the resulting bar chart looks great!

Bar chart using the mtcars dataset

This works fine, if all the values are positive. But what if we have negative values?

To test what happens, we will use a fictional dataset that contains the net profit of some company for different years.

company_data <- tibble::tibble(
    year = c(2015, 2016, 2017, 2018, 2019),
    return = c(-3.2, -1.5, 2.3, -0.1, 1.2)
)
Now we plot the net profit
  ggplot(
    company_data,
    aes(
        x = year,
        y = return,
        fill = return > 0
    )
  ) +
  geom_bar(stat = "identity") +
  geom_text(
      aes(
          label = return
      ),
      vjust = -0.5
  ) +
  scale_fill_manual(
      values = c("#9b2113", "#024e73"),
      guide = FALSE
  ) +
  labs(
      title = "Net profit for each year",
      subtitle = "In millions of dollars",
      x = "Year"
  )

Bar chart using the company data with messed up labels Bar chart using the company data with fixed labels

As we can see, the labels are not aligned with the bars 😥. This is because we just raise each label, but we actually want to lower the label if the value is negative.

How to fix it

To fix this plot we need to adjust the vjust argument depending on the value. This can be done using ifelse(return > 0, -0.5, adjustment_for_negative_values). The correct adjustment is not 0.5, as one would assume but 1.5. To access the value of return, we also need to put the vjust argument inside the aes function.

Putting everything together we get the following code

  ggplot(
    company_data,
    aes(
        x = year,
        y = return,
        fill = return > 0
    )
  ) +
  geom_bar(stat = "identity") +
  geom_text(
      aes(
          label = return,
          vjust = ifelse(
            return > 0, -0.5, 1.5
        )
      ),
      size = 5
  ) +
  scale_fill_manual(
      values = c("#9b2113", "#024e73"),
      guide = FALSE
  ) +
  labs(
      title = "Net profit for each year",
      subtitle = "In millions of dollars",
      x = "Year"
  )