Jak mogę ustawić dowolną liczbę ggplots za pomocą grid.arrange?

93

To jest opublikowane w grupie google ggplot2

Moja sytuacja jest taka, że pracuję nad funkcją, która generuje dowolną liczbę wykresów (w zależności od danych wejściowych dostarczonych przez użytkownika). Funkcja zwraca listę n wykresów i chciałbym ułożyć te wykresy w układzie 2 x 2. Borykam się z jednoczesnymi problemami:

  1. Jak mogę pozwolić na przydzielenie elastyczności dowolnej (n) liczbie działek?
  2. Jak mogę również określić, czy chcę je rozłożyć na 2 x 2

Moja obecna strategia korzysta grid.arrangez gridExtrapakietu. Prawdopodobnie nie jest optymalny, zwłaszcza, że ​​i to jest kluczowe, całkowicie nie działa . Oto mój skomentowany przykładowy kod, eksperymentujący z trzema wykresami:

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)

Jak to zwykle robię, pokornie kulę się w kącie, niecierpliwie czekając na mądrą opinię społeczności znacznie mądrzejszej ode mnie. Zwłaszcza jeśli utrudniam to, niż powinno.

briandk
źródło
2
Uznanie za BARDZO dobrze zrobione pytanie. Mam zamiar użyć tego jako przykładu, jak napisać dobre pytanie SO [r].
JD Long
1
zwłaszcza część „skromnie przytulona” - nie ma to jak dobry płacz :-)
Ben Bolker
@JD i @Ben - pochlebia mi, chłopaki. Z poważaniem. Naprawdę doceniam pomoc.
briandk

Odpowiedzi:

45

Jesteś prawie na miejscu! Problem polega na tym, że do.calloczekuje , że twoje argumenty będą w nazwanym listobiekcie. Umieściłeś je na liście, ale jako ciągi znaków, a nie nazwane elementy listy.

Myślę, że to powinno działać:

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

jak Ben i Joshua wskazali w komentarzach, podczas tworzenia listy mogłem przypisać imiona:

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

lub

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
źródło
1
Kilka razy zmieniałem kod. Przepraszamy za zmiany. czy to ma teraz sens? Kiedy wcześniej powiedziałem, że były wektorem, pomyliłem się. Przepraszam za to.
JD Long
2
Możesz nazwać argumenty podczas tworzenia listy:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
Nie dokładnie. Twój ma odpowiednią długość. Struktura Twojej listy różni się od struktury listy JD. Użyj str () i names (). Wszystkie elementy listy są nienazwane, więc do.callaby zakończyło się sukcesem, musiałoby być dokładne dopasowanie pozycyjne.
IRTFM
2
@JD Long; Serdecznie się zgadzam. A jeśli nie zapobiegnie to wszystkim błędom, nadal otrzymujesz znacznie lepsze komunikaty o błędach i traceback()informacje, jeśli użyjesz nazwanych argumentów.
IRTFM,
1
Nie do końca śledzę dyskusję tutaj; ponieważ pierwszy argument grid.arrange()jest ...pozycyjny dopasowanie jest prawdopodobnie nieistotne. Każde wejście musi być obiektem siatki (z nazwą lub bez), nazwanym parametrem dla grid.layoutlub nazwanym parametrem dla pozostałych argumentów.
baptysta
16

Spróbuj tego,

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
źródło
3
version> = 0.9 of gridExtra zapewnia marrangeGrob, aby robił to wszystko automatycznie, gdy nrow * ncol <length (plots)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste
4

Odpowiadam trochę późno, ale natknąłem się na rozwiązanie w książce kucharskiej R Graphics, które robi coś bardzo podobnego za pomocą niestandardowej funkcji o nazwie multiplot. Być może pomoże to innym, którzy znajdą to pytanie. Dodam również odpowiedź, ponieważ rozwiązanie może być nowsze niż inne odpowiedzi na to pytanie.

Wiele wykresów na jednej stronie (ggplot2)

Oto aktualna funkcja, ale użyj powyższego linku, ponieważ autor zauważył, że została zaktualizowana dla ggplot2 0.9.3, co oznacza, że ​​może się ponownie zmienić.

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

Tworzy się obiekty działki:

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

A następnie przekazuje je do multiplot:

multiplot(p1, p2, ..., cols = n)
Hendy
źródło