Comment puis-je organiser un nombre arbitraire de ggplots à l'aide de grid.arrange?

93

Ceci est posté sur le groupe google ggplot2

Ma situation est que je travaille sur une fonction qui génère un nombre arbitraire de tracés (en fonction des données d'entrée fournies par l'utilisateur). La fonction renvoie une liste de n parcelles, et j'aimerais disposer ces parcelles en formation 2 x 2. Je lutte avec les problèmes simultanés de:

  1. Comment puis-je permettre à la flexibilité de recevoir un nombre arbitraire (n) de parcelles?
  2. Comment puis-je également spécifier que je veux les disposer 2 x 2

Ma stratégie actuelle utilise grid.arrangele gridExtrapackage. Ce n'est probablement pas optimal, d'autant plus que, et c'est la clé, cela ne fonctionne absolument pas . Voici mon exemple de code commenté, en expérimentant trois graphiques:

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)

Comme je n'ai pas l'habitude de le faire, je me blottis humblement dans le coin, attendant avec impatience les commentaires avisés d'une communauté bien plus sage que moi. Surtout si je rends cela plus difficile que nécessaire.

briandk
la source
2
Félicitations pour une question TRÈS bien faite. Je vais utiliser ceci comme exemple de la façon d'écrire une bonne question SO [r].
JD Long
1
en particulier la partie "humblement blottis" - rien de tel qu'un bon grovel :-)
Ben Bolker
@JD et @Ben - Je suis flatté, les gars. Cordialement. Et j'apprécie vraiment l'aide.
briandk

Réponses:

45

Tu y es presque! Le problème est que do.calls'attend à ce que vos arguments soient dans un listobjet nommé . Vous les avez mis dans la liste, mais sous forme de chaînes de caractères et non d'éléments de liste nommés.

Je pense que cela devrait fonctionner:

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

comme Ben et Joshua l'ont souligné dans les commentaires, j'aurais pu attribuer des noms lorsque j'ai créé la liste:

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

ou

args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
JD Long
la source
1
J'ai changé le code plusieurs fois. Désolé pour les modifications. cela a-t-il un sens maintenant? Quand j'ai dit qu'ils étaient un vecteur plus tôt, je me suis mal exprimé. Désolé pour ça.
JD Long
2
Vous pouvez nommer les arguments lors de la création de la liste:args.list <- list(x=x, y=y, z=x, nrow=2, ncol=2)
Joshua Ulrich
2
Pas exactement. Le vôtre est de la bonne longueur. La structure de votre liste est différente de la structure de la liste de JD. Utilisez str () et names (). Tous les éléments de votre liste ne sont pas nommés, donc pour que le do.callréussisse, il aurait fallu une correspondance positionnelle exacte.
IRTFM
2
@JD Long; Je suis tout à fait d'accord. Et même si cela n'empêche pas toutes les erreurs, vous obtenez toujours de bien meilleurs messages d'erreur et traceback()informations si vous utilisez des arguments nommés.
IRTFM
1
Je ne suis pas tout à fait la discussion ici; depuis le premier argument grid.arrange()est ...mise en correspondance de position est probablement sans importance. Chaque entrée doit être un objet de grille (avec ou sans nom), un paramètre nommé pour grid.layoutou un paramètre nommé pour les arguments restants.
baptiste le
16

Essaye ça,

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
la source
3
version> = 0.9 de gridExtra fournit à marrangeGrob pour faire tout cela automatiquement chaque fois que nrow * ncol <length (plots)
baptiste
5
ggsave("multipage.pdf", do.call(marrangeGrob, c(plots, list(nrow=2, ncol=2))))
baptiste le
4

Je réponds un peu tard, mais je suis tombé sur une solution au R Graphics Cookbook qui fait quelque chose de très similaire en utilisant une fonction personnalisée appelée multiplot. Cela aidera peut-être ceux qui trouvent cette question. J'ajoute également la réponse car la solution peut être plus récente que les autres réponses à cette question.

Plusieurs graphiques sur une page (ggplot2)

Voici la fonction actuelle, mais veuillez utiliser le lien ci-dessus, car l'auteur a noté qu'elle a été mise à jour pour ggplot2 0.9.3, ce qui indique qu'elle pourrait changer à nouveau.

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

On crée des objets de tracé:

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

Et puis les transmet à multiplot:

multiplot(p1, p2, ..., cols = n)
Hendy
la source