22

It's relatively simple using grid.arrange in the gridExtra package to arrange multiple plots in a matrix, but how can you arrange plots (the ones I'm working on are from ggplot2) when some plots are intended to be larger than others? In base, I can use layout() such as in the example below:

 nf <- layout(matrix(c(1,1,1,2,3,1,1,1,4,5,6,7,8,9,9), byrow=TRUE, nrow=3))
 layout.show(nf)

what is the equivalent for ggplot plots?

enter image description here

Some plots for inclusion

library(ggplot2)
p1 <- qplot(x=wt,y=mpg,geom="point",main="Scatterplot of wt vs. mpg", data=mtcars)
p2 <- qplot(x=wt,y=disp,geom="point",main="Scatterplot of wt vs disp", data=mtcars)
p3 <- qplot(wt,data=mtcars)
p4 <- qplot(wt,mpg,data=mtcars,geom="boxplot")
p5 <- qplot(wt,data=mtcars)
p6 <- qplot(mpg,data=mtcars)
p7 <- qplot(disp,data=mtcars)
p8 <- qplot(disp, y=..density.., geom="density", data=mtcars)
p9 <- qplot(mpg, y=..density.., geom="density", data=mtcars)
Hugh
  • 15,521
  • 12
  • 57
  • 100
  • 1
    This [SO question](http://stackoverflow.com/questions/9490482/combined-plot-of-ggplot2-not-in-a-single-plot-using-par-or-layout-functio) could help. – Didzis Elferts Aug 25 '13 at 09:24

5 Answers5

13

You can use nested arrangeGrob calls like this example:

library(ggplot2)
library(gridExtra)

p <- ggplot(data.frame(x=1, y=1), aes(x,y)) + geom_point()

grid.arrange(
  arrangeGrob(
    p, 
    arrangeGrob(p, p, nrow=2),
    ncol=2 ,widths=c(2,1)),
  arrangeGrob(p, p ,p ,ncol=3, widths=rep(1,3)),
  nrow=2)

Edit:

gl <- lapply(1:9, function(ii) grobTree(rectGrob(),textGrob(ii)))

grid.arrange(
  arrangeGrob(gl[[1]],
              do.call(arrangeGrob, c(gl[2:5], ncol=2)),
              nrow=1,
              widths=3:2),
  do.call(arrangeGrob, c(gl[6:9], nrow=1, list(widths=c(1,1,1,2)))),
nrow=2, heights=c(2,1))

enter image description here

Roland
  • 127,288
  • 10
  • 191
  • 288
  • you don't need an anonymous function with `do.call`, there's already `...` in the definition of `arrangeGrob` – baptiste Aug 25 '13 at 13:57
  • @baptiste I know and use that with `nrow`, but got an error when trying to pass `widths`. – Roland Aug 25 '13 at 14:01
  • 1
    it's because of the way `c()` works with lists. One needs to use `do.call(arrangeGrob, c(gl[6:9], nrow=1, list(widths=c(1,1,1,2))))` – baptiste Aug 25 '13 at 21:13
11

An alternative with gtable

library(gtable)

gl <- lapply(1:9, function(ii) grobTree(textGrob(ii), rectGrob()))
# gl <- lapply(1:9, function(ii) ggplotGrob(qplot(1,1) + ggtitle(ii)))

gt <- gtable(widths=unit(rep(1,5), "null"),
             heights=unit(rep(1,3), "null"))

gtable_add_grobs <- gtable_add_grob # alias

gt <- gtable_add_grobs(gt, gl, 
                       l=c(1,4,5,4,5,1,2,3,4),
                       r=c(3,4,5,4,5,1,2,3,5),
                       t=c(1,1,1,2,2,3,3,3,3),
                       b=c(2,1,1,2,2,3,3,3,3))
grid.newpage()
grid.draw(gt)

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • This seems a popular method, but I can't work out how to add ggplot-like grobs to the plot. (In Roland's example, it's easy.) Could you explain how to add plots in? (I've included some sample plots in OP.) – Hugh Aug 26 '13 at 23:54
  • 1
    `ggplotGrob` converts a ggplot to a grob, there's a commented line using it in my example – baptiste Aug 27 '13 at 00:01
9

You can use the same matrix interface as layout with grid.arrange,

library(gridExtra)
library(grid)
gl <- lapply(1:9, function(ii) grobTree(rectGrob(), textGrob(ii)))

grid.arrange(grobs = gl, layout_matrix = rbind(c(1,1,1,2,3),
                                               c(1,1,1,4,5),
                                               c(6,7,8,9,9)))

enter image description here

and the same works for ggplots; note that NA can be used to indicate blank cells. The result is a gtable, compatible with ggsave().

gl <- replicate(9, ggplot(), FALSE)
grid.arrange(grobs = gl, layout_matrix = rbind(c(1,1,1,2,3),
                                               c(1,1,1,4,5),
                                               c(6,7,8,NA,9)))

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
8

I appreciate all the other answers, but Didzis Elferts's comment on the OP connected to the answer that I found easiest to implement.

library(ggplot2)
p1 <- qplot(x=wt,y=mpg,geom="point",main="Scatterplot of wt vs. mpg", data=mtcars)
p2 <- qplot(x=wt,y=disp,geom="point",main="Scatterplot of wt vs disp", data=mtcars)
p3 <- qplot(wt,data=mtcars)
p4 <- qplot(wt,mpg,data=mtcars,geom="boxplot")
p5 <- qplot(wt,data=mtcars)
p6 <- qplot(mpg,data=mtcars)
p7 <- qplot(disp,data=mtcars)
p8 <- qplot(disp, y=..density.., geom="density", data=mtcars)
p9 <- qplot(mpg, y=..density.., geom="density", data=mtcars)

vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)

grid.newpage()
pushViewport(viewport(layout = grid.layout(3, 5))) # 3 rows, 5 columns
print(p1, vp = vplayout(1:2, 1:3))  # the big plot covers rows 1:2 and cols 1:3
print(p2, vp = vplayout(1, 4))
print(p3, vp = vplayout(1, 5))
print(p4, vp = vplayout(2, 4))
print(p5, vp = vplayout(2, 5))
print(p6, vp = vplayout(3, 1))
print(p7, vp = vplayout(3, 2))
print(p8, vp = vplayout(3, 3))
print(p9, vp = vplayout(3, 4:5))
Community
  • 1
  • 1
Hugh
  • 15,521
  • 12
  • 57
  • 100
8

I like the interface provided by the lay_out function (formerly in the wq package) . It takes arguments of the form list(plot, row(s), column(s)). For your example:

lay_out(list(p1, 1:2, 1:3),
       list(p2, 1, 4),
       list(p3, 1, 5),
       list(p4, 2, 4),
       list(p5, 2, 5),
       list(p6, 3, 1),
       list(p7, 3, 2),
       list(p8, 3, 3),
       list(p9, 3, 4:5))

Which yields:

enter image description here

lay_out = function(...) {    
    x <- list(...)
    n <- max(sapply(x, function(x) max(x[[2]])))
    p <- max(sapply(x, function(x) max(x[[3]])))
    grid::pushViewport(grid::viewport(layout = grid::grid.layout(n, p)))    

    for (i in seq_len(length(x))) {
        print(x[[i]][[1]], vp = grid::viewport(layout.pos.row = x[[i]][[2]], 
            layout.pos.col = x[[i]][[3]]))
    }
} 

(Code sourced from a prior version of the wq package, from the commit history on the unofficial Github CRAN mirror.)

Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
  • this is the simplest solution but it appears that the layOut function no longer exists. Are there any other straightforward options that you are aware of? – Meli Jun 06 '16 at 15:30
  • Thank you. Is there a way to make this work for combining ggplot and non ggplot graphics? – Meli Jun 10 '16 at 18:51
  • It should work for any `grid` graphics, which mostly means `ggplot` and `lattice`. If you want to use `base` graphics you could maybe make it work with the `gridBase` package. – Gregor Thomas Jun 10 '16 at 19:48