Wie kann ich mit grid.arrange eine beliebige Anzahl von ggplots anordnen?

93

Dies wird in der Google-Gruppe ggplot2 veröffentlicht

Meine Situation ist, dass ich an einer Funktion arbeite , die eine beliebige Anzahl von Plots ausgibt (abhängig von den vom Benutzer bereitgestellten Eingabedaten). Die Funktion gibt eine Liste von n Plots zurück, und ich möchte diese Plots in 2 x 2-Formation anordnen. Ich kämpfe mit den gleichzeitigen Problemen von:

  1. Wie kann ich zulassen, dass der Flexibilität eine beliebige (n) Anzahl von Parzellen übergeben wird?
  2. Wie kann ich auch angeben, dass sie 2 x 2 angelegt werden sollen?

Meine aktuelle Strategie verwendet grid.arrangeaus dem gridExtraPaket. Es ist wahrscheinlich nicht optimal, zumal, und das ist der Schlüssel, es funktioniert überhaupt nicht . Hier ist mein kommentierter Beispielcode, der mit drei Plots experimentiert:

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)

Wie ich es gewohnt bin, dränge ich mich demütig in die Ecke und warte gespannt auf das kluge Feedback einer Community, die viel weiser als ich ist. Vor allem, wenn ich das schwieriger mache, als es sein muss.

briandk
quelle
2
Ein großes Lob für eine sehr gut gemachte Frage. Ich werde dies als Beispiel dafür verwenden, wie man eine gute SO [r] -Frage schreibt.
JD Long
1
vor allem der "demütig huddling" Teil - nichts wie ein guter Grovel :-)
Ben Bolker
@JD und @Ben - Ich bin geschmeichelt, Leute. Mit freundlichen Grüßen. Und ich schätze die Hilfe sehr.
Briandk

Antworten:

45

Du bist fast da! Das Problem ist, dass do.callerwartet wird, dass sich Ihre Argumente in einem benannten listObjekt befinden. Sie haben sie in die Liste aufgenommen, aber als Zeichenfolgen, nicht als benannte Listenelemente.

Ich denke das sollte funktionieren:

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

Wie Ben und Joshua in den Kommentaren betonten, hätte ich bei der Erstellung der Liste Namen vergeben können:

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

oder

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
quelle
1
Ich habe den Code ein paar Mal geändert. Entschuldigung für die Änderungen. macht es jetzt Sinn? Als ich früher sagte, sie seien ein Vektor, habe ich falsch geschrieben. Das tut mir leid.
JD Long
2
Sie können die Argumente während der Listenerstellung benennen:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
Nicht genau. Ihre ist von der richtigen Länge. Die Struktur Ihrer Liste unterscheidet sich von der Struktur der JD-Liste. Verwenden Sie str () und names (). Alle Ihre Listenelemente sind unbenannt. Um do.callerfolgreich zu sein, wäre eine genaue Positionsübereinstimmung erforderlich gewesen.
IRTFM
2
@ JD Long; Ich stimme von Herzen zu. Und wenn es nicht alle Fehler verhindert, erhalten Sie immer noch viel bessere Fehlermeldungen und traceback()Informationen, wenn Sie benannte Argumente verwenden.
IRTFM
1
Ich verfolge die Diskussion hier nicht ganz. da das erste Argument für grid.arrange()die ...Positionsanpassung wahrscheinlich irrelevant ist. Jede Eingabe muss entweder ein Rasterobjekt (mit oder ohne Namen), ein benannter Parameter für grid.layoutoder ein benannter Parameter für die verbleibenden Argumente sein.
Taufe
16

Versuche dies,

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
quelle
3
version> = 0.9 von gridExtra bietet marrangeGrob, um all dies automatisch zu tun, wenn nrow * ncol <length (plot)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
Taufe
4

Ich antworte etwas spät, bin aber auf eine Lösung im R Graphics Cookbook gestoßen, die mit einer benutzerdefinierten Funktion namens etwas sehr Ähnliches bewirkt multiplot. Vielleicht hilft es anderen, die diese Frage finden. Ich füge auch die Antwort hinzu, da die Lösung möglicherweise neuer ist als die anderen Antworten auf diese Frage.

Mehrere Grafiken auf einer Seite (ggplot2)

Hier ist die aktuelle Funktion, verwenden Sie jedoch bitte den obigen Link, da der Autor festgestellt hat, dass sie für ggplot2 0.9.3 aktualisiert wurde, was darauf hinweist, dass sie sich möglicherweise erneut ändert.

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

Man erstellt Handlungsobjekte:

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

Und gibt sie dann an multiplot:

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