ggplot2 and grid.arrange, place legend below arranged plots - r

I am facing a problem when I arrange 2 ggplots next to each other using grid.arrange().
I want to place a legend evenly below both plots.
So I have (pseudocode):
p1 <- ggplot(data = df, aes(group = name, color=as.factor(fac), y = ys, x= (xs))) +
geom_point() +
geom_line() +
theme(legend.position="bottom")
p2 <- ggplot(data = df, aes(group = name, color=as.factor(fac), y = ys, x= (xs))) +
geom_point() +
geom_line() +
grid.arrange(p1,p2, ncol=2, widths=c(0.9,1))
Is there any possibility to arrange the legend so that it is positioned evenly below both plots? If I do it as above, the legend is (logically) placed below the first plot.

With package patchwork you could try this...
library(ggplot2)
library(patchwork)
p1 <- ggplot(data = mtcars, aes(group = gear, color=as.factor(am), y = mpg, x = hp)) +
geom_point() +
geom_line()
p2 <- ggplot(data = mtcars, aes(group = gear, color=as.factor(am), y = mpg, x = hp)) +
geom_point() +
geom_line()
p1 + p2 +
plot_layout(guides = "collect") &
theme(legend.position='bottom')
Created on 2022-02-04 by the reprex package (v2.0.1)

Related

Patchwork legend collecting fails

I have been trying to collect legends of plots created in a loop. However, this fails to produce the desired result, as the legends are not collected. I tried
(res[[1]] / res[[2]]) + plot_layout(guides = "collect") + theme(legend.position = "bottom")
where res[[1]] and res[[2]] are list elements created in the loop.
Any advice would be much appreciated.
Resulting plot
Remove + theme(legend.position = "bottom"):
library(ggplot2)
library(patchwork)
p1 <- ggplot(data = mtcars, aes(x = disp, y = cyl, color = as.factor(vs))) +
geom_point()
p2 <- ggplot(data = mtcars, aes(x = disp, y = cyl, color = as.factor(vs))) +
geom_point()
p1 + p2 + plot_layout(guides = "collect")
Or if you'd like to place the legend in the center:
p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = 'bottom')

Controlling widths of many patchworked ggplots

I'm trying to make a graph with several ggplot2 graphs combined via patchwork.
I want first a shared y-axis for plot 1 and 3. Then plot 1 and 3 and at the end plot 2 and 4. This I have achieved with the help from #Allan Cameron - see the plot. Unfortunately I cannot control the width of plot 1,3,2 and 4. I would like plot 1 and 3 to be wider than plot 2 and 4. Also, for some reason the legend ends up in the middle of the plots. How can I put it all the way to the right?
Any ideas? All help is much appreaciated!
Here's the code:
mtcars
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars) +
geom_point(aes(disp, wt, colour = mpg)) +
ggtitle('Plot 1')
p2 <- ggplot(mtcars) +
geom_point(aes(carb, wt)) +
ggtitle('Plot 2')
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
p4 <- ggplot(mtcars) +
geom_area(aes(gear, carb)) +
ggtitle('Plot 4')
# Patchwork graph with shared y-axis
y_axis <- ggplot(data.frame(l = p1$labels$y, x = 1, y = 1)) +
geom_text(aes(x, y, label = l), angle = 90) +
theme_void() +
coord_cartesian(clip = "off")
p1$labels$y <- p2$labels$y <- " "
y_axis + (p1 + p2) / (p3 + p4) + plot_layout(widths = c(1, 15, 5), guides = "collect")
With regards to the widths issue, the nesting you do -for example (p1 + p1)- causes the nested objects to respond differently. Instead you can use the design argument in plot_layout() to achieve the same, but responsive to the widths.
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars) +
geom_point(aes(disp, wt, colour = mpg)) +
ggtitle('Plot 1')
p2 <- ggplot(mtcars) +
geom_point(aes(carb, wt)) +
ggtitle('Plot 2')
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
p4 <- ggplot(mtcars) +
geom_area(aes(gear, carb)) +
ggtitle('Plot 4')
# Patchwork graph with shared y-axis
y_axis <- ggplot(data.frame(l = p1$labels$y, x = 1, y = 1)) +
geom_text(aes(x, y, label = l), angle = 90) +
theme_void() +
coord_cartesian(clip = "off")
p1$labels$y <- p2$labels$y <- " "
y_axis + p1 + p2 + p3 + p4 +
plot_layout(widths = c(1, 15, 5),
guides = "collect",
design = "
123
145
")
Created on 2020-12-16 by the reprex package (v0.3.0)
Small note, you're deleting the y-axis lable of p2, whereas I think you meant to delete it from p3.
The patchwork can control the width of each part by iteral call the "plot_layout" in the sub-plot, just like :
patchwork <- (p1+p2)/(p3) + plot_layout(guides = "collect") while you want to make p1 wider than p2, you could try :
patchwork <- (p1+p2 + plot_layout(widths = c(2, 1), guides = "collect"))/(p3) + plot_layout(guides = "collect")

Merging two y-axes titles in patchwork

Any ideas as to how I can "merge" two identical y-axes titles into one, and then place this y-axis title in the middle between the plot? I have succeded in merging legends by using plot_layout(guides = "collect") but I cannot seem to find anything similar for axes. In this case I would merge the two axes titles called disp_disp_disp into one.
mtcars
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
labs(x = "mpg", y = "disp_disp_disp_disp_disp")
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
labs(x = "gear", y = "disp_disp_disp_disp_disp")
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
p1 / (p2 | p3)
I guess it would be slightly easier to strip out the y axis title before the plot is built then draw it back on after it is plotted:
library(ggplot2)
library(patchwork)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
labs(x = "mpg", y = "disp_disp_disp_disp_disp")
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
labs(x = "gear", y = "disp_disp_disp_disp_disp")
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
ylab <- p1$labels$y
p1$labels$y <- p2$labels$y <- " "
p1 / (p2 | p3)
grid::grid.draw(grid::textGrob(ylab, x = 0.02, rot = 90))
Another option if you want to avoid getting your hands dirty with grobs altogether is to specify a text-only ggplot and add that as your axis text:
p4 <- ggplot(data.frame(l = p1$labels$y, x = 1, y = 1)) +
geom_text(aes(x, y, label = l), angle = 90) +
theme_void() +
coord_cartesian(clip = "off")
p1$labels$y <- p2$labels$y <- " "
p4 + (p1 / (p2 | p3)) + plot_layout(widths = c(1, 25))
This behaves a bit better on resizing too.
The only way I could think of is to hack this at the gtable level, but I'd also be excited to learn more convenient ways. Here is the gtable method:
library(ggplot2)
library(patchwork)
library(grid)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
labs(x = "mpg", y = "disp_disp_disp_disp_disp")
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
labs(x = "gear", y = "disp_disp_disp_disp_disp")
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
p123 <- p1 / (p2 | p3)
# Convert to gtable
gt <- patchworkGrob(p123)
# Stretching one y-axis title
is_yaxis_title <- which(gt$layout$name == "ylab-l")
# Find new bottom position based on gtable::gtable_show_layout(gt)
gt$layout$b[is_yaxis_title] <- gt$layout$b[is_yaxis_title] + 18
# Deleting other y-axis title in sub-patchwork
is_patchwork <- which(gt$layout$name == "patchwork-table")
pw <- gt$grobs[[is_patchwork]]
pw <- gtable::gtable_filter(pw, "ylab-l", invert = TRUE)
# Set background to transparent
pw$grobs[[which(pw$layout$name == "background")[1]]]$gp$fill <- NA
# Putting sub-patchwork back into main patchwork
gt$grobs[[is_patchwork]] <- pw
# Render
grid.newpage(); grid.draw(gt)
Created on 2020-12-14 by the reprex package (v0.3.0)
Another way to do this with gridExtra.
library(ggplot2)
library(patchwork)
library(gridExtra)
p1 <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
labs(x = "mpg") +
theme(axis.title.y = element_blank())
p2 <- ggplot(mtcars) +
geom_boxplot(aes(gear, disp, group = gear)) +
labs(x = "gear") +
theme(axis.title.y = element_blank())
p3 <- ggplot(mtcars) +
geom_point(aes(hp, wt, colour = mpg)) +
ggtitle('Plot 3')
grid.arrange(patchworkGrob(p1 / (p2 | p3)), left = "disp_disp_disp_disp_disp")

chose colors for scale_colour_colorblind() in ggthemes

I would like to chose specific colors of the colorblind_pal() from ggthemes
This works:
library(ggplot2)
library(ggthemes)
p <- ggplot(mtcars) + geom_point(aes(x = wt, y = mpg,
colour = factor(gear))) + facet_wrap(~am)
p + theme_igray() + scale_colour_colorblind()
Now I would like to chose specific colors of the colorblind_pal() for my plot. How can I chose them?
I tried following with no success:
my_palette <- palette(c("#000000","#F0E442","#D55E00"))
p + theme_igray() + scale_colour_colorblind(my_palette)
You could use scale_color_manual in order to manually specify the colors to use:
library(ggplot2)
library(ggthemes)
p <- ggplot(mtcars) +
geom_point(aes(x = wt, y = mpg, colour = factor(gear))) +
facet_wrap(~am) +
theme_igray() +
scale_color_manual(values = c("#000000","#F0E442","#D55E00"))
p
Since you already have the colors, you can just use scale_color_manual:
library(ggthemes)
library(ggplot2)
COLS=colorblind_pal()(8)
COLS = COLS[c(1,5,7)]
p <- ggplot(mtcars) + geom_point(aes(x = wt, y = mpg,
colour = factor(gear))) + facet_wrap(~am)
p + theme_igray() + scale_colour_manual(values=COLS))

Combined bar plot and points in ggplot2

I would like to plot a "combined" bar plot with points.
Consider to following dummy data:
library(ggplot2)
library(gridExtra)
library(dplyr)
se <- function(x){sd(x)/sqrt(length(x))}
p1 <- ggplot(mtcars, aes(y=disp, x=cyl, fill=cyl))
p1 <- p1 + geom_point() + theme_classic() + ylim(c(0,500))
my_dat <- summarise(group_by(mtcars, cyl), my_mean=mean(disp),my_se=se(disp))
p2 <- ggplot(my_dat, aes(y=my_mean,x=cyl,ymin=my_mean-my_se,ymax=my_mean+my_se))
p2 <- p2 + geom_bar(stat="identity",width=0.75) + geom_errorbar(stat="identity",width=0.75) + theme_classic() + ylim(c(0,500))
The final plot should look like that:
You can add layers together, but if they have different data and/or aesthetics you'll want to include the data and aes arguments in each graphical layer.
p3 <- ggplot() +
geom_bar(data=my_dat, aes(y=my_mean,x=cyl,ymin=my_mean-my_se,ymax=my_mean+my_se), stat="identity", width = 0.75) +
geom_errorbar(data=my_dat, aes(y=my_mean,x=cyl,ymin=my_mean-my_se,ymax=my_mean+my_se), width = 0.75) +
geom_point(data=mtcars, aes(y=disp, x=cyl, fill=cyl)) +
ylim(c(0,500)) +
theme_classic()
If you want to make it so that the the points are off to the side of the bars, you could subtract an offset from the cyl values to move over the points. Like #LukeA mentioned, by changing the geom_point to geom_point(data=mtcars, aes(y=disp, x=cyl-.5, fill=cyl)).
You can specify each layer individually to ggplot2. Often you are using the same data frame and options for each geom, so it makes sense to set defaults in ggplot(). In your case you should specify each geom separately:
library(ggplot2)
library(gridExtra)
library(dplyr)
se <- function(x){sd(x)/sqrt(length(x))}
my_dat <- summarise(group_by(mtcars, cyl),
my_mean = mean(disp),
my_se = se(disp))
p1 <- ggplot() +
geom_bar(data = my_dat,
aes(y = my_mean, x = cyl,
ymin = my_mean - my_se,
ymax = my_mean + my_se), stat="identity", width=0.75) +
geom_errorbar(data = my_dat,
aes(y = my_mean, x = cyl,
ymin = my_mean - my_se,
ymax = my_mean + my_se), stat="identity", width=0.75) +
geom_point(data = mtcars, aes(y = disp, x = cyl, fill = cyl)) +
theme_classic() + ylim(c(0,500))
p1

Resources