Search code examples
rggplot2dplyr

Problems adding errorbars to a clustered column chart in ggplot2


I am trying to add SEM errorbars to a ggplot clustered bar chart but they are all appearing on top of eachother in the middle bar of each cluster, rather than over the corresponding bar. I am not sure how to fix this. I have tried manually fixing the dodge applied. code to create sample data:

id <- 1:15
role <- rep(c("Admin", "User","bug"), each = 5)  # Example roles
Questions <- factor(c("blue", "black", "pink", "red", "gold"), levels = c("blue", "black", "pink", "red", "gold"))
Answers <- factor(1:5, levels = 1:5)

set.seed(123)  # For reproducibility
data <- expand.grid(id = id, Questions = Questions)

data$role <- rep(role, each = length(Questions))


data$Answers <- factor(sample(1:5, nrow(data), replace = TRUE), levels = 1:5)

data <- data %>% 
select(id, role, Questions, Answers)

the code I am using to create the plot:

dodge <- position_dodge(width = 0.9)
  
data %>%
  group_by(Questions,role)%>%
  summarise(mean_score = mean(as.numeric(Answers)),
            sem = sd(as.numeric(Answers)) / sqrt(n()),.groups = "keep")%>%
  ggplot(aes(reorder(Questions,-mean_score), mean_score))+
  geom_col(aes(fill = role), position = "dodge")+
  geom_errorbar(aes(ymin = mean_score - sem, ymax = mean_score + sem), width = 0.2, position = "dodge") +
  xlab("")+
  ylab("Mean score")+
  coord_flip()+
  theme_classic()+
  scale_fill_viridis_d()

(I realise that the repex doesn't generate bars for every group in every category, the issue is still clear in the categories with multiple bars though)


Solution

    • Add a group = role inside the aesthetics for the errorbar (the fill argument creates the group for the columns).

    • If you're specifying the dodge as a variable, make sure you include it without quotations.

    dodge <- position_dodge(width = 0.9)
    
    data %>%
      group_by(Questions, role) %>%
      summarise(
        mean_score = mean(as.numeric(Answers)),
        sem = sd(as.numeric(Answers)) / sqrt(n()), .groups = "keep"
      ) %>%
      ggplot(aes(reorder(Questions, -mean_score), mean_score)) +
      geom_col(aes(fill = role), position = dodge) +
      geom_errorbar(
        aes(
          ymin = mean_score - sem,
          ymax = mean_score + sem, group = role
        ),
        width = 0.2, position = dodge
      ) +
      xlab("") +
      ylab("Mean score") +
      coord_flip() +
      theme_classic() +
      scale_fill_viridis_d()
    

    enter image description here