aelwan aelwan - 3 months ago 41
R Question

ggplot: align plots together and add common labels and legend

I have a

data.frame
in which one variable has many levels (e.g.
param1
,
param2
and
param3
). All these levels have the same unit (concentration mg L-1) but they have different ranges of values.

For example

param1
ranges from
0
to
0.4


param2
ranges from
0
to
0.07


param3
ranges from
0
to
3000


Instead of using my data.frame, I used
diamonds
to create a reproducible example

library(dplyr)
library(ggplot2)
df <- diamonds %>%
dplyr::filter(cut%in%c("Fair","Ideal")) %>%
dplyr::filter(clarity%in%c("I1" , "SI2" , "SI1" , "VS2" , "VS1", "VVS2")) %>%
dplyr::mutate(new_price = ifelse(cut == "Fair",
price/100000,
price/10))


As
Fair
and
Ideal
have different ranges of values, I created two separate plots for
Fair
and
Ideal
to be able to assign the breaks in y axis

I want the two axes in the two plots to have the same number of decimals. I used
fmt_decimals()
from here.

library(dplyr)
library(ggplot2)
library(gridExtra)
library(grid)
#function to assign decimals for axes
fmt_dcimals <- function(decimals=0){
function(x) format(x,nsmall = decimals,scientific = FALSE)
}


f1 <-
ggplot(df[df$cut == "Fair",], aes(x=carat , y= new_price, color = color))+
geom_point(alpha = 0.3)+
scale_y_continuous( limits = c(0,0.20), breaks=c(0, 0.05,0.1,0.15,0.2), labels = fmt_dcimals(2))+
scale_x_continuous( limits = c(0,5.2), breaks=c(0,1,2,3,4,5), labels = fmt_dcimals(2))+
facet_wrap(~cut) +
labs(x = "",
y = "")


f2 <-
ggplot(df[df$cut == "Ideal",], aes(x=carat , y= new_price, color = color))+
geom_point(alpha = 0.3)+
scale_y_continuous( limits = c(0,2000), breaks=c(0, 250,500,750,1000, 1250, 1500, 1750, 2000), labels = fmt_dcimals(2))+
scale_x_continuous( limits = c(0,5.2), breaks=c(0,1,2,3,4,5), labels = fmt_dcimals(2))+
facet_wrap(~cut) +
labs(x = "",
y = "")


f <- gridExtra::arrangeGrob(f1,f2, ncol=1,
bottom=grid::textGrob(label= expression(Flow~(m^{3}~s^{-1})),
gp= gpar(fontsize=12, fontface="bold", col="black")),
left=grid::textGrob(label=expression(Concentration~mg~L^{-1}), rot=90,
gp= gpar(fontsize=12, fontface="bold", col="black")))

grid::grid.newpage()
grid::grid.draw(f)


RESULT
enter image description here

The top and bottom plots are not aligned along x axis.

I can fix this using
ggarrange()
from
egg
package

library(egg)
f_1 <- ggarrange( f1, f2)
f_1


enter image description here

Now the two plots are perfectly aligned.

QUESTIONS

Any suggestions to align the two plots along x axis using
gridExtra
?
If not
Can I add common labels for axes using
egg
?

and how can I add only one legend for the two plots?

UPDATE

Thanks to @eipi10

I updated it as below

library(cowplot)
# Function to extract legend
# https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
g_legend <-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend) }

# Extract legend as a grob
leg = g_legend(f1)

# Remove legend from plots
f1 = f1 + theme(legend.position = "none")
f2 = f2 + theme(legend.position = "none")

# Lay out the two
f_1 = plot_grid(f1, f2, ncol =1, align="v")

f_3 <- grid.arrange(
arrangeGrob(f_1,
bottom=grid::textGrob(label= expression(Flow~(m^{3}~s^{-1})),
gp= gpar(fontsize=12, fontface="bold", col="black")),
left=grid::textGrob(label=expression(Concentration~mg~L^{-1}), rot=90,
gp= gpar(fontsize=12, fontface="bold", col="black"))),
leg,
widths=c(9,1))


But it seems using
cowplot
, there is no vertical y line on the right side of the plot?

enter image description here

Answer

I think you can everything you want using only ggplot2, including a shared x-axis, shared axis titels and proper spacing. To me that seems easier than messing around with gtables.

This is a perfectly fine attempt:

ggplot(df[df$cut %in% c("Fair", "Ideal"),], aes(x=carat , y= new_price, color = color))+
  geom_point(alpha = 0.3)+
  scale_y_continuous(labels = fmt_dcimals(2))+
  scale_x_continuous(limits = c(0,5.2), breaks=c(0,1,2,3,4,5), labels = fmt_dcimals(2))+
  facet_wrap(~cut, scales = 'free_y', nrow = 2) +
  labs(x = "",
       y = "")

enter image description here

If you really want to have custom breaks, you can use a break function to supply those:

make_breaks <- function(ranges) {
  if(ranges[2] > 1) {
    c(0, 250,500,750,1000, 1250, 1500, 1750, 2000)
  } else {
    c(0, 0.05,0.1,0.15,0.2)
  }
}

ggplot(df[df$cut %in% c("Fair", "Ideal"),], aes(x=carat , y= new_price, color = color))+
  geom_point(alpha = 0.3)+
  scale_y_continuous(labels = fmt_dcimals(2), breaks = make_breaks)+
  scale_x_continuous(limits = c(0,5.2), breaks=c(0,1,2,3,4,5), labels = fmt_dcimals(2))+
  facet_wrap(~cut, scales = 'free_y', nrow = 2) +
  labs(x = "",
       y = "")

enter image description here