Using diamonds
, I want to plot carat
vs price
for 4 levels (Fair
, Good
, Very Good
and Pre
In addition to using functions from the gridExtra
package (as suggested by @user20650), you can also create your plots with less code by splitting the diamonds
data frame by levels of cut
and using mapply
.
The answer below also includes solutions for follow-up questions in the comments. We show how to lay out the four plots, add single x and y labels (including making them bold and controlling their color and size) that apply to all the plots, and get a single legend rather than a separate legend for each plot.
library(ggplot2)
library(gridExtra)
library(grid)
library(scales)
Remove rows where cut
is "Ideal"
:
dat = diamonds[diamonds$cut != "Ideal",]
dat$cut = droplevels(dat$cut)
Create four plots, one for each remaining level of cut
and store in a list. We use mapply
(instead of lapply
) so that we can provide both separate data frames for each level of cut
and a vector of custom ymax
values to set the highest value on the y-axis separately for each plot. We also add color=clarity
in order to create a color legend:
pl = mapply(FUN = function(df, ymax) {
ggplot(df, aes(carat, price, color=clarity))+
geom_point()+
facet_wrap(~cut, ncol=2)+
scale_x_continuous(limits = c(0,4), breaks=0:4)+
scale_y_continuous(limits = c(0, ymax), labels=dollar_format()) +
labs(x=expression(" "),
y=expression(" "))
}, df=split(dat, dat$cut), ymax=c(1e4,5e3,1e3,3e3), SIMPLIFY=FALSE)
Okay, we have our four plots, but each one has its own legend. So now we want to arrange to have only one overall legend. We do this by extracting one of the legends as a separate grob (graphical object) and then removing the legends from the four plots.
Extract the legend as a separate grob using a small helper function:
# 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(pl[[1]])
Now we need to arrange the four plots in a 2x2 grid and then place the legend to the right of this grid. We use arrangeGrob
to lay out the plots (and note how we use lapply
to remove the legend from each plot before rendering it). This is essentially the same as what we did with grid.arrange
in an earlier version of this answer, except that arrangeGrob
creates the 2x2 plot grid object without drawing it. Then we lay out the legend beside the 2x2 plot grid by wrapping the whole thing inside grid.arrange
. widths=c(9,1)
allocates 90% of the horizontal space to the 2x2 grid of plots and 10% to the legend. Whew!
grid.arrange(
arrangeGrob(grobs=lapply(pl, function(p) p + guides(colour=FALSE)), ncol=2,
bottom=textGrob("Carat", gp=gpar(fontface="bold", col="red", fontsize=15)),
left=textGrob("Price", gp=gpar(fontface="bold", col="blue", fontsize=15), rot=90)),
leg,
widths=c(9,1)
)