我如何使用 grid 来安排任意数量的 ggplot?

这是交叉张贴在 ggplot2谷歌组

我的情况是,我是 正在做一个功能,它输出任意数量的图(取决于用户提供的输入数据)。该函数返回一个 n 个图形的列表,我想将这些图形以2 x 2的形式展开。我一直在努力解决同时存在的问题:

  1. 我怎样才能允许灵活性被交给任意(n)数量的情节?
  2. 我怎样才能指定它们是2 x 2的呢

我目前的策略使用 gridExtra包中的 grid.arrange。这可能不是最佳选择,特别是因为,这是关键,完全没用。下面是我的注释示例代码,试验了三个情节:

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)

正如我习惯做的那样,我谦卑地蜷缩在角落里,急切地等待着一个比我聪明得多的社区的睿智反馈,尤其是当我让这件事变得比实际需要的更困难的时候。

34448 次浏览

You're ALMOST there! The problem is that do.call expects your args to be in a named list object. You've put them in the list, but as character strings, not named list items.

I think this should work:

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

as Ben and Joshua pointed out in the comments, I could have assigned names when I created the list:

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

or

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

Try this,

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)

I'm answering a bit late, but stumbled on a solution at the R Graphics Cookbook that does something very similar using a custom function called multiplot. Perhaps it will help others who find this question. I'm also adding the answer as the solution may be newer than the other answers to this question.

Multiple graphs on one page (ggplot2)

Here's the current function, though please use the above link, as the author noted that it's been updated for ggplot2 0.9.3, which indicates it may change again.

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

One creates plot objects:

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

And then passes them to multiplot:

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