Como posso organizar um número arbitrário de ggplots usando grid.arrange?

93

Esta é uma postagem cruzada no grupo ggplot2 google

Minha situação é que estou trabalhando em uma função que produz um número arbitrário de gráficos (dependendo dos dados de entrada fornecidos pelo usuário). A função retorna uma lista de n gráficos, e eu gostaria de colocá-los em uma formação 2 x 2. Estou lutando com os problemas simultâneos de:

  1. Como posso permitir que a flexibilidade seja entregue a um número arbitrário (n) de parcelas?
  2. Como posso também especificar que quero eles dispostos 2 x 2

Minha estratégia atual usa grid.arrangedo gridExtrapacote. Provavelmente não é o ideal, especialmente porque, e essa é a chave, não funciona totalmente . Aqui está meu código de amostra comentado, experimentando três gráficos:

library(ggplot2)
library(gridExtra)

x <- qplot(mpg, disp, data = mtcars)
y <- qplot(hp, wt, data = mtcars)
z <- qplot(qsec, wt, data = mtcars)

# A normal, plain-jane call to grid.arrange is fine for displaying all my plots
grid.arrange(x, y, z)

# But, for my purposes, I need a 2 x 2 layout. So the command below works acceptably.
grid.arrange(x, y, z, nrow = 2, ncol = 2)

# The problem is that the function I'm developing outputs a LIST of an arbitrary
# number plots, and I'd like to be able to plot every plot in the list on a 2 x 2
# laid-out page. I can at least plot a list of plots by constructing a do.call()
# expression, below. (Note: it totally even surprises me that this do.call expression
# DOES work. I'm astounded.)
plot.list <- list(x, y, z)
do.call(grid.arrange, plot.list)

# But now I need 2 x 2 pages. No problem, right? Since do.call() is taking a list of
# arguments, I'll just add my grid.layout arguments to the list. Since grid.arrange is
# supposed to pass layout arguments along to grid.layout anyway, this should work.
args.list <- c(plot.list, "nrow = 2", "ncol = 2")

# Except that the line below is going to fail, producing an "input must be grobs!"
# error
do.call(grid.arrange, args.list)

Como estou acostumado a fazer, humildemente me encolho no canto, aguardando ansiosamente o feedback sagaz de uma comunidade muito mais sábia do que eu. Especialmente se estou tornando isso mais difícil do que o necessário.

Briandk
fonte
2
Parabéns por uma pergunta MUITO bem feita. Vou usar isso como um exemplo de como escrever uma boa pergunta SO [r].
JD Long
1
especialmente a parte "humildemente amontoada" - nada como um bom rastejar :-)
Ben Bolker
@JD e @Ben - Estou lisonjeado, pessoal. Atenciosamente. E eu realmente agradeço a ajuda.
briandk

Respostas:

45

Você está quase lá! O problema é que do.callespera que seu args esteja em um listobjeto nomeado . Você os colocou na lista, mas como cadeias de caracteres, não como itens nomeados da lista.

Eu acho que isso deve funcionar:

args.list <- c(plot.list, 2,2)
names(args.list) <- c("x", "y", "z", "nrow", "ncol")

como Ben e Joshua apontaram nos comentários, eu poderia ter atribuído nomes quando criei a lista:

args.list <- c(plot.list,list(nrow=2,ncol=2))

ou

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
fonte
1
Eu mudei o código algumas vezes. Desculpe pelas edições. Faz sentido agora? Quando eu disse que eles eram um vetor antes, falei mal. Me desculpe por isso.
JD Long
2
Você pode nomear os argumentos durante a criação da lista:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
Não exatamente. O seu tem o comprimento adequado. A estrutura da sua lista é diferente da estrutura da lista do JD. Use str () e nomes (). Todos os elementos da sua lista não têm nome, portanto, para que o tenha do.callsucesso, seria necessário haver correspondência posicional exata.
IRTFM
2
@JD Long; Eu concordo sinceramente. E mesmo que isso não evite todos os erros, você ainda obterá mensagens de erro e traceback()informações muito melhores se usar argumentos nomeados.
IRTFM
1
Não acompanho bem a discussão aqui; uma vez que o primeiro argumento para grid.arrange()é a ...correspondência posicional, é provavelmente irrelevante. Cada entrada deve ser um objeto de grade (com ou sem nome), um parâmetro nomeado para grid.layoutou um parâmetro nomeado para os argumentos restantes.
baptiste
16

Tente isto,

require(ggplot2)
require(gridExtra)
plots <- lapply(1:11, function(.x) qplot(1:10,rnorm(10), main=paste("plot",.x)))

params <- list(nrow=2, ncol=2)

n <- with(params, nrow*ncol)
## add one page if division is not complete
pages <- length(plots) %/% n + as.logical(length(plots) %% n)

groups <- split(seq_along(plots), 
  gl(pages, n, length(plots)))

pl <-
  lapply(names(groups), function(g)
         {
           do.call(arrangeGrob, c(plots[groups[[g]]], params, 
                                  list(main=paste("page", g, "of", pages))))
         })

class(pl) <- c("arrangelist", "ggplot", class(pl))
print.arrangelist = function(x, ...) lapply(x, function(.x) {
  if(dev.interactive()) dev.new() else grid.newpage()
   grid.draw(.x)
   }, ...)

## interactive use; open new devices
pl

## non-interactive use, multipage pdf
ggsave("multipage.pdf", pl)
batista
fonte
3
version> = 0.9 of gridExtra fornece marrangeGrob para fazer tudo isso automaticamente sempre que nrow * ncol <length (plotagens)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste
4

Estou respondendo um pouco tarde, mas tropecei em uma solução no R Graphics Cookbook que faz algo muito semelhante usando uma função personalizada chamada multiplot. Talvez ajude outros que encontrarem esta questão. Também estou adicionando a resposta, pois a solução pode ser mais recente do que as outras respostas a esta pergunta.

Vários gráficos em uma página (ggplot2)

Aqui está a função atual, embora use o link acima, pois o autor observou que ela foi atualizada para ggplot2 0.9.3, o que indica que pode mudar novamente.

# Multiple plot function
#
# ggplot objects can be passed in ..., or to plotlist (as a list of ggplot objects)
# - cols:   Number of columns in layout
# - layout: A matrix specifying the layout. If present, 'cols' is ignored.
#
# If the layout is something like matrix(c(1,2,3,3), nrow=2, byrow=TRUE),
# then plot 1 will go in the upper left, 2 will go in the upper right, and
# 3 will go all the way across the bottom.
#
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
  require(grid)

  # Make a list from the ... arguments and plotlist
  plots <- c(list(...), plotlist)

  numPlots = length(plots)

  # If layout is NULL, then use 'cols' to determine layout
  if (is.null(layout)) {
    # Make the panel
    # ncol: Number of columns of plots
    # nrow: Number of rows needed, calculated from # of cols
    layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
                    ncol = cols, nrow = ceiling(numPlots/cols))
  }

 if (numPlots==1) {
    print(plots[[1]])

  } else {
    # Set up the page
    grid.newpage()
    pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))

    # Make each plot, in the correct location
    for (i in 1:numPlots) {
      # Get the i,j matrix positions of the regions that contain this subplot
      matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))

      print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
                                      layout.pos.col = matchidx$col))
    }
  }
}

Um cria objetos de enredo:

p1 <- ggplot(...)
p2 <- ggplot(...)
# etc.

E então os passa para multiplot:

multiplot(p1, p2, ..., cols = n)
Hendy
fonte