Vertically align of plots of different heights usi

2019-07-13 03:41发布

问题:

I am trying to combine two ggplot objects using cowplot::plot_grid() and vertically align them. This is normally quite simple using align = "v".

dat1 <- data.frame(x = rep(1:10, 2), y = 1:20)
dat2 <- data.frame(x = 1:10, y = 1:10)
plot1 <- ggplot(dat1, aes(x = x, y = y)) + geom_point()
plot2 <- ggplot(dat2, aes(x = x, y = y)) + geom_point()
cowplot::plot_grid(plot1, plot2, ncol = 1, align = "v")

However, this approach fails when the ggplots use coord_equal() because plot_grid() cannot modify the axes when the aspect ratio is forced. Instead, the default is to keep the heights of each plot the same.

plot1 <- ggplot(dat1, aes(x = x, y = y)) + geom_point() + coord_equal()
plot2 <- ggplot(dat2, aes(x = x, y = y)) + geom_point() + coord_equal()
cowplot::plot_grid(plot1, plot2, ncol = 1, align = "v")

I can force my objective by playing with and getting the rel_heights argument just right, but this is not a viable solution as I have many, dynamic plots to build. Here, the y-axes are aligned, and the coordinates of all axes are still equal.

cowplot::plot_grid(plot1, plot2, ncol = 1, align = "v", rel_heights = c(2, 1.07))

I've seen many approaches to similar questions that utilize ggplot2::ggplotGrob() and grid::grid_draw(), but nothing quite gets at this issues when coord_equal() is used. Perhaps the best solution doesn't use cowplot::plot_grid() at all, or perhaps the solution is somehow dynamically determining and passing the right values to rel_heights. I think I would prefer the later option so as to be able to easily use the other features that come with cowplot::plot_grid(). Perhaps some useful inspiration can be found in this related approach.

回答1:

Author of cowplot::plot_grid() here. It doesn't work when you're trying to align plots with specified aspect ratio, which you generate when using coord_equal(). The solution is to use either the egg library or the patchwork library. Patchwork is still in development but should be released to CRAN soon. In the mean time, you can install from github.

Here is a solution using egg. It seems to me that it works just fine.

library(ggplot2)
library(egg)

dat1 <- data.frame(x = rep(1:10, 2), y = 1:20)
dat2 <- data.frame(x = 1:10, y = 1:10)
plot1 <- ggplot(dat1, aes(x = x, y = y)) + geom_point() + coord_equal()
plot2 <- ggplot(dat2, aes(x = x, y = y)) + geom_point() + coord_equal()
ggarrange(plot1, plot2, ncol = 1)

Two minor issues I see are that (1) the axis ticks for the two y axes are different, and that makes it look like the spacing is different, and (2) the axes are expanded to different limits. You can work around both by manually setting ticks and expansion.

plot1 <- ggplot(dat1, aes(x = x, y = y)) + geom_point() + 
  scale_y_continuous(limits = c(0, 21), breaks = 5*(0:4), expand = c(0, 0)) +
  coord_equal()
plot2 <- ggplot(dat2, aes(x = x, y = y)) + geom_point() + 
  scale_y_continuous(limits = c(0, 11), breaks = 5*(0:4), expand = c(0, 0)) +
  coord_equal()
ggarrange(plot1, plot2, ncol = 1)



回答2:

By default, the range of the axes actually extends a little bit past the limits in the ggplot. The expand argument in function scale_continuous/discrete() was used to setting the extends. As in the scale_continuous() documentation:

A numeric vector of length two giving multiplicative and additive expansion constants. These constants ensure that the data is placed some distance away from the axes. The defaults are c(0.05, 0) for continuous variables, and c(0, 0.6) for discrete variables.

library(ggplot2)
dat1 <- data.frame(x = rep(1:10, 2), y = 1:20)
dat2 <- data.frame(x = 1:10, y = 1:10)
plot1 <- ggplot(dat1, aes(x = x, y = y)) + geom_point() + coord_equal()
plot2 <- ggplot(dat2, aes(x = x, y = y)) + geom_point() + coord_equal()

Fist, we can calculate the actual heights of this two plots, this post explains how the expand argument work.

# The defaults are c(0.05, 0) for your continuous variables
limity1 <- max(dat1$y) - min(dat1$y)
y1 <- limity1 + 2 * limity1 * 0.05
limity2 <- max(dat2$y) - min(dat2$y)
y2 <- limity2 + 2 * limity2 * 0.05

Then, use patchwork to compose this two plots

library(patchwork)
#  actual heights of plots was used to set the heights argment
plot1 + plot2 + plot_layout(ncol = 1, heights = c(y1, y2))