rggplot2patchwork

How to make combined legend for ggplots using either patchwork() or ggarrange() etc


I am struggling to make a legend that I want for my graph. Here is what the graph currently looks like (it's not a final version, I know it's not super pretty) enter image description here

I am frustrated by the fact that I can't seem to combine the legends into one using patchwork or ggarrange packages. I am used to using scale_fcn_manual() and making the aesthetic calls identical, then calling for "collected" legends in patchwork(). However, because the data within each plot is actually different, it is still making them into 3 separate plot legends, despite the fact that I have all possible treatment options in my code.

Does anyone know how to either create an entirely new legend from scratch or fix my code? I have tried using a common legend using ggarrange() as well, but got a similar result. Below, I will include my ggplot code and also what I would LIKE the figure to look like, manually edited on Paint. Thanks! enter image description here

plot_size_p = ggplot (filter(snail_size, food %in% c("C","J","K","L")), aes(x = week, y = mean_size, color = food, shape=infected, linetype=infected)) +
  labs(color = "Resource Type",
       linetype = "Parasite Status",
       shape = "Parasite Status")+
  geom_linerange(aes(ymin=mean_size-SEM_size, ymax=mean_size+SEM_size, color=food)) +
  geom_point (aes(color = food, shape=infected)) +
  geom_line (aes(color = food, linetype=infected))+
  scale_color_manual(breaks = c("F","I","H","G","L","K","J","O","N","M","C"),labels = c("100% Pleco Wafers","High N","Med N","Low N","High P","Med P","Low P","High (N+P)","Med (N+P)","Low (N+P)", "Baseline"),values = c("green4", brewer.pal(9, "Reds")[7],brewer.pal(9, "Reds")[6],brewer.pal(9, "Reds")[5],brewer.pal(9, "Blues")[7],brewer.pal(9, "Blues")[6],brewer.pal(9, "Blues")[5],brewer.pal(9, "Purples")[7],brewer.pal(9, "Purples")[6],brewer.pal(9, "Purples")[5], "grey"))+
  scale_linetype_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(1,2))+
  scale_shape_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(19,17))

plot_size_n = ggplot (filter(snail_size, food %in% c("C","G","H","I")), aes(x = week, y = mean_size, color = food, shape=infected, linetype=infected)) +
  labs(color = "Resource Type",
       linetype = "Parasite Status",
       shape = "Parasite Status")+
  geom_linerange(aes(ymin=mean_size-SEM_size, ymax=mean_size+SEM_size, color=food)) +
  geom_point (aes(color = food, shape=infected)) +
  geom_line (aes(color = food, linetype=infected))+
  scale_color_manual(breaks = c("F","I","H","G","L","K","J","O","N","M","C"),labels = c("100% Pleco Wafers","High N","Med N","Low N","High P","Med P","Low P","High (N+P)","Med (N+P)","Low (N+P)", "Baseline"),values = c("green4", brewer.pal(9, "Reds")[7],brewer.pal(9, "Reds")[6],brewer.pal(9, "Reds")[5],brewer.pal(9, "Blues")[7],brewer.pal(9, "Blues")[6],brewer.pal(9, "Blues")[5],brewer.pal(9, "Purples")[7],brewer.pal(9, "Purples")[6],brewer.pal(9, "Purples")[5], "grey"))+
  scale_linetype_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(1,2))+
  scale_shape_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(19,17))

plot_size_np = ggplot (filter(snail_size, food %in% c("C","M","N","O")), aes(x = week, y = mean_size, color = food, shape=infected, linetype=infected)) +
  labs(color = "Resource Type",
       linetype = "Parasite Status",
       shape = "Parasite Status")+
  geom_point (aes(color = food, shape=infected)) +
  geom_line (aes(color = food, linetype=infected))+
  scale_color_manual(breaks = c("F","I","H","G","L","K","J","O","N","M","C"),labels = c("100% Pleco Wafers","High N","Med N","Low N","High P","Med P","Low P","High (N+P)","Med (N+P)","Low (N+P)", "Baseline"),values = c("green4", brewer.pal(9, "Reds")[7],brewer.pal(9, "Reds")[6],brewer.pal(9, "Reds")[5],brewer.pal(9, "Blues")[7],brewer.pal(9, "Blues")[6],brewer.pal(9, "Blues")[5],brewer.pal(9, "Purples")[7],brewer.pal(9, "Purples")[6], brewer.pal(9, "Purples")[5], "grey"))+
  scale_linetype_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(1,2))+
  scale_shape_manual(breaks=c(0,1), labels=c("Uninfected","Infected"), values=c(19,17))

size_plots = wrap_plots(plot_size_p,plot_size_n,plot_size_np, nrow = 1, ncol = 3)+ 
  plot_layout(guides = "collect")
size_plots

Solution

  • In many cases this is simplest to do with facets. For instance, here I facet by gear, but color by gear+am. That puts two colors into the middle facet:

    mtcars |>
      mutate(Resource = paste(gear, am)) |>
      ggplot(aes(wt, mpg, color = Resource)) +
      geom_point(size = 4) +
      scale_color_manual(breaks = c("3 0", "4 0", "4 1", "5 1"),
                         labels = c("Moose", "Bunny", "Rabbit", "Flour"),
                         values = c("green4", RColorBrewer::brewer.pal(9, "Reds")[7], 
                                    RColorBrewer::brewer.pal(9, "Reds")[5], "blue")) +
      facet_wrap(~gear)
    

    plot using facets, with two categories in middle facet

    As @stefan noted in the comments, if we use separate plots with patchwork, we will not see a legend for categories not represented in the data (using ggplot2 >= 3.5.0), unless we add show.legend = TRUE to the geom in question. Compare the three plots below, the first of which omits that. Its guide doesn't match the other guide so it doesn't get combined:

    enter image description here

    If we add it, the guides can be combined:

    enter image description here

    my_col_scale <- scale_color_manual(limits =  c("3 0", "4 0", "4 1", "5 1"),
                                      name = "Resource",
                                       labels = c("Moose", "Bunny", "Rabbit", "Flour"),
                                       values = c("green4", RColorBrewer::brewer.pal(9, "Reds")[7], 
                                                  RColorBrewer::brewer.pal(9, "Reds")[5], "blue")) 
    
    mtcars |>
      mutate(Resource = paste(gear, am)) -> mtcars2
      
    
    library(patchwork)
    
    wrap_plots(
      ggplot(mtcars2 |> filter(gear == 3), aes(wt, mpg, color = Resource)) +
        geom_point(size = 4) +   # note missing `show.legend = TRUE`
      my_col_scale2,
      
      ggplot(mtcars2 |> filter(gear == 4), aes(wt, mpg, color = Resource)) +
      geom_point(size = 4, show.legend = TRUE) + 
      my_col_scale2,
      
      ggplot(mtcars2 |> filter(gear == 5), aes(wt, mpg, color = Resource)) +
        geom_point(size = 4, show.legend = TRUE) + 
        my_col_scale2
    ) +
      plot_layout(guides = "collect")