Search code examples
rggplot2formattingbar-chartstacked-chart

Formatting GGplot stacked barplot


I am making a set of scorecards where I am generating a set of graphs that show the distribution of responses from a survey and also where the response for a specific company falls. I need to modify the formatting of a graph, a stacked barchart, and add a few features I’ve outlined below. I’ve already spent a few hours getting my chart to where it is now and would appreciate your help with the features I outline below.

Data is

Data<-data.frame(Reviewed = c("Annually", "Annually", "Hourly", "Monthly", "Weekly","Monthly","Weekly","Other","Other","Monthly","Weekly"),Company=c("a","b","c","d","e","f","g","h","i","j","k"),Question="Q1")

So far I’ve developed this

ggplot(Data, aes(x="Question", fill=Reviewed)) + geom_bar(position='fill' ) +
  coord_flip()

enter image description here

I would like to do the following:

  • Order the variables so they are arranged on plot as follows: Annually,Monthly,Weekly,Hourly,Other
  • Express the y axis in terms of percent. I.e. 0.25 turns into 25%
  • Move y-axis directly underneath the bar.
  • Remove the legend but move the terms underneath the respective part of the graph on a diagonal slant.
  • Add a black line that cuts down the 50% mark
  • Add a dot in at the midpoint of the stack for the value of company “e”.
  • Remove gray background

This is what I'm hoping the finished graph will look like. enter image description here


Solution

  • There's a lot to unpack here, so I'll break it down bit by bit:

    Order the variables so they are arranged on plot as follows: Annually,Monthly,Weekly,Hourly,Other

    Assign "Reviewed" as an ordered factor. I'm reversing the order here since it wants to plot the "lowest" factor first (to the left).

    Data$Reviewed <- factor(Data$Reviewed, 
                            levels = rev(c('Annually', 'Monthly', 'Weekly', 'Hourly', 'Other')),
                            ordered = T)
    
    ggplot(Data, aes(x="Question", fill=Reviewed)) + geom_bar(position='fill' ) +
      coord_flip()
    

    enter image description here

    Express the y axis in terms of percent. I.e. 0.25 turns into 25%

    Use scale_y_continuous(labels = scales::percent) to adjust the labels. I believe that the scales was pulled in when you installed ggplot2.

    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      scale_y_continuous(labels = scales::percent) +
      coord_flip()
    

    enter image description here

    Move y-axis directly underneath the bar. Remove gray background

    These are done all at once by adding expand = F to coord_flip.

    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(expand = F)
    

    enter image description here

    Remove the legend...

    Add theme(legend.position = 'none').

    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(expand = F) +
      theme(legend.position = 'none')
    

    enter image description here

    but move the terms underneath the respective part of the graph on a diagonal slant.

    This is tougher and takes a good amount of fiddling.

    1. Use geom_text to make the labels
    2. Calculate the position along the bar using the 'count' stat
    3. Move the labels to the bottom of the plot by providing a fake x coordinate
    4. Align the labels in the center of the bars using position_stack, and make them abut the x axis using hjust.
    5. Add angle.
    6. Use clip = 'off' in coord_flip to make sure that these values are not cut out since they're outside the plotting area.
    7. Fiddle with the x limits to crop out empty plotting area.
    8. Adjust the plot margin in theme to make sure everything can be seen.
    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      geom_text(aes(label = Reviewed, x = 0.45,
                    y = stat(..count../sum(..count..))), stat = 'count',
                position = position_stack(0.5),
                hjust = 0, 
                angle = 45) +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
      theme(plot.margin = margin(0, 0, 35, 10),
            legend.position = 'none')
    

    enter image description here

    Add a black line that cuts down the 50% mark

    Use geom_hline(yintercept = 0.5); remember that it's a "horizontal" line since the coordinates are flipped.

    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      geom_text(aes(label = Reviewed, x = 0.45,
                    y = stat(..count../sum(..count..))), stat = 'count',
                position = position_stack(0.5),
                hjust = 0, 
                angle = 45) +
      geom_hline(yintercept = 0.5) +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
      theme(plot.margin = margin(0, 0, 20, 10),
            legend.position = 'none')
    

    enter image description here

    Add a dot in at the midpoint of the stack for the value of company “e”.

    This is pretty hack-y. Using the same y values as in geom_text, use geom_point to plot a point for every value of Reviewed, then use position_stack(0.5) to nudge them to the center of the bar. Then use scale_color_manual to only color "Weekly" values (which is the corresponding value of Reviewed for Company "e"). I'm sure there's a way to do this more programmatically.

    ggplot(Data, aes(x="Question", fill=Reviewed)) +
      geom_bar(position = 'fill') +
      geom_text(aes(label = Reviewed, x = 0.45,
                    y = stat(..count../sum(..count..))), stat = 'count',
                position = position_stack(0.5),
                hjust = 0, 
                angle = 45) +
      geom_hline(yintercept = 0.5) +
      geom_point(aes(y = stat(..count../sum(..count..)),
                     color = Reviewed), stat = 'count',
                 position = position_stack(0.5), size = 5) +
      scale_color_manual(values = 'black', limits = 'Weekly') +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
      theme(plot.margin = margin(0, 0, 20, 10),
            legend.position = 'none')
    

    enter image description here

    This is what I'm hoping the finished graph will look like.

    Prettying things up:

    ggplot(Data, aes(x="Question", fill = Reviewed)) +
      geom_bar(position = 'fill') +
      geom_text(aes(label = Reviewed, x = 0.45,
                    y = stat(..count../sum(..count..))), stat = 'count',
                position = position_stack(0.5),
                hjust = 0, 
                angle = 45) +
      geom_hline(yintercept = 0.5) +
      geom_point(aes(y = stat(..count../sum(..count..)),
                     color = Reviewed), stat = 'count',
                 position = position_stack(0.5), size = 5) +
      scale_color_manual(values = 'black', limits = 'Weekly') +
      scale_y_continuous(labels = scales::percent) +
      coord_flip(xlim = c(0.555, 1.4), clip = 'off', expand = F) +
      labs(x = NULL, y = NULL) +
      theme_minimal() +
      theme(plot.margin = margin(0, 0, 35, 10),
            legend.position = 'none')
    

    enter image description here