21
votes

I am trying to use grid.arrange to display multiple graphs on the same page generated by ggplot. The plots use the same x data but with different y variables. The plots come out with differing dimensions due to the y-data having different scales.

I have tried using various theme options within ggplot2 to change the plot size and move the y axis label but none have worked to align the plots. I want the plots arranged in a 2 x 2 square so that each plot is the same size and the x-axes align.

Here is some test data:

A <- c(1,5,6,7,9)
B <- c(10,56,64,86,98)
C <- c(2001,3333,5678,4345,5345)
D <- c(13446,20336,24333,34345,42345)
L <- c(20,34,45,55,67)
M <- data.frame(L, A, B, C, D)

And the code that I am using to plot:

x1 <- ggplot(M, aes(L, A,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x2 <- ggplot(M, aes(L, B,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x3 <- ggplot(M, aes(L, C,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x4 <- ggplot(M, aes(L, D,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
grid.arrange(x1,x2,x3,x4,nrow=2)

If you run this code, you will see that the bottom two plots have a smaller plot area due to the greater length of the y-axes units.

How do I make the actual plot windows the same?

5

5 Answers

9
votes

I would use faceting for this problem:

library(reshape2)
dat <- melt(M,"L") # When in doubt, melt!

ggplot(dat, aes(L,value)) + 
geom_point() + 
stat_smooth(method="lm") + 
facet_wrap(~variable,ncol=2,scales="free")

Example

Note: The layman may miss that the scales are different between facets.

21
votes

Edit

Simpler solutions are: 1) use the cowplot package (see answer here); or 2) use egg package available on github.

# devtools::install_github("baptiste/egg")
library(egg)
library(grid)

g = ggarrange(x1, x2, x3, x4, ncol = 2)
grid.newpage()
grid.draw(g)

Original

Minor edit: Updating code.

If you want to keep the axis labels, then with some fiddling, and borrowing code from here, this does the job.

library(ggplot2)
library(gtable)
library(grid)
library(gridExtra)

# Get the widths
gA <- ggplotGrob(x1)
gB <- ggplotGrob(x2)
gC <- ggplotGrob(x3)
gD <- ggplotGrob(x4)
maxWidth = unit.pmax(gA$widths[2:3], gB$widths[2:3], 
                     gC$widths[2:3], gD$widths[2:3])

# Set the widths
gA$widths[2:3] <- maxWidth
gB$widths[2:3] <- maxWidth
gC$widths[2:3] <- maxWidth
gD$widths[2:3] <- maxWidth

# Arrange the four charts
grid.arrange(gA, gB, gC, gD, nrow=2)

enter image description here

ALTERNATIVE SOLUTIONS: There are rbind and cbind functions in the gtable package for combining grobs into one grob. For the charts here, the widths should be set using size = "max", but the CRAN version of gtable throws an error.

One option is to examine the grid.arrange plot, then use size = "first" or size = "last"` options:

# Get the ggplot grobs
gA <- ggplotGrob(x1)  
gB <- ggplotGrob(x2)
gC <- ggplotGrob(x3)
gD <- ggplotGrob(x4)

# Arrange the four charts
grid.arrange(gA, gB, gC, gD, nrow=2)

# Combine the plots   
g = cbind(rbind(gA, gC, size = "last"), rbind(gB, gD, size = "last"), size = "first")

# draw it
grid.newpage()
grid.draw(g)

A second option is to binding functions from gridExtra package.

# Get the ggplot grobs
gA <- ggplotGrob(x1)  
gB <- ggplotGrob(x2)
gC <- ggplotGrob(x3)
gD <- ggplotGrob(x4)

# Combine the plots
g = cbind.gtable(rbind.gtable(gA, gC, size = "max"), rbind.gtable(gB, gD, size = "max"), size = "max")

# Draw it
grid.newpage()
grid.draw(g)
10
votes

That's exactly the kind of problem for which I wrote the cowplot package. It can be done in one line in that package:

require(cowplot) # loads ggplot2 as dependency
# re-create the four plots
A <- c(1,5,6,7,9)
B <- c(10,56,64,86,98)
C <- c(2001,3333,5678,4345,5345)
D <- c(13446,20336,24333,34345,42345)
L <- c(20,34,45,55,67)
M <- data.frame(L, A, B, C, D)
x1 <- ggplot(M, aes(L, A,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x2 <- ggplot(M, aes(L, B,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x3 <- ggplot(M, aes(L, C,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')
x4 <- ggplot(M, aes(L, D,xmin=10,ymin=0)) + geom_point() + stat_smooth(method='lm')

# arrange into grid and align
plot_grid(x1, x2, x3, x4, align='vh')

This is the result: enter image description here (Note that cowplot changes the default ggplot2 theme. You can get the gray one back though if you really want to.)

As a bonus feature, you can also add plot labels in the top-left corner of each graph:

plot_grid(x1, x2, x3, x4, align='vh', labels=c('A', 'B', 'C', 'D'))

Result: enter image description here

I use the labels option on virtually every multi-part graph I make.

4
votes

Patchwork is a new package which makes it really easy to format and layout multiple ggplots. One of the best things about it is that it aligns the plot areas automatically. Plus, the syntax is really easy.

devtools::install_github("thomasp85/patchwork")
library(patchwork)
x1 + x2 + x3 + x4 + plot_layout(ncol = 2)

enter image description here

Check out the GitHub page for more examples: https://github.com/thomasp85/patchwork

0
votes

If you are using RMarkdown and knitting to PDF, I have an alternative approach. Knitr offers the functionality to plot subfigures when creating a PDF, which allows you to have multiple figures in a plot, each with their own caption.

For this to work, each plot has to be displayed separately. By joining together several functions from the cowplot package, I made the following function which aligns plots whilst keeping them as separate objects:

plot_grid_split <- function(..., align = "hv", axis= "tblr"){
  aligned_plots <- cowplot::align_plots(..., align=align, axis=axis)
  plots <- lapply(1:length(aligned_plots), function(x){
    cowplot::ggdraw(aligned_plots[[x]])
  })
  invisible(capture.output(plots))
}

Here is an example, comparing the layout normally vs using the function:

---
output: pdf_document
header-includes:
   - \usepackage{subfig}
---

```{r}
plot_grid_split <- function(..., align = "hv", axis= "tblr"){
  aligned_plots <- cowplot::align_plots(..., align=align, axis=axis)
  plots <- lapply(1:length(aligned_plots), function(x){
    cowplot::ggdraw(aligned_plots[[x]])
  })
  invisible(capture.output(plots))
}
```

```{r fig-sub, fig.cap='Four Plots Not Aligned', fig.subcap=c('Plot One', 'Plot Two', 'Plot Three', 'Plot Four'), out.width='.49\\linewidth', fig.asp=1, fig.ncol = 2}  
library(ggplot2) 
plot <- ggplot(iris, aes(Sepal.Length, Sepal.Width, colour = Species)) +
  geom_point()

plot + labs(title = "A nice title")
plot + labs(caption = "A sample caption")
plot + theme(legend.position = "none")
plot + theme(legend.position = "top")
```  

```{r fig-sub-2, fig.cap='Four Plots Aligned', fig.subcap=c('Plot One', 'Plot Two', 'Plot Three', 'Plot Four'), out.width='.49\\linewidth', fig.asp=1, fig.ncol = 2}

x1 <- plot + labs(title = "A nice title")
x2 <- plot + labs(caption = "A sample caption")
x3 <- plot + theme(legend.position = "none")
x4 <- plot + theme(legend.position = "top")

plot_grid_split(x1, x2, x3, x4)
```

enter image description here