R: Error when setting colors for multiple plots using ggplot2 and marrangeGrob

1k views Asked by At

I need to make a lot of plots of drug concentration over the course of time for various subjects in a study, and I'd like to consistently set the color based on what drug they've taken. Not all patients are taking the same drugs, though. Here is what I've tried:

library(plyr)
library(ggplot2)
library(gridExtra)

A <- data.frame(Time = seq(0, 20, 5),
            DrugConcentration = 100*exp(-0.25*seq(0, 20, 5)),
            Drug = "Midazolam")

B <- data.frame(Time = rep(seq(0, 20, 5), 2),
                DrugConcentration = c(100*exp(-0.25*seq(0, 20, 5)),
                                      75*exp(-0.1*seq(0, 20, 5))),
                Drug = rep(c("Midazolam", "Dextromethorphan"), each = 5))

C <- data.frame(Time = rep(seq(0, 20, 5), 3),
                DrugConcentration = c(100*exp(-0.25*seq(0, 20, 5)),
                                      75*exp(-0.1*seq(0, 20, 5)),
                                      50*exp(-0.15*seq(0, 20, 5))),
                Drug = rep(c("Midazolam", "Dextromethorphan", "Tolbutamide"), 
                           each = 5))

D <- data.frame(Time = rep(seq(0, 20, 5), 2),
                DrugConcentration = c(100*exp(-0.25*seq(0, 20, 5)),
                                      50*exp(-0.15*seq(0, 20, 5))),
                Drug = rep(c("Midazolam", "Tolbutamide"), 
                           each = 5))

DrugList <- list(A, B, C, D)

MyColors <- data.frame(Drug = c("Midazolam", "Dextromethorphan", "Tolbutamide"),
                       Color = c("red", "green", "blue"),
                       stringsAsFactors = FALSE)

PlotList <- list()

for(i in 1:length(DrugList)){
      DrugList[[i]] <- arrange(DrugList[[i]], Drug, Time)
      MyColors.temp <- join(DrugList[[i]][, c("Drug", "Time")], 
                        MyColors, by = "Drug")
      MyColors.temp <- unique(MyColors.temp[, c("Drug", "Color")])
      MyColors.temp <- arrange(MyColors.temp, Drug)

      PlotList[[i]] <- 
          ggplot(DrugList[[i]], 
               aes(x = Time, y = DrugConcentration, 
                   color = Drug)) +
          geom_point() + geom_line() +
          scale_color_manual(values = MyColors.temp$Color)
}

The loop runs, but when I try

marrangeGrob(PlotList, nrow = 2, ncol = 2)

I get the error: Insufficient values in manual scale. 3 needed but only 2 provided. If I look at each plot individually, e.g., by typing PlotList[[1]], the first two plots will at least generate plots (although the colors are not consistent for each drug and not what I specified), but the third is the one that gives me that error about not having enough values.

Here is the 1st plot, with the correct color for midazolam: concentration time curve midazolam

And here is the 2nd plot, which does not have the correct colors: concentration time curve midazolam dextromethorphan

What's going on here? Why isn't this working?

1

There are 1 answers

7
baptiste On BEST ANSWER

Why isn't this working?

plot 3 is the problem – not by itself, but in combination with the for loop and ggplot2's lazy evaluation mechanism. When you're defining each plot, ggplot2 captures correctly the environment for the i-th dataset, but the manual scale is stored as an unevaluated promise. When it's eventually evaluated (when ggplot_build is called shortly before drawing), MyColors.temp only has two values (the latest iteration of the loop), and plot 3 complains that the manual scale is provided with too few values.

Why is the colour scale inconsistent?

scale_colour_manual requires a named vector, not a pair of values–breaks (it certainly fooled me until I read the help page!).

There are a few possible solutions.

Passing the whole set of colours as named vector

MyColors <- c("Midazolam" = "red", "Dextromethorphan" = "green", "Tolbutamide" = "blue")

DrugList <- list(A, B, C, D)

PlotList <- list()

for(i in 1:length(DrugList)){

  PlotList[[i]] <- 
    ggplot(arrange(DrugList[[i]], Drug, Time), 
           aes(x = Time, y = DrugConcentration, 
               color = Drug)) +
    geom_point() + geom_line()  +
    scale_colour_manual(values = MyColors) +
    theme()
}

grid.arrange(grobs=PlotList)

enter image description here

Facetting

m <- reshape2::melt(DrugList, measure.vars = "DrugConcentration")
str(m)

p <- ggplot(m, aes(x = Time, y = value, color = Drug)) +
  facet_wrap(~L1) +
  geom_point() + geom_line() +
  scale_colour_manual(values = MyColors)

enter image description here

ggforce appears to provide a facetting function that can extend over multiple pages. It's also pretty easy to achieve with an extra dummy variable to split the entire dataset into groups to plot as individual 2x2 facets.

Split-apply

If you need separate plots, it's also easier to work with the long format data.frame,

lp <- plyr::dlply(m, "L1", function(d) p %+% d)

grid.arrange(grobs = lp)

enter image description here