Bagaimana cara mengatur jumlah ggplot yang berubah-ubah menggunakan grid.arrange?

93

Ini diposkan silang di grup google ggplot2

Situasi saya adalah bahwa saya sedang mengerjakan fungsi yang mengeluarkan sejumlah plot yang berubah-ubah (tergantung pada data input yang disediakan oleh pengguna). Fungsi mengembalikan daftar plot n, dan saya ingin meletakkan plot tersebut dalam formasi 2 x 2. Saya berjuang dengan masalah simultan dari:

  1. Bagaimana saya dapat mengizinkan fleksibilitas untuk diberikan sejumlah plot (n) yang berubah-ubah?
  2. Bagaimana saya juga bisa menentukan saya ingin mereka ditata 2 x 2

Strategi saya saat ini menggunakan grid.arrangedari gridExtrapaket. Ini mungkin tidak optimal, terutama karena, dan ini adalah kuncinya, ini sama sekali tidak berfungsi . Berikut kode contoh saya yang dikomentari, bereksperimen dengan tiga plot:

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)

Seperti yang biasa saya lakukan, saya dengan rendah hati meringkuk di sudut, dengan penuh semangat menunggu umpan balik bijak dari komunitas yang jauh lebih bijaksana daripada saya. Terutama jika saya membuat ini lebih sulit dari yang seharusnya.

briandk
sumber
2
Kudos untuk pertanyaan yang SANGAT bagus. Saya akan menggunakan ini sebagai contoh bagaimana menulis pertanyaan SO [r] yang baik.
JD Long
1
terutama bagian "berkumpul dengan rendah hati" - tidak ada yang seperti merendahkan diri yang baik :-)
Ben Bolker
@JD dan @Ben - Aku tersanjung, guys. Hormat kami. Dan saya sangat menghargai bantuannya.
briandk

Jawaban:

45

Kamu hampir sampai! Masalahnya adalah do.callAnda mengharapkan args Anda berada di listobjek bernama . Anda telah memasukkannya ke dalam daftar, tetapi sebagai string karakter, bukan item daftar bernama.

Saya pikir ini seharusnya berhasil:

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

seperti yang ditunjukkan Ben dan Joshua di komentar, saya bisa saja menetapkan nama ketika saya membuat daftar:

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

atau

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
sumber
1
Saya mengubah kode beberapa kali. Maaf atas pengeditannya. apakah itu masuk akal sekarang? Ketika saya mengatakan mereka adalah vektor sebelumnya, saya salah bicara. Maaf soal itu.
JD Long
2
Anda dapat memberi nama args selama pembuatan daftar:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
Tidak persis. Panjang Anda sesuai. Struktur daftar Anda berbeda dengan struktur daftar JD. Gunakan str () dan nama (). Semua elemen daftar Anda tidak memiliki nama, jadi do.callagar berhasil, diperlukan pencocokan posisi yang tepat.
IRTFM
2
@Pengen Saya sangat setuju. Dan jika itu tidak mencegah semua kesalahan, Anda masih mendapatkan pesan dan traceback()informasi kesalahan yang jauh lebih baik jika Anda menggunakan argumen bernama.
IRTFM
1
Saya kurang mengikuti diskusi di sini; karena argumen pertama grid.arrange()adalah ...pencocokan posisi mungkin tidak relevan. Setiap masukan harus berupa objek kisi (dengan atau tanpa nama), parameter bernama untuk grid.layout, atau parameter bernama untuk argumen yang tersisa.
baptiste
16

Coba ini,

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
sumber
3
version> = 0.9 dari gridExtra menyediakan marrangeGrob untuk melakukan semua ini secara otomatis setiap kali nrow * ncol <length (plot)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste
4

Saya menjawab agak terlambat, tetapi tersandung pada solusi di R Graphics Cookbook yang melakukan sesuatu yang sangat mirip menggunakan fungsi khusus yang disebut multiplot . Mungkin itu akan membantu orang lain yang menemukan pertanyaan ini. Saya juga menambahkan jawaban karena solusinya mungkin lebih baru daripada jawaban lain untuk pertanyaan ini.

Beberapa grafik dalam satu halaman (ggplot2)

Inilah fungsi saat ini, meskipun harap gunakan tautan di atas, seperti yang dicatat oleh penulis bahwa itu telah diperbarui untuk ggplot2 0.9.3, yang menunjukkan bahwa itu dapat berubah lagi.

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

Satu membuat objek plot:

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

Dan kemudian meneruskannya ke multiplot:

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