многопанельный ggplot из списка с grid_arrange_shared_legend

#r #ggplot2 #lemon

#r #ggplot2

Вопрос:

Я пытаюсь сделать мою мультипанель ggplot с общей легендой более гибкой в ShinyApp , позволяя пользователю выбирать, сколько панелей отображать.

В настоящее время мой код записывает объекты панели по 1 за раз, подобный этому.

 grid_arrange_shared_legend(p1,p2,p3,p4, ncol = 4, nrow = 1)
  

Я не совсем понимаю, почему я не могу найти способ сообщить grid_arrange_shared_legend , чтобы он принимал список графиков (list object), а не выписывал их один за другим.
Выдает эту ошибку:

Ошибка в UseMethod(«ggplot_build»): нет применимого метода для ‘ggplot_build’, примененного к объекту класса «NULL»

 library(ggplot2)
library(lemon)
plotlist <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plotlist$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plotlist$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plotlist$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plotlist$p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(plotlist, ncol = 4, nrow = 1)
  

при использовании списка не имело бы значения, сколько графиков в списке, и я бы рассчитал ncol или nrow на основе длины списка…

Ответ №1:

Моя домашняя версия функции получает это, добавляя plotlist параметр и plots <- c(list(...), plotlist) строку в качестве первой строки кода. Таким образом, он может принимать как список графиков, так и отдельные объекты графика.

 grid_arrange_shared_legend_plotlist <- function(..., 
                                                plotlist=NULL,
                                                ncol = length(list(...)),
                                                nrow = NULL,
                                                position = c("bottom", "right")) {

  plots <- c(list(...), plotlist)

  if (is.null(nrow)) nrow = ceiling(length(plots)/ncol)

  position <- match.arg(position)
  g <- ggplotGrob(plots[[1]]   theme(legend.position = position))$grobs
  legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
  lheight <- sum(legend$height)
  lwidth <- sum(legend$width)
  gl <- lapply(plots, function(x) x   theme(legend.position="none"))
  gl <- c(gl, ncol = ncol, nrow = nrow)

  combined <- switch(position,
                     "bottom" = arrangeGrob(do.call(arrangeGrob, gl),
                                            legend,
                                            ncol = 1,
                                            heights = unit.c(unit(1, "npc") - lheight, lheight)),
                     "right" = arrangeGrob(do.call(arrangeGrob, gl),
                                           legend,
                                           ncol = 2,
                                           widths = unit.c(unit(1, "npc") - lwidth, lwidth)))

  grid.newpage()
  grid.draw(combined)

  # return gtable invisibly
  invisible(combined)
}
  

Используя ваш пример:

 library(gridExtra)
library(grid)
library(ggplot2)
plots <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plots$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plots$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plots$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plots$p4 <- qplot(depth, price, data = dsamp, colour = clarity)

grid_arrange_shared_legend_plotlist(plotlist = plots, ncol = 4)
  

результирующий график

Комментарии:

1. по-прежнему получаю ту же ошибку Ошибка в UseMethod(«ggplot_build»): нет применимого метода для ‘ggplot_build’, примененного к объекту класса «NULL» grid_arrange_shared_legend_custom(plotlist, ncol = 4, nrow = 1) (присвоено функции новое имя)

2. Вы явно передаете объект plotlist в параметр plotlist? (извините, что они называются одинаково). Нравится grid_arrange_shared_legend(plotlist = plotlist, ncol = 4) . Если вы этого не сделаете, список графиков будет взят … предполагается, что это один объект plot, что приводит к ошибке.

3. ах да, это все объясняет. Я также уже понял, что «plotlist» не было разумным названием для фиктивного кода, в моем реальном приложении у него другое имя. Я обнаружил, что мое глупое решение eval (parse, приведенное ниже, тоже работает как шарм

4. Однако ваше решение намного приятнее читается с точки зрения кода, и действительно, сейчас оно работает нормально. обнимашки. Я приму ваш ответ как решение

5. Спасибо! Рад, что был чем-то полезен. Оглядываясь назад, я должен был включить рабочий пример с самого начала. Впредь я буду иметь это в виду.

Ответ №2:

Решение для вставки уродливой текстовой строки:

Поскольку предоставленные ответы, похоже, не работают или не подходят (перестраивают совершенно другой набор графиков, чем список объектов графика, который у меня уже есть из обширного кода, я немного поиграл с eval(parse(text = ....) и paste0 , чтобы динамически генерировать текстовую строку, которая в конечном итоге является полностью выписанным кодом (который работает), фактически не записывая его

 nplots = 4
nrow = 2
ncol = ceiling(nplots/nrow)
eval(parse( text = paste0("grid_arrange_shared_legend(", paste0("plotlist", "[[", c(1:nplots), "]]", sep = '', collapse = ','), ",ncol =", ncol, ",nrow =", nrow, ", position = 'right',  top=grid::textGrob('My title', gp=grid::gpar(fontsize=18)))", sep = '')))
  

которое создает:

[1] «grid_arrange_shared_legend(список сюжетов[[1]], список сюжетов[[2]], список сюжетов[[3]], список сюжетов [[4]], ncol =2,nrow = 2, position = ‘right’, top=grid:: textGrob(‘Мой заголовок’, gp=grid::gpar(размер шрифта = 18)))»