3

I have a list of plots and am using marrangeGrob to write multiple plots per page. This gives me 6 plots per page:

marrangeGrob(plots, nrow=3, ncol=2, top=NULL)

But what I need is 5 plots per page (i.e. bottom right plot area is left blank)

Here is a reproducible example based on a subset of my data:

> CohData
     Name ScenID ScenName Year VarName Units value
1  Site01      0     Hind 2000     ANC ueq/L 28.23
2  Site02      0     Hind 2000     ANC ueq/L 35.34
3  Site03      0     Hind 2000     ANC ueq/L 31.73
4  Site04      0     Hind 2000     ANC ueq/L 54.21
5  Site05      0     Hind 2000     ANC ueq/L 28.23
6  Site01      1       BC 2020     ANC ueq/L 45.01
7  Site02      1       BC 2020     ANC ueq/L 32.86
8  Site03      1       BC 2020     ANC ueq/L 30.38
9  Site04      1       BC 2020     ANC ueq/L 53.01
10 Site05      1       BC 2020     ANC ueq/L 31.11
11 Site01      0     Hind 2000      pH        6.40
12 Site02      0     Hind 2000      pH        5.93
13 Site03      0     Hind 2000      pH        6.36
14 Site04      0     Hind 2000      pH        5.75
15 Site05      0     Hind 2000      pH        5.16
16 Site01      1       BC 2020      pH        6.63
17 Site02      1       BC 2020      pH        5.86
18 Site03      1       BC 2020      pH        6.33
19 Site04      1       BC 2020      pH        5.73
20 Site05      1       BC 2020      pH        5.23

library(ggplot2)
library(grid)
library(gridExtra)

## Iterate plots and write to PDF
pdf("plots.pdf")
plots <- list()
CohData_nohind <- subset(CohData, ScenID > 0)
stream_vars <- list("ANC", "pH")
for (i in unique(CohData_nohind$ScenID)){
  subdata1 <- subset(CohData_nohind, ScenID == i)
  ScenName <- unique(subdata1$ScenName)
  subdata1 <- subset(CohData, ScenID == i | ScenID == 0)
  for (j in stream_vars){
    subdata2 <- subset(subdata1, VarName == j)
    units <- unique(subdata2$Units)
    for (k in unique(subdata2$Name)){
      subdata3 <- subset(subdata2, Name == k)

      if (j=='ANC'){
        p <- ggplot(subdata3, aes(x = Year, y = value)) + geom_line() + expand_limits(y=c(0,100)) + 
          ggtitle(paste0(as.character(ScenName),":\n", as.character(k))) + ylab(paste0(j, " (", units, ")"))
        plots[[paste(i,j,k,sep="_")]] = p

      } else if (j=='pH'){
        p <- ggplot(subdata3, aes(x = Year, y = value)) + geom_line() + expand_limits(y=c(3,7)) +
          ggtitle(paste0(as.character(ScenName),":\n", as.character(k))) + ylab(paste0(j, " (", units, ")"))
        plots[[paste(i,j,k,sep="_")]] = p
      }
    }
  }
}
interleave <- function(x, y, after = 1){

  nx <- length(x)
  ny <- length(y)

  if((nx %% after) > 0) x <- rep(x, length.out=after * ceiling(nx / after))
  mx <- matrix(x, ncol = after, byrow=TRUE)
  my <- rep(y, length.out = nrow(mx))

  c(t(cbind(mx, my)))
}

x <- replicate(7, ggplot(), simplify=FALSE)
y <- replicate(3, nullGrob(), simplify=FALSE)

marrangeGrob(interleave(x,y,3), ncol=2, nrow=2)
dev.off()

How can I adjust this code for writing each of the 5 "Names" to a single page for each "VarName"?

viridius
  • 477
  • 5
  • 17

3 Answers3

6

I've added the option to pass a layout, let me know if it breaks,

# devtools::install_github("baptiste/gridextra")
library(gridExtra)
library(grid)

gl <- lapply(seq_len(7), function(ii) grobTree(rectGrob(gp=gpar(fill=ii)), 
                                               textGrob(ii)))
marrangeGrob(gl, layout_matrix=matrix(c(1,2,3,NA), 2))

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • I included this function after all of the for loops and received the error `Error in gtable_add_grob(gt, grobs, t = positions$t, b = positions$b, : Not all inputs have either length 1 or same length same as 'grobs'`, is there something I need to modify to fit my code (other than changing from `gl` to `plots` in the last line)? – viridius May 25 '16 at 15:37
  • i'm guessing your list of plots has a length that's not compatible with the layout per page x number of pages (7 grobs break the above example). I'd need to rethink the strategy a little bit, in the meantime DWin's idea is a good workaround. – baptiste May 25 '16 at 20:11
  • I did not see anything from DWin. The suggestion below from 42- works for this example, but not for the more than 200 plots in the full list. I will have several hundred plots for other lists of `VarName`s. Thanks for rethinking this. – viridius May 25 '16 at 20:38
  • @viridius : baptiste has a good memory. I used to use the SO-moniker DWin. – IRTFM May 25 '16 at 23:19
3

Insert a blank grob in the 6th and 12th positions:

plots[7:11] <- plots[6:10]
plots[[6]] <- textGrob("")
plots[[12]] <- textGrob("")

 pdf("plots.pdf")
 marrangeGrob(plots, nrow=3, ncol=2, top=NULL)
 dev.off()

enter image description here

IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • 1
    `nullGrob()` is probably a better option than `textGrob("")` – baptiste May 25 '16 at 06:21
  • 2
    I bow in the direction of the GrobMaster. – IRTFM May 25 '16 at 06:53
  • Thanks for this. Although it works for the test dataset, my full dataset includes a list of 210 plots, and this number will change as I plot different sets of `VarNames` for the same set of `Names` – viridius May 25 '16 at 15:38
  • I should note that I have effectively used `layout(matrix(c(1,2,3,4,5,0), 3, 2, byrow = TRUE))` with the standard `plot` function in the past. I am essentially trying to replicate this with `ggplot` as I transition to using `ggplot2` (which I just started using yesterday) – viridius May 25 '16 at 15:44
  • I tried using the 'layout.matrix' argument to `arrangeGrob`, but failed, perhaps because I was using NA instead of 0 for the blank position? – IRTFM May 25 '16 at 15:48
  • I can try this using 0 in place of NA for the blank position. How did you have the remainder specified? – viridius May 25 '16 at 16:23
  • I'm not sure it matters. I tried again with 0 in place of NA and again failed. – IRTFM May 25 '16 at 16:47
  • blank space is specified by NA in the layout_matrix of arrangeGrob, but the wrapper marrangeGrob currently creates its own layout based on nrow and ncol, and fills each page until the last. – baptiste May 25 '16 at 22:29
  • @viridius automating the process for any number of plots shouldn't be too difficult: you want to interleave a list of N `nullGrob()` elements at specified positions in a list of M plots. Searching for "interleave" should get you there. – baptiste May 25 '16 at 22:31
  • Well, that explains my failure. I had passed the matrix layout to arrangeGrob and then given that result to marrangeGrob, which apparently totally ignored my sensible efforts. – IRTFM May 25 '16 at 22:32
  • actually, the issue seems to be with the last page: when given a layout_matrix, arrangeGrob expects as many grobs as there are unique ids in non-NA cells. I'll try to fix this later – baptiste May 25 '16 at 22:47
  • @basptiste I found [this](http://stackoverflow.com/questions/13863599/insert-a-character-at-a-specific-location-in-a-string) and [this](http://stackoverflow.com/questions/16443260/interleave-lists-in-r/16443987#16443987) on interleave. It seems that the "interleave" function is custom built/defined within a given script, which also seems like something that is beyond my current capabilities. – viridius May 26 '16 at 16:01
  • Does anyone have a more specific suggestion for how to interleave a list of N `nullGrob()` elements suggested by @baptiste? – viridius Jun 14 '16 at 15:01
  • I thought your question was already answered. My version worked but baptiste made a minor improvement. I don't think you made any edits that demonstrate any remaining problems. Correct me if that is not the case. – IRTFM Jun 14 '16 at 23:18
0

This version is slightly more general

interleave <- function(x, y, after = 1){

  nx <- length(x)
  ny <- length(y)

  if((nx %% after) > 0) x <- rep(x, length.out=after * ceiling(nx / after))
  mx <- matrix(x, ncol = after, byrow=TRUE)
  my <- rep(y, length.out = nrow(mx))

  c(t(cbind(mx, my)))
}

library(ggplot2)
library(grid)
library(gridExtra)
x <- replicate(7, ggplot(), simplify=FALSE)
y <- replicate(3, nullGrob(), simplify=FALSE)

marrangeGrob(interleave(x,y,3), ncol=2, nrow=2)
baptiste
  • 75,767
  • 19
  • 198
  • 294
  • I included this function after the for loops (as shown in the edited original post), however, this did not produce the desired result. Should the function be placed elsewhere or other edits made? – viridius Jun 17 '16 at 19:04