Label individual panels in a multi-panel ggplot2 - r

I'm interested in trying to create simple corner labels for a multipanel figure I am preparing in ggplot. This is similar to this previously asked question, but the answers only explained how to include a label at the top of the plot, not produce a corner label in the format required by many journals. I hope to replicate something similar to the plotrix function corner.label() in ggplot2.
Here is an example using plottrix of what I would like to recreate in ggplot2.
require(plotrix)
foo1<-rnorm(50,25,5)
foo2<-rpois(50,25)
foo3<-rbinom(50,25,0.5)
foo4<-rnbinom(50,25,0.5)
par(mfrow=c(2,2))
hist(foo1)
corner.label(label='a',figcorner=T)
hist(foo2)
corner.label(label='b',figcorner=T)
hist(foo3)
corner.label(label='c',figcorner=T)
hist(foo4)
corner.label(label='d',figcorner=T)
This produces the following:
Thanks for any help in advance!

Two recent changes have made this a lot easier:
The latest release of ggplot2 has added the tag caption which can be used to label subplots.
The package patchwork makes it really easy to plot multiple ggplot objects. https://github.com/thomasp85/patchwork
This means that no altering of grobs is required. Adapting the reproducible example provided by Kev:
library(ggplot2)
# install.package("patchwork")
library(patchwork)
a <- 1:20
b <- sample(a, 20)
c <- sample(b, 20)
d <- sample(c, 20)
mydata <- data.frame(a, b, c, d)
myplot1 <- ggplot(mydata, aes(x=a, y=b)) + geom_point() + labs(tag = "A")
myplot2 <- ggplot(mydata, aes(x=b, y=c)) + geom_point() + labs(tag = "B")
myplot3 <- ggplot(mydata, aes(x=c, y=d)) + geom_point() + labs(tag = "C")
myplot4 <- ggplot(mydata, aes(x=d, y=a)) + geom_point() + labs(tag = "D")
myplot1 + myplot2 + myplot3 + myplot4
Extension: Changing Style:
If you want to change the labelling style, you can either set this individually for each plot or set a theme default. I would recommend the second approach. Add the following line before you build your plots to make the font bold and blue
ggplot2::theme_update(plot.tag = element_text(face = "bold", colour = "blue"))
For more information on customising the theme of ggplot2, see here.

I had the same problem and came up with the following solution, which is a bit different:
loading r packages
library(ggplot2)
library(grid)
library(gridExtra)
example data
a <- 1:20
b <- sample(a, 20)
c <- sample(b, 20)
d <- sample(c, 20)
create a data frame
mydata <- data.frame(a, b, c, d)
create example plots
myplot1 <- ggplot(mydata, aes(x=a, y=b)) + geom_point()
myplot2 <- ggplot(mydata, aes(x=b, y=c)) + geom_point()
myplot3 <- ggplot(mydata, aes(x=c, y=d)) + geom_point()
myplot4 <- ggplot(mydata, aes(x=d, y=a)) + geom_point()
set corner labels
myplot1 <- arrangeGrob(myplot1, top = textGrob("A", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot2 <- arrangeGrob(myplot2, top = textGrob("B", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot3 <- arrangeGrob(myplot3, top = textGrob("C", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot4 <- arrangeGrob(myplot4, top = textGrob("D", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
plotting all plots on one page
grid.arrange(myplot1, myplot2, myplot3, myplot4, ncol = 2)

An example:
d <- data.frame(x = runif(16),
y = runif(16),
grp = rep(letters[1:4],each = 4))
ggplot(d,aes(x = x,y = y)) +
facet_wrap(~grp) +
geom_point() +
theme(strip.text = element_text(hjust = -0.05),
strip.background = element_blank())

Here's a solution using a custom labeller function. This doesn't invovle any manipulations to the data. Currently it only works with 1-dimensional facets (facet_wrap). I'm still working on how to increment along a 2-D grid...
Define the labeller function
make_labelstring <- function(mypanels) {
mylabels <- sapply(mypanels,
function(x) {LETTERS[which(mypanels == x)]})
return(mylabels)
}
label_panels <- ggplot2::as_labeller(make_labelstring)
Pass label_panels as the labeller to facet_wrap
library(ggplot2)
data("diamonds")
# create a faceted plot
ggplot(data = diamonds, aes(x = depth, y = price)) +
geom_point() +
facet_wrap(~cut, labeller = label_panels) +
theme(strip.text = element_text(hjust = -0),
strip.background = element_blank())

Related

Decrease margins between plots when using cowplot

I would like to combine some graphs together using cowplot. But I cannot change the margin sizes. I want to use only one y-axes, but than the margin is still quite large, which I want to decrease. I have used the plot.margin code from ggplot, although that works when I look at the single plot, it doesn't seem to work when the plots are combined.
I have made some example code:
library(ggplot2)
library(cowplot)
x <- c("a", "b")
y1 <- c(3,6)
y2 <- c(10,15)
data1 <- data.frame(x,y1)
data2 <- data.frame(x, y2)
ylab1 <- ylab("Very nice y values")
xlab1 <- xlab("Very nice factors")
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,0.5), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0.5,0.5,0.5,-0.5), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3 # Quite large margin between the two plots
I am aware that I could avoid this problem by using facets, however my real plot is rather more complicated than this graph.
Increasing the space between plots in plot_grid was also addressed in this issue.
An extra interesting solution is the one suggested in this comment - try to add an extra empty plot between the two plots and adjust the relative columns widths:
plot4 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, 0, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot4
Can even try negative values in rel_widths, which gives better results:
plot5 <- plot_grid(plot1, NULL, plot2, rel_widths = c(1, -0.1, 1), align = "hv",
labels = c("A", "B"), nrow = 1)
plot5
So, try a combination of adjusting the plot.margin (as answered by #J.Con) and adding an extra empty plot with tweaking rel_widths.
EDIT 2019-12-11
Also check out this comment of the author of cowplot (Claus Wilke):
For those kinds of problems I would now recommend the patchwork library. It's inherently difficult with plot_grid(), due to its underlying design
So, a fast example with patchwork based on their vignette Adding Annotation and Style goes like this:
library(patchwork)
plot3 <- plot1 + plot2 +
plot_annotation(tag_levels = 'A') &
theme(plot.tag = element_text(size = 8))
plot3
Created on 2019-12-11 by the reprex package (v0.3.0)
Your plot.margins were actually working against you. Set them to zero to fill up that white space.
plot1 <- ggplot(data1, aes(x=x, y = y1)) +
geom_bar(stat ="identity", position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab1 + ylab1
plot1
ylab2 <- ylab("")
xlab2 <- xlab("Very nice factors")
plot2 <- ggplot(data2, aes(x=x, y = y2)) +
geom_bar(stat = "identity",position=position_dodge(), fill = "grey")+
theme(plot.margin = unit(c(0,0,0,0), "cm")) + xlab2 + ylab2
plot2
plot3 <- plot_grid(plot1, plot2, labels = c("A", "B"), align = "hv",nrow = 1, ncol = 2)
plot3

Specify plot height in plot_grid with 'hv' aligment: cowplot

I have been using the plot_grid command from cowplot to arrange my plots. I use the labeling feature, and my plots all look the same in that regard. However, when I 'hv' align some plots that have very different y-axis limits, such as the one below, it appears the height of the plot with shortest range of y is used.
If I just 'v' align the plot it looks better in some respects, but it is hard to resize the plot and have the labels looking good. I'd prefer the plot height not consider the x-axis labels, etc, like above.
Using gtables, I can get the desired width/height (below), but these leaves me without the consistent labels across all the figures in a document. Can I use the 'hv' alignment with cowplot and specify which plot height to use?
library(ggplot2)
library(dplyr)
library(scales)
library(grid)
library(cowplot)
data(iris)
iris <- iris %>% mutate(Petal.Width2 = ifelse(Species == "setosa", Petal.Width * 75, Petal.Width))
p1 <- ggplot(data=iris, aes(x = factor(Species), y=Sepal.Width)) +
geom_bar(stat="identity") +
labs(x = NULL, y = "Plot One") +
scale_y_continuous(labels = percent) +
theme(axis.text.x = element_blank(),
axis.title.y = element_text(vjust=1), plot.margin=unit(c(2,2,0,2),"mm"))
p2 <- ggplot(data=iris, aes(x = factor(Species), y=Petal.Width2)) + geom_bar(stat="identity") +
labs(x = NULL, y = "Plot Two") +
scale_y_continuous(labels = percent) +
theme(axis.text.x = element_blank(),
axis.title.y = element_text(vjust=1), plot.margin=unit(c(0,2,0,2),"mm"))
p3 <- ggplot(data=iris, aes(x = factor(Species), y=Petal.Length*0+.01)) + geom_bar(stat="identity") +
labs(x = "SPECIES", y = "The Third plot") +
scale_y_continuous(labels = percent) +
theme( axis.title.y = element_text(vjust=1, color="blue"), plot.margin=unit(c(0,2,0,2),"mm"),
axis.text.x = element_text(angle = 90, hjust=1, vjust=1,face ="italic", size=10))
plot_grid(p1,p2,p3,ncol=1, align="v", labels=c("A", "B", "C"))
# https://stackoverflow.com/a/27408589/1670053
plots <- list(p1, p2, p3)
grobs = lapply(plots, ggplotGrob)
g = do.call(rbind, c(grobs, size="first"))
g$widths = do.call(unit.pmax, lapply(grobs, "[[", "widths"))
grid.newpage()
grid.draw(g)
it's easy as to add labels,
plots <- list(p1, p2, p3)
grobs = lapply(plots, ggplotGrob)
library(gridExtra)
g = do.call(rbind, grobs) # uses gridExtra::rbind.gtable
panels <- g$layout[g$layout$name=="panel",]
g <- gtable::gtable_add_grob(g, lapply(LETTERS[1:nrow(panels)],
textGrob, vjust=1, y=1,
gp=gpar(fontface=2)),
t=panels$t, l=2)
grid.newpage()
grid.draw(g)

problems with arrangeGrob under R version 3.2.2

I 've updated my R version including all packages and the function arrangeGrob (Package gridExtra) has changed.
On my old version R version 3.1.3 I used it as the following to make corner labels:
loading r packages
library(ggplot2)
library(grid)
library(gridExtra)
example data
a <- 1:20
b <- sample(a, 20)
c <- sample(b, 20)
d <- sample(c, 20)
create a data frame
mydata <- data.frame(a, b, c, d)
create example plots
myplot1 <- ggplot(mydata, aes(x=a, y=b)) + geom_point()
myplot2 <- ggplot(mydata, aes(x=b, y=c)) + geom_point()
myplot3 <- ggplot(mydata, aes(x=c, y=d)) + geom_point()
myplot4 <- ggplot(mydata, aes(x=d, y=a)) + geom_point()
set corner labels
myplot1 <- arrangeGrob(myplot1, main = textGrob("A", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot2 <- arrangeGrob(myplot2, main = textGrob("B", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot3 <- arrangeGrob(myplot3, main = textGrob("C", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
myplot4 <- arrangeGrob(myplot4, main = textGrob("D", x = unit(0, "npc")
, y = unit(1, "npc"), just=c("left","top"),
gp=gpar(col="black", fontsize=18, fontfamily="Times Roman")))
grid.arrange(myplot1, myplot2, myplot3, myplot4)
and I got the following plot, which was fine:
but under the new R version 3.2.2 the image looks like this:
arrangeGrob opens for every textGrob a new image and I got eight images on one page instead of four. How can I fixed it that the plot looks like in the old version of R and gridExtra?
From Kev's comment:
There has been a rewrite of gridExtra, that is not (fully) backward
compatible - may be the issue. Have a look at the new wiki
cran.r-project.org/web/packages/gridExtra/vignettes/… . Try changing
main to top – user20650

"Unitless", qualitative, or relative axis scales ggplot2

Suppose I have the following chart
dat <- data.frame(x = 1:10, y = 1:10)
ggplot(dat, aes(x=x, y=y)) + geom_point()
but I'm not actually interested in the values 2.5, 5, 7.5, 10 on either axis. All I want to mark is something like "Low to High". I know I can mark + xlab("Low to High") but I would prefer "Low" to be at the far left of the axis (under the origin) and "High" to be at the far right (under 10.0) with perhaps an arrow from Low ---> High. I could specify the breaks manually, but that seems too much of a kludge.
Something like this might help,
dat <- data.frame(x = 1:10, y = 1:10)
p <- ggplot(dat, aes(x=x, y=y)) + geom_point() +
scale_x_continuous('', breaks=NULL)+
scale_y_continuous('', breaks=NULL)
g <- ggplotGrob(p)
library(gtable)
library(grid)
my_axis <- function(low="low", high="high", axis=c("x", "y"), ...){
axis <- match.arg(axis)
if(axis == "x"){
g1 <- textGrob(low, x=unit(0,"npc"), hjust=0)
g3 <- textGrob(high, x=unit(1,"npc"), hjust=1)
g2 <- segmentsGrob(grobWidth(g1) + unit(2,"mm"), unit(0.5,"npc"),
unit(1,"npc") - grobWidth(g3)- unit(2,"mm"),
unit(0.5,"npc"), ...)
} else if(axis == "y"){
g1 <- textGrob(low, y=unit(0,"npc"), rot=90, hjust=0)
g3 <- textGrob(high, y=unit(1,"npc"), rot=90, hjust=1)
g2 <- segmentsGrob(unit(0.5,"npc"),grobHeight(g1) + unit(2,"mm"),
unit(0.5,"npc"),
unit(1,"npc") - grobHeight(g3)- unit(2,"mm"),
...)
}
grobTree(g1,g2,g3)
}
g <- gtable_add_grob(g, my_axis(arrow=arrow(length=unit(2,"mm"))),
t=nrow(g)-2, b=nrow(g)-1, l=4)
g <- gtable_add_grob(g, my_axis(axis="y", arrow=arrow(length=unit(2,"mm"))),
t=3, l=1,r=3)
grid.newpage()
grid.draw(g)
As an alternative, and since you did ask for "Low ---> High", here's an option that doesn't involve disassembling the plot
dat <- data.frame(x = 1:10, y = 1:10)
low_to_high <- paste0("low ", paste0(rep("-", 35), collapse = ""), "> high")
library(ggplot2)
ggplot(dat, aes(x, y)) +
geom_point() +
labs(x = low_to_high, y = low_to_high) +
coord_equal() +
theme_bw() +
theme(axis.title = element_text(size=20),
axis.text = element_blank(),
axis.ticks = element_blank())
Admittedly, the number of - required will vary and needs to be adjusted per your plot size, and it's not as pretty as the properly rendered arrow. Still, this is quick to make from scratch.

Top to bottom alignment of two ggplot2 figures

I realize that the align.plots function from the ggExtra package has been deprecated and removed. However, I am using my own version as it seems to provide the specific functionality I need. I have looked into faceting to solve my problem but I don't think it will work for my particular issue. What seems to be the problem is that the top-to-bottom images don't align when I use coord_equal on one of them. This doesn't seem to affect left-to-right though. Here is a simplified (or at least as simple as I can make it) version of what I am trying to achieve.
Create some dummy data frames:
source('https://raw.github.com/jbryer/multilevelPSA/master/r/align.R')
require(psych)
df = data.frame(x=rnorm(100, mean=50, sd=10),
y=rnorm(100, mean=48, sd=10),
group=rep(letters[1:10], 10))
dfx = describe.by(df$x, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfx) = c('group', 'x', 'x.n', 'x.min', 'x.max')
dfy = describe.by(df$y, df$group, mat=TRUE)[,c('group1', 'mean', 'n', 'min', 'max')]
names(dfy) = c('group', 'y', 'y.n', 'y.min', 'y.max')
df2 = cbind(dfx, dfy[,2:ncol(dfy)])
range = c(0,100)
This will setup the three plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
opts(legend.position='none') +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1)
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + opts(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + opts(legend.position='none')
The alignment top to bottom does not work with coord_equal
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Broken Plot http://bryer.org/alignplots1.png
The fix is to add respect=TRUE to the grid.layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)
But if I don't use coord_equal the alignment works fine:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1))
grid.newpage()
pushViewport( viewport( layout=grid_layout, width=1, height=1 ) )
align.plots(grid_layout, list(p1a, 1, 2), list(p3, 1, 1), list(p2, 2, 2))
Working Plot http://bryer.org/alignplots2.png
ggplot2 now has ggplotGrob(), which may help with this.
First, we need to update the code used to generate the plots:
p1a = ggplot(df2, aes(x=x, y=y, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + scale_y_continuous(limits=range)
p1 = p1a + coord_equal(ratio=1) + theme_minimal() + theme(legend.position='none')
p2 = ggplot(df, aes(x=x, y=group, colour=group)) + geom_point() +
scale_x_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p3 = ggplot(df, aes(x=group, y=y, colour=group)) + geom_point() +
scale_y_continuous(limits=range) + theme_minimal() + theme(legend.position='none')
p4 <- ggplot(df, aes(x = group, y = y)) +
geom_blank() +
theme(line = element_blank(),
rect = element_blank(),
text = element_blank(),
title = element_blank())
p4 will be blank; we just need the grob to draw.
Then we load the grid package and draw the grobs in a list arranged in rows and columns using cbind() and rbind().
library(grid)
grid.newpage()
grid.draw(
cbind(
rbind(ggplotGrob(p3), ggplotGrob(p4), size = "first"),
rbind(ggplotGrob(p1), ggplotGrob(p2), size = "first"),
size = "first"))
I'm not sure if this method will let you plot p3 in a different width and p2 in a different height, as you have them in the original example; I normally need a grid of similarly-sized graphs, and haven't needed to figure out different sizes.
This answer is partially based on partially based on https://stackoverflow.com/a/17463184/393354
Here is an example:
m <- matrix(c(3, 1, 0, 2), 2, byrow = T)
lay <- gglayout(m, widths = c(1, 3), heights = c(3, 1))
ggtable(p1, p2, p3, layout = lay)
you can use this by
install.packages('devtools')
library(devtools)
dev_mode()
install_github("ggplot2", "kohske", "cutting-edge")
library(ggplot2)
note that this branch is experimental, so maybe there are bugs.
To solve the problem using the align.plots method, specify respect=TRUE on the layout call:
grid_layout <- grid.layout(nrow=2, ncol=2, widths=c(1,2), heights=c(2,1), respect=TRUE)

Resources