ggplot2 facet margin - r

I use facet_wrap to plot some data. Here is an example:
library (ggplot2)
library (reshape)
# generate some dummy data
x = seq(0,1,0.05)
precision = sqrt(x)
recall = 1 - precision
fmeasure = 2 * (precision * recall) / (precision + recall)
# prepare it for plotting
df = data.frame(x=x, precision=precision, recall=recall, fmeasure=fmeasure)
df = melt(df, id.vars=c(x))
# plot it
p = ggplot(df, aes(x=x, y=value, group=variable))
p = p + geom_line() + facet_wrap(~variable, ncol=3)
p = p + coord_cartesian(xlim=c(0,1), ylim=c(0,1)) # second plot is without this line
print (p)
Figure 1: Plot for above code.
However, what you see in Figure 1 is that the first and last labels of consequent facets overlap. This could be fixed by increasing the space between facets. Other option is to remove xlim and ylim ranges as depicted in Figure 2, but this keeps unnecessary space in the facet itself.
Figure 2: Plot with line p = p + coord_cartesian(xlim=c(0,1), ylim=c(0,1)) removed.
I have tried to increase the space between the facets, but so far I have been unable to do it. Do you have any advice?
I use ggplot2 version 0.9.1 .

for 0.9.1 use: p + opts(panel.margin = unit(2, "lines")) but you have a lot of extra white space and IMO lose some of the effect of the faceting (note 0.9.2 now uses theme instead of opts)
Over the years the ggplot2 API has changed, as of 2018-02-01 this is the updated solution:
p + theme(panel.spacing = unit(2, "lines"))

Building upon Tyler's answer, you can further squeeze the facet panels together using the strip.text theme parameter as follows:
library(tidyverse)
mpgTidy <- pivot_longer(mpg, c(cty, hwy), names_to="mpg_categ", values_to="mpg")
g <- ggplot(mpgTidy, aes(x=displ, y=mpg, color=factor(cyl))) +
facet_wrap(~ mpg_categ) +
geom_point()
g
g + theme(strip.text=element_text(margin=margin()),
panel.spacing=unit(0, "lines"))
This can be useful when facet labels are long or include newlines and the faceted plot has both rows and columns.

Related

Adding a "//" on the x-axis to remove whitespace in one side of the ggplot panel plot

I'm hoping if there's a way to remove whitespace in one side of the panel plot (created by facet_wrap) by adding "//" on the x-axis. Below is sample data and code:
df <- data.frame(
condition = c("cond1","cond2","cond3"),
measure = c("type1","type2"),
value = rep(NA, 6)
)
# all type 1 measure values are between -0.5 and 0.5
# all type 2 measure values are between 0.5 and 2
df[df$measure=="type1",]$value <- runif(3, min=-0.5, max=0.5)
df[df$measure=="type2",]$value <- runif(3, min= 1.5, max=2.0)
# both panels should have same axis tick intervals
custom_breaks = function(x){
seq(round(min(x), 2), round(max(x), 2), 0.2)
}
# create a panel plot with vertical line at y=0 for both panels
ggplot(df, aes(x=condition, y=value, color=measure)) +
geom_point() +
geom_hline(aes(yintercept=0), color="grey") +
scale_y_continuous(breaks=custom_breaks) +
facet_wrap(~measure, scales="free_x") +
coord_flip() +
theme_bw() +
theme(panel.grid.major=element_blank(), panel.grid.minor=element_blank())
This code returns the below plot:
Because the values for type 2 (right panel) are far off from zero, adding a vertical line at y=0 results in lots of whitespace. I'm wondering if there's a way to put a "//" on the x-axis on the right panel after 0 and going straight to 1.5 so there aren't tons of wasted white space. Any help would be greatly appreciated!
Broken axes are generally discouraged because they can lead to misleading visualizations, so this is intentionally not implemented in ggplot2 (as answered by Hadley Wickham himself).
My preferred solutions for something like this are (a) facetting (which you are already doing) or (b) log transormation of the axis - but only if it makes sense for the given data.
Take this barchart for example (source / link to image): Since there is valuable information in the outliers (red circle and arrows) both log transformation and broken axes would distort the representation of reality. The package library(ggforce) has an implementation for such zoom facets with the facet_zoom() function.
Your scales = "free_x" is working just fine - the issue is that your geom_hline putting a line at 0 is included in both facets. Here's a way to include it only on the first facet.
ggplot(df, aes(x=condition, y=value, color=measure)) +
geom_point() +
geom_hline(data = data.frame(measure = "type1"), aes(yintercept=0), color="grey") +
scale_y_continuous(breaks=custom_breaks) +
facet_wrap(~measure, scales="free_x") +
coord_flip() +
theme_bw() +
theme(panel.grid.major=element_blank(), panel.grid.minor=element_blank())

Add significance lines outside/between facets

I wanted to add significant stars over 3 facets to compare them.
I google online but it is so complicated to add things outside plot. There is a ggsignif package but it does nothing to facets (https://github.com/const-ae/ggsignif/issues/22). It seems possible using gridExtra but I cannot make it.
The stars can be draw easily in a single plot, not facets. But I have to use facets to have separate rugs on the left. If you know how to have separate rugs inside a single plot, it should also solve the problem.
Here is the code and plot I want to add things on:
library(ggplot2)
ToothGrowth$dose = factor(ToothGrowth$dose)
ggplot(ToothGrowth, aes(x='', y=len, color=dose)) +
geom_boxplot() +
geom_rug(sides="l") +
facet_grid(. ~ dose)
What I want is:
Sorry for the drawing. The line width should be the same. The final result should be really similar to this but for facets:
This is a workaround - plot two plots (one for significance annotation, another for boxplots).
library(ggplot2)
library(ggsignif)
ToothGrowth$dose <- factor(ToothGrowth$dose)
Plot significance annotation. Don't use boxplot here and set tips to 0 (using only one comparison here as others return error from statistical test, but I'm assuming that this is only an example dataset).
p1 <- ggplot(ToothGrowth, aes(as.factor(dose), len)) +
geom_signif(comparisons = list(c("1", "2")), tip_length = 0.005) +
coord_cartesian(ylim = c(35, 35.5)) +
theme_void()
Plot boxplots with different x axis (need this to specify comparisons groups in ggsignif)
p2 <- ggplot(ToothGrowth, aes(factor(dose), len)) +
geom_boxplot() +
geom_rug(sides = "l") +
facet_grid(. ~ dose, scales = "free_x") +
labs(x = NULL) +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank())
Draw plots together geom_signif on-top of geom_boxplot with facet_wrap
egg::ggarrange(p1, p2, heights = c(2, 10))

Smart association of graphs where one is faceted - ggplot2

I want to combine these two graphs :
p1 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
facet_wrap(~ Species)
p2 <- ggplot(iris, aes(Sepal.Length)) +
geom_density()
To combine, I do :
multiplot(p1, p2, cols = 2)
But it is not the desired shape.
I would like the graph p2 has the same dimensions than others and is situated just next to the last faceted graph.
Thanks for help
Not sure if this is applicable in you generic case, but with facet_grid instead of facet_wrap, you can use the margins argument:
library(ggplot2)
ggplot(iris, aes(Sepal.Length)) +
geom_density() +
facet_grid(. ~ Species, margins = T)
If you question is more generic the answer probably lies in grid.arrange.
Something like this could be a start:
library(gridExtra)
grid.arrange(arrangeGrob(p1, p2,
widths = c(3,1),
heights = c(1,20),
layout_matrix = matrix(c(1,1,NA,2),2)))
As you can see there are several problems (different axes, top strip), but working with grid could gets complicated quickly.
This code should work:
p1 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
ylim(limits = c(0, 1.25))+
facet_wrap(~ Species)
p2 <- ggplot(iris, aes(Sepal.Length)) +
geom_density() +
ggtitle("") + # ad empty title as place holder
labs(y = "", x = "") + # hide axis labels
ylim(limits = c(0, 1.25)) + # y axis values should be fixed in both plots
coord_fixed(ratio=20/1) + # ratio of x- and y-axis to reduce width of plot
theme(axis.ticks.y = element_blank(), axis.text.y = element_blank(), axis.line.y = element_blank(),
plot.margin=unit(c(0,0,0.65,-10), "lines")) # margin between plots = "0.65"
I fiddled a bit and used just different styling options to produce this result. If you have more plots than this one I would recommend to use one theme for all.
You can use either the multiplot function that you are already using
multiplot(p1, p2, cols = 2)
or you install the packages gridExtra and grid and use that one:
grid.arrange(p1, p2, ncol=2)
Hope this helps!

Consistent plotting panel width/height when using gridExtra

I'm trying to construct a 5 x 6 matrix of plots in R using ggplot2 and gridExtra. For simplicity, I can show my issue with a 2 x 2 matrix and some fake data.
#Load libraries
library(ggplot2); library(gridExtra)
#Data
data = rbind(data.frame(x=rnorm(100,0,1),ALP='A',NUM=1),data.frame(x=rnorm(100,20000,1000),ALP='A',NUM=2),data.frame(x=rnorm(100,100,10),ALP='B',NUM=1),data.frame(x=rnorm(5000,1000),ALP='B',NUM=2))
#Ggplot2 facet_grid
ggplot(data,aes(x=x,y=..scaled..,fill='red')) + geom_density() + facet_grid(ALP~NUM,scales='free') + guides(fill=FALSE)
The result doesn't look good, as the x-scale is so different across the faceting labels. I tried to do it manually with gridExtra.
#Assemble grobs
plt1 = ggplot(subset(data,ALP=='A'&NUM==1),aes(x=x,y=..scaled..,fill=ALP)) + geom_density() + facet_grid(.~NUM,scales='free') + guides(fill=FALSE) + theme(axis.title.x=element_blank(),axis.title.y=element_blank())
plt2 = ggplot(subset(data,ALP=='A'&NUM==2),aes(x=x,y=..scaled..,fill=ALP)) + geom_density() + facet_grid(ALP~NUM,scales='free') + guides(fill=FALSE) + theme(axis.text.y=element_blank(),axis.ticks.y=element_blank(),axis.title.y=element_blank(),axis.title.x=element_blank())
plt3 = ggplot(subset(data,ALP=='B'&NUM==1),aes(x=x,y=..scaled..,fill=ALP)) + geom_density() + guides(fill=FALSE) + theme(axis.title.x=element_blank(),axis.title.y=element_blank())
plt4 = ggplot(subset(data,ALP=='B'&NUM==2),aes(x=x,y=..scaled..,fill=ALP)) + geom_density() + facet_grid(ALP~.,scales='free') + guides(fill=FALSE) + theme(axis.text.y=element_blank(),axis.ticks.y=element_blank(),axis.title.y=element_blank(),axis.title.x=element_blank())
#Plot it out
grid.arrange(plt1,plt2,plt3,plt4,nrow=2,ncol=2,left=textGrob("scaled",rot=90,vjust=1),bottom=textGrob("x"))
I'm almost there, unfortunately the plotting panel (x,y) in the upper, right-hand corner is smaller than all the rest. Similarly, the plotting panel (x,y) in the lower, left-hand corner is bigger than all the rest. I would like all of the plotting panels (x,y) to be the same height/width. I found some code using gtable, but it only seems to work consistently when the grobs don't have facet labels. The effect is even more exaggerated when the number of rows/columns increases.
as an alternative to facetting, you could work with gtable,
plt <- lapply(list(plt1,plt2, plt3,plt4), ggplotGrob)
left <- rbind(plt[[1]], plt[[3]])
right <- rbind(plt[[2]], plt[[4]])
all <- cbind(left, right)
grid.newpage()
grid.draw(all)
the panel sizes should all be equal (1null) with this layout.

How to use scale from previous plot in current plot with ggplot2?

I am using ggplot2 to produce a plot that has 3 facets. Because I am comparing two different data sets, I would like to then be able to plot a second data set using the same y scale for the facets as in the first plot. However, I cannot find a simple way to save the settings of the first plot to then re-use them with the second plot. Since each facet has its own y scale, it will be a pain to specify them by hand for the second plot. Does anyone know of a quick way of re-using scales? To make this concrete, here is how I am generating first my plot:
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p + facet_wrap(~ cyl, scales = "free_y")
EDIT
When applying one of the suggestions below, I found out that my problem was more specific than described in the original post, and it had to do specifically with scaling of the error bars. Concretely, the error bars look weird when I rescale the second plot as suggested. Does anyone have any suggestions on how to keep the same scale for both plots and dtill display the error bars correctly? I am attaching example below for concreteness:
#Create sample data
d1 <- data.frame(fixtype=c('ff','ff','fp','fp'), detype=c('det','pro','det','pro'),
diffscore=c(-1,-15,3,-17),se=c(2,3,1,2))
d2 <- data.frame(fixtype=c('ff','ff','fp','fp'), detype=c('det','pro','det','pro'),
diffscore=c(-1,-3,-2,-1),se=c(4,3,5,3))
#Plot for data frame 1, this is the scale I want to keep
lim_d1 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_point(aes(size=1), shape=15) +
geom_errorbar(lim_d1, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#Plot for data frame 2 original scale
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d2, aes(colour=detype, y=diffscore, x=detype)) +
geom_point(aes(size=1), shape=15) +
geom_errorbar(lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#Plot for data frame 2 adjusted scale. This is where things go wrong!
#As suggested below, first I plot the first plot, then I draw a blank screen and try
#to plot the second data frame on top.
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_blank() +
geom_point(data=d2, aes(size=1), shape=15) +
geom_errorbar(lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#If the error bars are fixed, by adding data=d2 to geom_errorbar(), then
#the error bars are displayed correctly but the scale gets distorted again
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_blank() +
geom_point(data=d2, aes(size=1), shape=15) +
geom_errorbar(data=d2,lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
You may first call ggplot on your original data where you add a geom_blank as a first layer. This sets up a plot area, with axes and legends based on the data provided in ggplot.
Then add geoms which use data other than the original data. In the example, I use a simple subset of the original data.
From ?geom_blank: "The blank geom draws nothing, but can be a useful way of ensuring common scales between different plots.".
ggplot(data = mtcars, aes(mpg, wt)) +
geom_blank() +
geom_point(data = subset(mtcars, wt < 3)) +
facet_wrap(~ cyl, scales = "free_y")
Here is an ugly hack that assumes you have an identical facetting layout in both plots.
It replaces the panel element of the ggplot build.
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p1 <- p + facet_wrap(~ cyl, scales = "free_y") + labs(title = 'original')
# create "other" data.frame
n <- nrow(mtcars)
set.seed(201405)
mtcars2 <- mtcars[sample(seq_len(n ),n-15),]
# create this second plot
p2 <- p1 %+% mtcars2 + labs(title = 'new data')
# and a copy so we can attempt to fix
p3 <- p2 + labs(title = 'new data original scale')
# use ggplot_build to construct the plots for rendering
p1b <- ggplot_build(p1)
p3b <- ggplot_build(p3)
# replace the 'panel' information in plot 2 with that
# from plot 1
p3b[['panel']] <- p1b[['panel']]
# render the revised plot
# for comparison
library(gridExtra)
grid.arrange(p1 , p2, ggplot_gtable(p3b))

Resources