Как я могу организовать произвольное количество ggplots с помощью grid.arrange?

93

Это размещено в группе ggplot2 Google

Моя ситуация такова, что я работаю над функцией, которая выводит произвольное количество графиков (в зависимости от входных данных, предоставленных пользователем). Функция возвращает список из n графиков, и я хотел бы расположить эти графики в форме 2 x 2. Я борюсь с одновременными проблемами:

  1. Как я могу позволить гибкости передавать произвольное (n) количество участков?
  2. Как я могу также указать, что я хочу, чтобы они были выложены 2 x 2

Моя текущая стратегия использует grid.arrangeиз gridExtraпакета. Вероятно, это не оптимально, тем более что, и это главное, это совершенно не работает . Вот мой прокомментированный пример кода, экспериментирующий с тремя графиками:

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)

Как я обычно делаю, я смиренно жмусь в углу, с нетерпением ожидая проницательной обратной связи от сообщества, гораздо более мудрого, чем я. Особенно, если я делаю это сложнее, чем нужно.

Briandk
источник
2
Престижность за ОЧЕНЬ хорошо сделанный вопрос. Я собираюсь использовать это как пример того, как написать хороший вопрос SO [r].
JD Long
1
особенно часть "
скромного сбивания в кучу
@JD и @Ben - я польщен, ребята. Искренне. И я очень ценю помощь.
briandk

Ответы:

45

Ты почти там! Проблема в том, do.callчто ваши аргументы должны находиться в именованном listобъекте. Вы поместили их в список, но как символьные строки, а не как элементы списка.

Я думаю, это должно сработать:

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

как Бен и Джошуа отметили в комментариях, я мог бы назначить имена при создании списка:

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

или

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
источник
1
Пару раз менял код. Извините за правки. теперь это имеет смысл? Когда я сказал, что они были вектором ранее, я оговорился. Прости за это.
JD Long
2
Вы можете назвать аргументы во время создания списка:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Джошуа Ульрих
2
Не совсем. У тебя подходящая длина. Структура вашего списка отличается от структуры списка JD. Используйте str () и names (). Все элементы вашего списка безымянны, поэтому для do.callуспеха необходимо точное позиционное соответствие.
IRTFM
2
@JD Long; Я полностью согласен. И даже если это не предотвратит всех ошибок, вы все равно получите гораздо лучшие сообщения об ошибках и traceback()информацию, если будете использовать именованные аргументы.
IRTFM
1
Я не совсем слежу за обсуждением здесь; так как первый аргумент grid.arrange()является ...позиционным соответствие, вероятно , не имеет значения. Каждый вход должен быть либо объектом сетки (с именем или без него), именованным параметром для grid.layoutили именованным параметром для остальных аргументов.
baptiste
16

Попробуй это,

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
версия> = 0,9 gridExtra обеспечивает marrangeGrob , чтобы сделать все это автоматически всякий раз , когда nrow * Ncol <длина (участки)
Батист
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste
4

Я отвечаю немного поздно, но наткнулся на решение в R Graphics Cookbook, которое делает что-то очень похожее с использованием специальной функции с именем multiplot. Возможно, это поможет другим, кто найдет этот вопрос. Я также добавляю ответ, поскольку решение может быть новее, чем другие ответы на этот вопрос.

Несколько графиков на одной странице (ggplot2)

Вот текущая функция, хотя, пожалуйста, используйте приведенную выше ссылку, поскольку автор отметил, что она была обновлена ​​для ggplot2 0.9.3, что означает, что она может измениться снова.

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

Создают сюжетные объекты:

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

А затем передает их multiplot:

multiplot(p1, p2, ..., cols = n)
Хенди
источник