Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Align multiple ggplot2 graphs with a common x axis and different y axes, each with different y-axis labels.
#' When plotting multiple data series that share a common x axis but different y axes,
#' we can just plot each graph separately. This suffers from the drawback that the shared axis will typically
#' not align across graphs due to different plot margins.
#' One easy solution is to reshape2::melt() the data and use ggplot2's facet_grid() mapping. However, there is
#' no way to label individual y axes.
#' facet_grid() and facet_wrap() were designed to plot small multiples, where both x- and y-axis ranges are
#' shared acros all plots in the facetting. While the facet_ calls allow us to use different scales with
#' the \code{scales = "free"} argument, they should not be used this way.
#' A more robust approach is to the grid package grid.draw(), rbind() and ggplotGrob() to create a grid of
#' individual plots where the plot axes are properly aligned within the grid.
#' Thanks to https://rpubs.com/MarkusLoew/13295 for the grid.arrange() idea.
library(ggplot2)
library(grid)
library(dplyr)
library(lubridate)
#' Create some data to play with. Two time series with the same timestamp.
df <- data.frame(DateTime = ymd("2010-07-01") + c(0:8760) * hours(2), series1 = rnorm(8761), series2 = rnorm(8761, 100))
#' Create the two plots.
plot1 <- df %>%
select(DateTime, series1) %>%
na.omit() %>%
ggplot() +
geom_point(aes(x = DateTime, y = series1), size = 0.5, alpha = 0.75) +
ylab("Red dots / m") +
theme_minimal() +
theme(axis.title.x = element_blank())
plot2 <- df %>%
select(DateTime, series2) %>%
na.omit() %>%
ggplot() +
geom_point(aes(x = DateTime, y = series2), size = 0.5, alpha = 0.75) +
ylab("Blue drops / L") +
theme_minimal() +
theme(axis.title.x = element_blank())
grid.newpage()
grid.draw(rbind(ggplotGrob(plot1), ggplotGrob(plot2), size = "last"))
# To plot two series vertically aligned with only one labelled x-axis,
# we can remove the axes from the top plot and then plot the two graphs
# together using either the egg package or the cowplot package
library(ggplot2) # 3.2.1
library(dplyr)
library(lubridate)
library(cowplot) # 1.0.0
library(egg) # 0.4.5
#' Create some data to play with. Two time series with the same timestamp.
df <- data.frame(DateTime = ymd("2010-07-01") + c(0:8760) * hours(2),
series1 = rnorm(8761),
series2 = rnorm(8761, 100))
#' Create the two plots.
plot1 <- df %>%
select(DateTime, series1) %>%
na.omit() %>%
ggplot() +
geom_point(aes(x = DateTime, y = series1), size = 0.5, alpha = 0.75) +
ylab("Red dots / m") +
theme_minimal() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank())
plot2 <- df %>%
select(DateTime, series2) %>%
na.omit() %>%
ggplot() +
geom_point(aes(x = DateTime, y = series2), size = 0.5, alpha = 0.75) +
ylab("Blue drops / L") +
theme_minimal() +
theme(axis.title.x = element_blank())
# Draw the two plot aligned vertically, with the top plot 1/3 of the height
# of the bottom plot
cowplot::plot_grid(plot1, plot2, align = "v", ncol = 1, rel_heights = c(0.25, 0.75))
egg::ggarrange(plot1, plot2, heights = c(0.25, 0.75))
@DaveParr

This comment has been minimized.

Copy link

@DaveParr DaveParr commented Feb 4, 2015

Love this solve, totally solves a problem I was having.

One thing: is that the ymd from lubridate? If so, shouldn't you put the package in on the top?

@keithpjolley

This comment has been minimized.

Copy link

@keithpjolley keithpjolley commented Mar 19, 2015

did something like:

p1 <- ggplot(...) + geom_histogram()
p2 <- ggplot(...) + geom_boxplot() + geom_point() + coord_flip()
grid.newpage()
grid.draw(...)

and the axis were close, but not right on.

edit: was able to get them to align by adding:

p1 <- p1 + scale_x_continuous(limits=c(x_min, x_max))
p2 <- p2 + scale_y_continuous(limits=c(x_min, x_max))

thanks!

@d8aninja

This comment has been minimized.

Copy link

@d8aninja d8aninja commented Jul 24, 2015

Wow. This is genius. I hadn't even thought about using pipes like that - I'm behind the curve!

@wyldsoul

This comment has been minimized.

Copy link

@wyldsoul wyldsoul commented Aug 19, 2015

This is fantastic, wish I happened upon this solution a long time ago!

@sahilseth

This comment has been minimized.

Copy link

@sahilseth sahilseth commented Sep 3, 2015

This is awesome !

It is possible to have p1 and p2 of different heights. I have tried several approaches, but they seem to be misaligned.

for example:

grid.newpage()
layout = grid.layout(2,1,heights = c(0.30, 0.70))
pushViewport(viewport(layout = layout))
print(plot1, vp=viewport(layout.pos.row=1,layout.pos.col=1))
print(plot2, vp=viewport(layout.pos.row=2,layout.pos.col=1))
library(cowplot)
ggdraw() +
  draw_plot(plot1,  0, .7, 1, .3) +
  draw_plot(plot2,  0, 0, 1, .7)
require(gridExtra)
grid.arrange(ggplotGrob(plot1), ggplotGrob(plot2), layout_matrix = cbind(c(1,2,2,2)))
@dschneiderch

This comment has been minimized.

Copy link

@dschneiderch dschneiderch commented Dec 10, 2015

use this to get different heights (all credit goes to an answer on stack overflow):

library(gridExtra)
gA=ggplot_gtable(ggplot_build(plot1))
gB=ggplot_gtable(ggplot_build(plot2))
maxWidth = grid::unit.pmax(gA$widths[2:3], gB$widths[2:3])
gA$widths[2:3] <- as.list(maxWidth)
gB$widths[2:3] <- as.list(maxWidth)
grid.newpage()

pdf('graphs/test.pdf',
     width=8,
     height=6)
grid.arrange(
    arrangeGrob(gA,gB,nrow=2,heights=c(.8,.3))
    )
dev.off()

you'll also want to play with

theme(plot.margin=unit(c(.2,1,.1,1),"cm"))

in each of your plots to control the distance between them.

@rmariscal

This comment has been minimized.

Copy link

@rmariscal rmariscal commented Dec 19, 2015

I note that in the last version the different heights code doesn't work properly. You have to get rid of the brackets:

maxWidth = grid::unit.pmax(gA$widths, gB$widths)
gA$widths <- as.list(maxWidth)
gB$widths <- as.list(maxWidth)

@smasongarrison

This comment has been minimized.

Copy link

@smasongarrison smasongarrison commented Jan 7, 2016

Note: I had to install an extra library to get this code to work. I had to add the library(lubridate) for the ymd function to work.

@anmolsethy

This comment has been minimized.

Copy link

@anmolsethy anmolsethy commented Aug 8, 2016

Very neat. Thanks a lot, this was really helpful. I was struggling to align the y-axis in multiple graphs.

@vc1492a

This comment has been minimized.

Copy link

@vc1492a vc1492a commented Dec 16, 2016

Very neat and tidy.

@meniluca

This comment has been minimized.

Copy link

@meniluca meniluca commented Mar 10, 2017

Thank you Tom!

@mgm-cincy

This comment has been minimized.

Copy link

@mgm-cincy mgm-cincy commented Mar 16, 2017

Thanks, this is very helpful. I have a question. I ran grid.draw and my plot with the two aligned graphs appeared in the plot frame of RStudio. I then tried this assignment:
pdbox_spc_cms <- grid.draw(rbind(ggplotGrob(pdbox1), ggplotGrob(pdbox2), size = "last")) so that I then could use ggsave to save in the file format, dimensions and dpi that I needed. But, that assignment returned as NULL. Any suggestions as to how should I have made that assignment?

@mattecologist

This comment has been minimized.

Copy link

@mattecologist mattecologist commented Apr 21, 2017

Nice! Cheers for this.

@wsgroves

This comment has been minimized.

Copy link

@wsgroves wsgroves commented Jun 8, 2017

In response to mgm-cincy on March 16:

ggplot2's ggsave() function has the ability to save grid.draw().

eg.:
ggsave("File name.png", path = "File path", plot = grid.draw(rbind(ggplotGrob(plot1), ggplotGrob(plot2), size = "last")), ... )

Hope this helps!

@tdjames1

This comment has been minimized.

Copy link

@tdjames1 tdjames1 commented Nov 14, 2017

Thanks, this is just what I needed.

@LukasBDF

This comment has been minimized.

Copy link

@LukasBDF LukasBDF commented Nov 22, 2017

Splendid! Is there a way to supress the printing of the same x axis label on each subplot except the last one?

@julian-lemos

This comment has been minimized.

Copy link

@julian-lemos julian-lemos commented Dec 21, 2017

Great solution, thanks!

@lker5lker5

This comment has been minimized.

Copy link

@lker5lker5 lker5lker5 commented Dec 29, 2017

Awesome! but a little bit extension: in this case, how can I seamlessly combine these two plots with only eaxcatly ONE x-axis (i.e. there is no space left between these two rows)?

@sunxm19

This comment has been minimized.

Copy link

@sunxm19 sunxm19 commented Jan 31, 2018

Just beautiful code!

@KanikaNahata

This comment has been minimized.

Copy link

@KanikaNahata KanikaNahata commented May 22, 2018

Thank you! This helps a lot!

@naiieandrade

This comment has been minimized.

Copy link

@naiieandrade naiieandrade commented May 27, 2018

Thannk you!! Easy and help me a lot too! 😁

@MattChristmas

This comment has been minimized.

Copy link

@MattChristmas MattChristmas commented Jun 5, 2018

Exactly what I was looking for, thank you! So neat and saves me fiddling around with aligning plots outside of R.

r9_onp_depth_plot copy

@ADam-Z514

This comment has been minimized.

Copy link

@ADam-Z514 ADam-Z514 commented Jul 20, 2018

Can both graphs have differents names?
I have managed to put 2 graphs in the same plot using grid.arrange : grid.arrange(graph_weight_ori, graph_weight_imp, nrow = 1)
how can I give them different names?

@DatenBergwerker

This comment has been minimized.

Copy link

@DatenBergwerker DatenBergwerker commented Jul 31, 2018

@ADam-Z514
It should work the same with grid.arrange. You should have to convert to Grob first and bind them together with rbind.

grid.arrange(rbind(ggplotGrob(graph_weight_ori), ggplotGrob(bpgraph_weight_imp), size = "first"))

@bggalvenmon

This comment has been minimized.

Copy link

@bggalvenmon bggalvenmon commented Aug 1, 2018

This code is great, but is there a way that you can bind both plots together so that x-axis for the upper plot is only separated by a line so as not to show the numbers. Basically, I want two plots directly on top of each other with the same scales for the y-axis but two different variables. Is this possible? I am having trouble finding any code to do this exactly.

@bggalvenmon

This comment has been minimized.

Copy link

@bggalvenmon bggalvenmon commented Aug 3, 2018

for some reason I got this plot to work and now the select command is giving me an error: "Error in select(., d34S, d13C) : unused arguments (d34S, d13C)". The libraries I am running in the background are:

library(tidyverse)
library(ggplot2)
library(broom)
library(sjstats)
library(plotly)
library(fastDummies)
library(MASS)
library(AICcmodavg)
library(car)
library(MASS)
library(grid)
library(dplyr)
library(lubridate)

Can anybody help me?

@gorgitko

This comment has been minimized.

Copy link

@gorgitko gorgitko commented Sep 11, 2018

@bggalvenmon Some package is masking dplyr's select: use dplyr::select

@jmbarbone

This comment has been minimized.

Copy link

@jmbarbone jmbarbone commented Nov 6, 2018

@bggalvenmon Some package is masking dplyr's select: use dplyr::select

I think there is a select function loaded through the MASS package - I recently had a script failing because of this issue.

If you don't want to keep calling the function direction you can add the following to the beginning of your script: select <- dplyr::select

@norakirkizh

This comment has been minimized.

Copy link

@norakirkizh norakirkizh commented Dec 2, 2019

did something like:

p1 <- ggplot(...) + geom_histogram()
p2 <- ggplot(...) + geom_boxplot() + geom_point() + coord_flip()
grid.newpage()
grid.draw(...)

and the axis were close, but not right on.

edit: was able to get them to align by adding:

p1 <- p1 + scale_x_continuous(limits=c(x_min, x_max))
p2 <- p2 + scale_y_continuous(limits=c(x_min, x_max))

thanks!

very nice solution, especially if one wants to compile 2-by-2 histograms from separate data frames.

@Sarikhan

This comment has been minimized.

Copy link

@Sarikhan Sarikhan commented Dec 8, 2019

Thanks!

@lars3033

This comment has been minimized.

Copy link

@lars3033 lars3033 commented May 11, 2020

Great help, thanks very much for posting!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.