Consistent plotting panel width/height when using gridExtra - r

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.

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())

R: arrange.grid different plot sizes because of mathematical title notation

I'm new in designing plots and I've tried constructing barplots with:
bar <- ggplot(month1, aes(x=variable, y=value, fill=merge1)) + geom_bar(stat="identity")
bar_f <- bar + ggtitle("k = 0") + theme(axis.text=element_text(size=12, color="gray0"),
axis.title=element_blank())
+ scale_y_continuous(breaks= function(x) unique(floor(pretty(seq(0, (max(x) + 1) * 1.1)))))
+ scale_fill_manual(values= grp_colors, guide=F)
bar2 <- ggplot(month2, aes(x=variable, y=value, fill=merge2)) + geom_bar (stat="identity")
bar2_f <- bar2 + ggtitle(expression(k %in% group("[", "1;12", "]")))
+ theme(axis.text=element_text(size=12 , color="gray0"), axis.title=element_blank())
+ scale_y_continuous(limits=c(0,3), breaks=seq(3)) + scale_fill_manual(values= grp_colors, guide=F)
combine <- gridExtra::grid.arrange(bar_f, bar2_f, nrow=1, top= textGrob("Monatsdaten: Häufigkeiten", gp=gpar(fontsize=20,font=1)))
My problem is, that I've an mathematical notation in my bar2 plot, produced by
expression(k %in% group("[", "1;12", "]"))
If I would change the title to a "normal" one, the size of the two plots are equal. Obviously the 2nd plot is a bit smaller, caused by the mathematical title. I tried to define outside of the ggplot environment and inside of grid.arrange, but it doesn't work with the expression notation. Is there way to have the same size of the plot objects ?
And another (it's not my main question, but I didn't find an answer) question, can I enlarge the distance between my top title and the plot titles ?
Try the cowplot package
# some plots
p <- ggplot(iris, aes(Species, Sepal.Length)) +
stat_summary(geom="bar", fun.y="mean")
p1 <- p + ggtitle("k=0")
p2 <- p + ggtitle(expression(k %in% group("[", "1;12", "]")))
# the plot. Add horizontal alignment
cowplot::plot_grid(p1, p2, align = "h")
grid.arrange has a "heights" option where you can change the size of your plots relative to one another. You would add a term like
heights =c(1,1.1)
to your grid.arrange call to make the plotted area of your second plot slightly larger. See Specify widths and heights of plots with grid.arrange for some examples.

ggplot2, applying two scales to the same plot? Top down barplot

See plot here:
(from here)
How do I reproduce both the upper and lower portion of the barplot using ggplot2?
For example, I can produce the upper portion with
ggplot(data.frame(x=rnorm(1000, 5)), aes(x=x)) + geom_bar() + scale_y_reverse()
However now if I add any other geom_, such as another geom_bar() the scale for y is reversed. Is it possible to apply the scale_y_reverse() to only a specific geom_?
Another option is to make two separate plots and combine them with arrangeGrob from the gridExtra package. After playing with the plot margins, you can arrive at something that looks decent.
library(gridExtra)
library(ggplot2)
set.seed(100)
p2 <- ggplot(data.frame(x=rnorm(1000, 5)), aes(x=x)) + geom_bar() + theme(plot.margin=unit(c(0,0,0,0), 'lines'))
p1 <- p2 + scale_y_reverse() +
theme(plot.margin=unit(c(0, 0, -.8, 0), 'lines'), axis.title.x=element_blank(),
axis.text.x=element_blank(), axis.ticks.x=element_blank())
p <- arrangeGrob(p1, p2)
print(p)
ggplot only like to have one y-axis scale. The easiest thing would be to basically reshape your data yourself. Here we can use geom_rect to draw the data where ever we like and we can condition it on group time. Here's an example
#sample data
dd<-data.frame(
year=rep(2000:2014, 2),
group=rep(letters[1:2], each=15),
count=rpois(30, 20)
)
And now we can plot it. But first, let's define the offset to the top bars by finding the maxima height at a year and adding a bit of space
height <- ceiling(max(tapply(dd$count, dd$year, sum))*1.10)
And here's how we plot
ggplot(dd) +
geom_rect(aes(xmin=year-.4, xmax=year+.4,
ymin=ifelse(group=="a", 0, height-count),
ymax=ifelse(group=="a", count, height), fill=group)) +
scale_y_continuous(expand=c(0,0))
And that will give us

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))

ggplot2 facet margin

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.

Resources