Come posso disporre un numero arbitrario di ggplots utilizzando grid.arrange?


93

Questo è cross-postato sul gruppo google ggplot2

La mia situazione è che sto lavorando su una funzione che restituisce un numero arbitrario di grafici (a seconda dei dati di input forniti dall'utente). La funzione restituisce un elenco di n grafici e mi piacerebbe disporre questi grafici in una formazione 2 x 2. Sto lottando con i problemi simultanei di:

  1. Come posso consentire alla flessibilità di ricevere un numero arbitrario (n) di lotti?
  2. Come posso anche specificare che li voglio disposti 2 x 2

La mia strategia attuale utilizza grid.arrangedal gridExtrapacchetto. Probabilmente non è ottimale, soprattutto perché, e questa è la chiave, non funziona completamente . Ecco il mio codice di esempio commentato, sperimentando tre grafici:

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)

Come sono abituato a fare, mi rannicchio umilmente in un angolo, aspettando con impazienza il feedback sagace di una comunità molto più saggia di me. Soprattutto se sto rendendo tutto più difficile del necessario.


2
Complimenti per una domanda MOLTO ben fatta. Lo userò come esempio di come scrivere una buona domanda SO [r].
JD Long

1
soprattutto la parte "umilmente rannicchiata" - niente come un buon umile :-)
Ben Bolker

@JD e @Ben - Sono lusingato, ragazzi. Cordiali saluti. E apprezzo davvero l'aiuto.
briandk

Risposte:


45

Ci sei quasi! Il problema è che si do.callaspetta che i tuoi argomenti siano in un listoggetto con nome . Li hai inseriti nell'elenco, ma come stringhe di caratteri, non come voci di elenco con nome.

Penso che dovrebbe funzionare:

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

come Ben e Joshua hanno sottolineato nei commenti, avrei potuto assegnare dei nomi quando ho creato l'elenco:

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

o

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)

1
Ho cambiato il codice un paio di volte. Ci scusiamo per le modifiche. ha senso adesso? Quando ho detto prima che erano un vettore, ho detto male. Mi dispiace per quello.
JD Long

2
Puoi nominare gli argomenti durante la creazione dell'elenco:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich

2
Non esattamente. Il tuo è della giusta lunghezza. La struttura della tua lista è diversa dalla struttura della lista di JD. Usa str () e nomi (). Tutti gli elementi della tua lista sono senza nome, quindi, per do.callavere successo, sarebbe stata necessaria una corrispondenza posizionale esatta.
IRTFM

2
@JD Long; Sono assolutamente d'accordo. E anche se non impedisce tutti gli errori, si ottengono comunque messaggi e traceback()informazioni di errore molto migliori se si utilizzano argomenti denominati.
IRTFM

1
Non seguo abbastanza la discussione qui; poiché il primo argomento per grid.arrange()è la ...corrispondenza posizionale è probabilmente irrilevante. Ogni input deve essere un oggetto griglia (con o senza nome), un parametro denominato per grid.layouto un parametro denominato per gli argomenti rimanenti.
baptiste

16

Prova questo,

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)

3
version> = 0.9 di gridExtra fornisce marrangeGrob per fare tutto questo automaticamente ogni volta che nrow * ncol <length (trame)
baptiste

5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste

4

Sto rispondendo un po 'in ritardo, ma sono incappato in una soluzione in R Graphics Cookbook che fa qualcosa di molto simile usando una funzione personalizzata chiamata multiplot. Forse aiuterà gli altri che trovano questa domanda. Aggiungo anche la risposta in quanto la soluzione potrebbe essere più recente delle altre risposte a questa domanda.

Grafici multipli su una pagina (ggplot2)

Ecco la funzione corrente, anche se si prega di utilizzare il collegamento sopra, poiché l'autore ha notato che è stato aggiornato per ggplot2 0.9.3, il che indica che potrebbe cambiare di nuovo.

# 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 oggetti della trama:

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

E poi li passa a multiplot:

multiplot(p1, p2, ..., cols = n)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.