¿Cómo puedo organizar un número arbitrario de ggplots usando grid.arrange?

93

Esto es una publicación cruzada en el grupo de google ggplot2

Mi situación es que estoy trabajando en una función que genera un número arbitrario de gráficos (dependiendo de los datos de entrada proporcionados por el usuario). La función devuelve una lista de n gráficos, y me gustaría colocar esos gráficos en una formación de 2 x 2. Estoy luchando con los problemas simultáneos de:

  1. ¿Cómo puedo permitir que la flexibilidad se entregue un número arbitrario (n) de parcelas?
  2. ¿Cómo puedo especificar también que los quiero distribuidos 2 x 2?

Mi estrategia actual utiliza grid.arrangedel gridExtrapaquete. Probablemente no sea óptimo, especialmente porque, y esto es clave, no funciona en absoluto . Aquí está mi código de muestra comentado, experimentando con tres 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 suelo hacer, me acurruco humildemente en un rincón, esperando ansiosamente los comentarios sagaces de una comunidad mucho más sabia que yo, especialmente si estoy haciendo esto más difícil de lo necesario.

briandk
fuente
2
Felicitaciones por una pregunta MUY bien hecha. Voy a usar esto como un ejemplo de cómo escribir una buena pregunta SO [r].
JD Long
1
especialmente la parte de "acurrucarse humildemente" - nada como una buena humillación :-)
Ben Bolker
@JD y @Ben: me siento halagado, chicos. Sinceramente. Y realmente agradezco la ayuda.
briandk

Respuestas:

45

¡Ya casi estás ahí! El problema es que do.callespera que sus argumentos estén en un listobjeto con nombre . Los ha puesto en la lista, pero como cadenas de caracteres, no como elementos de lista con nombre.

Creo que esto debería funcionar:

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

como señalaron Ben y Joshua en los comentarios, podría haber asignado nombres cuando creé la lista:

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

o

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
fuente
1
Cambié el código un par de veces. Perdón por las ediciones. tiene sentido ahora? Cuando dije que eran un vector antes, me equivoqué. Lo siento por eso.
JD Long
2
Puede nombrar los argumentos durante la creación de la lista:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
No exactamente. El suyo es de la longitud adecuada. La estructura de su lista es diferente a la estructura de la lista de JD. Utilice str () y names (). Todos los elementos de su lista no tienen nombre, por lo que do.callpara tener éxito, habría sido necesario una coincidencia posicional exacta.
IRTFM
2
@JD Long; Estoy totalmente de acuerdo. Y aunque no evite todos los errores, obtendrá mensajes de error e traceback()información mucho mejores si utiliza argumentos con nombre.
IRTFM
1
No sigo la discusión aquí; ya que el primer argumento grid.arrange()es ...concordancia posicional es probablemente irrelevante. Cada entrada debe ser un objeto de cuadrícula (con o sin nombre), un parámetro con nombre grid.layouto un parámetro con nombre para los argumentos restantes.
baptiste
16

Prueba esto,

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)
baptiste
fuente
3
version> = 0.9 de gridExtra proporciona marrangeGrob para hacer todo esto automáticamente siempre que nrow * ncol <length (plots)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste
4

Estoy respondiendo un poco tarde, pero encontré una solución en R Graphics Cookbook que hace algo muy similar usando una función personalizada llamada multiplot. Quizás ayude a otros que encuentren esta pregunta. También estoy agregando la respuesta, ya que la solución puede ser más nueva que las otras respuestas a esta pregunta.

Múltiples gráficos en una página (ggplot2)

Aquí está la función actual, aunque use el enlace anterior, ya que el autor señaló que se ha actualizado para ggplot2 0.9.3, lo que indica que puede cambiar nuevamente.

# 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))
    }
  }
}

Uno crea objetos de la trama:

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

Y luego los pasa a multiplot:

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