rggplot2

geom_bar - stack + dodge for categorical variable


I did find something about this for numeric values (ggplot bar chart with two dataframes) as well as date, but not for categorical, so I'm having issues.

I would like "grade" to be the stack, and then have the two groups next to each other at each x-axis point.

library(ggplot2)
data <- data.frame('group'=c(rep('g1',50), rep('g2',50)), 
                   'grade'=c(rep('1-2',20), rep('3-4',40), 
                             rep('1-2',35), rep('3-4',5)),
                   'problem'=c(rep('p1',5), rep('p2',52), 
                               rep('p3',29), rep('p4',14)))

This is what I'm using to just do the grade part, with descending frequency

ggplot(data, aes(fill=grade, x=reorder(problem, problem, 
       function(x)-length(x)))) + 
  geom_bar(aes(y = (..count..)/sum(..count..)))

I tried this based on the link provided, but it doesn't work because problem isn't numeric

ggplot(mapping=aes(x=problem)) +
  geom_bar(data=data1, aes(x=problem-0.1), fill="red", binwidth=0.1) +
  geom_bar(data=data2, fill="blue", binwidth=0.1)

Should I just map problem to numeric, and then relabel it, or is there a way to do an offset for a category? There was also an example using dates (geom_bar(): stacked and dodged combined) where the shift works, presumably because dates have numeric equivalents.


Solution

  • I shuffled the group column to showcase the solution better; here's the plot for the original data.

    library(ggplot2)
    
    set.seed(42)
    data <- data.frame(group = sample(c(rep('g1', 50), rep('g2', 50))), 
                       grade = c(rep('1-2', 20), rep('3-4', 40), 
                                 rep('1-2', 35), rep('3-4', 5)),
                       problem = c(rep('p1', 5), rep('p2', 52), 
                                   rep('p3', 29), rep('p4', 14)))
    
    within(data, 
           xdoge <- as.integer(reorder(problem, problem, 
                                       function(x) -length(x))) + 
             0.4 * scale(as.numeric(as.factor(group)), scale = FALSE)) |> 
    ggplot(aes(x = xdoge)) + 
      geom_bar(aes(color = group, y = after_stat(count)/sum(after_stat(count))), 
               width = 0.35, size = 2, fill = NA) +
      geom_bar(aes(fill = grade, y = after_stat(count)/sum(after_stat(count))), 
               width = 0.35) +
      scale_x_continuous(breaks = seq_along(unique(data$problem)),
                         labels = levels(reorder(data$problem, data$problem, 
                                                 function(x) -length(x)))) +
      scale_colour_grey(start = 0.4, end = 0.8) +
      theme_bw() +
      labs(x = "Problem", y = "Proportion",
           fill = "Grade", color = "Group")
    

    Note that dot-dot notation is deprecated. Use after_stat.

    Created on 2025-07-25 with reprex v2.1.1