Search code examples
rggplot2geom-bargeom-text

Modifying text labels on top ggplot percentage barplot


I am trying to generate a grouped barplot with percentage count on the y-axis and text on the top of each bar that represent its value

My code is below:

  geom_bar(aes(y = (..count..)/sum(..count..) * 100), width = 0.7) + 
  labs(x = "Hours Worked 48 or more", y = "% of Employees", fill = "Hours Worked 48 or more", title = "Post-Legislation") + 
  theme_minimal() +
  scale_fill_manual(values = c("orange", "blue")) + 
  geom_text(aes(label = (..count..)/sum(..count..) * 100, y = ..prop..), stat= "count", vjust = -.5) +
  theme(legend.position = "top")

enter image description here

I want the text to be white, accurate to 1dp, and placed at the top of each bar.

I have been trying to different codes, but can't get the desired result.

Any help would be appreciated.

Here is the data snippet:

structure(list(year = c("2018", "2018", "2018", "2018", "2018", 
"2018", "2018", "2018", "2018", "2018", "2018", "2018", "2018", 
"2018", "2018", "2018", "2018", "2018", "2018", "2018", "2018", 
"2018", "2018", "2018", "2018", "2018", "2018", "2018", "2018", 
"2018"), hours.48 = c("Yes", "No", "No", "No", "Yes", "No", "No", 
"No", "No", "No", "No", "No", "No", "No", "Yes", "No", "No", 
"No", "No", "No", "No", "No", "No", "No", "No", "No", "No", "No", 
"No", "No")), row.names = c(NA, 30L), class = "data.frame")

Solution

  • Try this:

    library(tidyverse)
    library(ggplot2)
    
    positions = df %>% group_by(hours.48) %>% summarise(prop=n()/nrow(df)*100)
    

    Note I am precomputing the labels = positions here. Given your example data, there aren't any decimals, but you can format the values you would like to see here if you want to go down this route. Personally, I prefer precomputing everything I want to plot to separate responsibilities.

    ggplot(df, aes(x=hours.48, fill=hours.48)) +
    geom_bar(aes(y = (..count..)/sum(..count..) * 100), width = 0.7) + 
      labs(x = "Hours Worked 48 or more", y = "% of Employees", fill = "Hours Worked 48 or more", title = "Post-Legislation") + 
      theme_minimal() +
      scale_fill_manual(values = c("orange", "blue")) + 
      geom_text(data=positions, aes(label = prop, y = prop), colour='white', stat= "identity", vjust = 1) +
      theme(legend.position = "top")
    

    enter image description here

    You can also simplify this even more by just querying positions as dataframe instead of the original df by using the following two lines instead of the corresponding ones above:

    ggplot(positions, aes(x=hours.48, fill=hours.48)) +
      geom_bar(aes(y = prop), width = 0.7, stat='identity') +