How to arrange plots with shared axes? - r

I am trying to create an arrangement of three scatterplots with shared axes and marginal histograms. This seems like it should be simple, but it's giving me fits. I have tried approaches with gridExtra and gtable, both of which have gotten the general arrangement like I want but with alignments and plot sizes that were off.
There are many other posts related to this question and I have experimented with the answers given to many of them, especially answers from #baptiste here and here. The latter's approach to controlling width might be the key to the alignments, but I haven't understood it well enough to adapt it to my problem.
A minimal working example follows, preceded by its result. The result has quite a few problems:
The bottom right plot is bigger than the other two. This has been an issue with all approaches, including grid.arrange(), with which I placed a blank rectangle grob to balance the scatter plots.
The sizes of the maringal histograms are way off and not what I would expect from the code, which is that their narrow dimension would be about 1/4 the size of the scatter plots' width.
The spacing of the plots is too wide.
None of the marginal histograms align correctly with the scatter plots.
The marginal histogram on the right (c) is so narrow that it doesn't show up at all in the png, yet it does show up (though still too narrow) in the RStudio plot viewer, though only when "zoomed".
Bonus points for an answer that will align the plots' axes correctly even if the width of the axes labels varies between plots as such would be much more adaptable for future problems.
library(ggplot2)
library(grid)
library(gtable)
data <- data.frame(a = rnorm(100, 30, 3), b = rnorm(100, 40, 5), c=rnorm(100, 50, 3))
b_a.scatter <- ggplot(data, aes(x=a, y=b)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
plot.margin = unit(c(1,0,-0.5,1), "cm")
)
c_a.scatter <- ggplot(data, aes(x=a, y=c)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(plot.margin = unit(c(-0.5,0,0.5,1), "cm"))
c_b.scatter <- ggplot(data, aes(x=b, y=c)) + geom_point() + coord_equal(ratio=1, xlim=c(0,100), ylim=c(0,100)) +
theme(
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(-0.5,0,0.5,0), "cm")
)
a.hist <- ggplot(data, aes(x=a)) + geom_histogram() + coord_equal(xlim=c(0,100), ratio=1/4) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(0,0,1,1), "cm")
)
b.hist <- ggplot(data, aes(x=b)) + geom_histogram() + coord_equal(xlim=c(0,100), ratio=1/4) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = unit(c(0,0,1,0), "cm")
)
c.hist <- ggplot(data, aes(x=c)) + geom_histogram() + coord_flip(xlim=c(0,100)) +
theme(
axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
plot.margin = unit(c(0,1,0,0), "cm")
)
blankPanel <- grid.rect(gp=gpar(col="white"))
gt <- gtable(widths = unit(rep(1,9), "null"),
heights = unit(rep(1,9), "null"),
respect=T)
gl <- list(ggplotGrob(b_a.scatter),
ggplotGrob(c_a.scatter), ggplotGrob(c_b.scatter), ggplotGrob(c.hist),
ggplotGrob(a.hist), ggplotGrob(b.hist))
gt <- gtable_add_grob(gt, gl,
l=c(1,1,5,9,1,5),
r=c(4,4,8,9,4,8),
t=c(1,5,5,5,9,9),
b=c(4,8,8,8,9,9))
grid.newpage()
png('multiplot.png')
grid.draw(gt)
dev.off()

Here's one approach: combine graphs column by column using rbind, then cbind the three columns. Dummy gtables are provided for the empty cells, they only contain layout information.
library(ggplot2)
library(gtable)
d <- data.frame(a = rnorm(100, 30, 3),
b = rnorm(100, 40, 5),
c=rnorm(100, 50, 3))
theme_set(theme_bw() +
theme(plot.background=element_rect(colour="red",size = 2)))
## define simpler plots
a <- ggplot(d, aes(x=a, y=b)) + geom_point() + xlim(0,100)+ ylim(0,90)
b <- ggplot(d, aes(x=a, y=c)) + geom_point() + xlim(0,100) + ylim(0,90)
c <- ggplot(d, aes(x=b, y=c)) + geom_point() + xlim(0,100) + ylim(0,90)
ah <- ggplot(d, aes(x=a)) + geom_histogram() +
xlim(0,100)+ ylim(0,2000000)
bh <- ggplot(d, aes(x=b)) + geom_histogram() +
xlim(0,100) + ylim(0,2000000)
ch <- ggplot(d, aes(x=c)) + geom_histogram() +
coord_flip(xlim=c(0,200000))
pl <- lapply(list(a,b,c,ah,bh,ch), ggplotGrob)
## function to create a dummy table (no grobs, zero size) of the right dim for (r/c)bind
dummy_gtable <- function(g){
gtable(widths=unit(rep(0,ncol(g)), 'null'), heights=unit(rep(0, nrow(g)), 'null'))
}
left <- rbind(pl[[1]],pl[[2]],pl[[4]])
middle <- rbind(dummy_gtable(pl[[1]]),pl[[3]],pl[[5]])
right <- rbind(dummy_gtable(pl[[1]]),pl[[6]], dummy_gtable(pl[[5]]))
grid.newpage()
grid.draw(cbind(left, middle, right))
Note that I'm using cbind and rbind from my experimental fork of gtable, because the released version does not use unit.pmax as unit comparison for widths and heights. Custom functions could be borrowed from another question to keep using the stable gtable version.

Related

ggplot2: How to crop out of the blank area on top and bottom of a plot?

This is a follow up of Question How to fit custom long annotations geom_text inside plot area for a Donuts plot?. See the accepted answer, the resulting plot understandably has extra blank area on the top and on the bottom. How can I get rid of those extra blank areas? I looked at theme aspect.ratio but this is not what I intend though it does the job but distorts the plot. I'm after cropping the plot from a square to a landscape form.
How can I do that?
UPDATE This is a self contained example of my use-case:
library(ggplot2); library(dplyr); library(stringr)
df <- data.frame(group = c("Cars", "Trucks", "Motorbikes"),n = c(25, 25, 50),
label2=c("Cars are blah blah blah", "Trucks some of the best in town", "Motorbikes are great if you ..."))
df$ymax = cumsum(df$n)
df$ymin = cumsum(df$n)-df$n
df$ypos = df$ymin+df$n/2
df$hjust = c(0,0,1)
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
# no change to theme
theme(axis.title=element_blank(),axis.text=element_blank(),
panel.background = element_rect(fill = "white", colour = "grey50"),
panel.grid=element_blank(),
axis.ticks.length=unit(0,"cm"),axis.ticks.margin=unit(0,"cm"),
legend.position="none",panel.spacing=unit(0,"lines"),
plot.margin=unit(c(0,0,0,0),"lines"),complete=TRUE) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar("y", start=0) + scale_x_discrete()
And this is the result I'd like to find an answer to fix those annotated resulting blank spaces:
This is a multi-part solution to answer this and the other related question you've posted.
First, for changing the margins in a single graph, #Keith_H was on the right track; using plot.margin inside theme() is a convenient way. However, as mentioned, this alone won't solve the issue if the goal is to combine multiple plots, as in the case of the other question linked above.
To do that you'll need a combination of plot.margin and a specific plotting order within arrangeGrob(). You'll need a specific order because plots get printed in the order you call them, and because of that, it will be easier to change the margins of plots that are layered behind other plots, instead of in front of plots. We can think of it like covering the plot margins we want to shrink by expanding the plot on top of the one we want to shrink. See the graphs below for illustration:
Before plot.margin setting:
#Main code for the 1st graph can be found in the original question.
After plot.margin setting:
#Main code for 2nd graph:
ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)),
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) +
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
After combining plot.margin setting and arrangeGrob() reordering:
#Main code for 3rd graph:
p1 <- ggplot(mtcars,aes(x=1:nrow(mtcars),y=mpg)) + geom_point()
p2 <- ggplot(df %>%
mutate(label2 = str_wrap(label2, width = 10)), #change width to adjust width of annotations
aes(x="", y=n, fill=group)) +
geom_rect(aes_string(ymax="ymax", ymin="ymin", xmax="2.5", xmin="2.0")) +
geom_text(aes_string(label="label2",x="3",y="ypos",hjust="hjust")) +
coord_polar(theta='y') +
expand_limits(x = c(2, 4)) + #change x-axis range limits here
guides(fill=guide_legend(override.aes=list(colour=NA))) +
theme(axis.line = element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
axis.text.y=element_blank(),
axis.text.x=element_blank(),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = "white"),
plot.margin = unit(c(-2, 0, -2, -.1), "cm"),
legend.position = "none") +
scale_x_discrete(limits=c(0, 1))
final <- arrangeGrob(p2,p1,layout_matrix = rbind(c(1),c(2)),
widths=c(4),heights=c(2.5,4),respect=TRUE)
Note that in the final code, I reversed the order you had in the arrangeGrob from p1,p2 to p2,p1. I then adjusted the height of the first object plotted, which is the one we want to shrink. This adjustment allows the earlier plot.margin adjustment to take effect, and as that takes effect, the graph printed last in order, which is P1, will start to take the space of what was the margins of P2. If you make one of these adjustments with out the others, the solution won't work. Each of these steps are important to produce the end result above.
You can set the plot.margins to negative values for the top and bottom of the plot.
plot.margin=unit(c(-4,0,-4,0),"cm"),complete=TRUE)
edit: here is the output:

Using Factor Variables to Facet a Histogram Beside a Scatter Plot in R

Is it possible to use a factor variable to facet a histogram below or beside a scatter plot in ggplot2 in R (such that the histograms are of the x- and y-components of the data)?
The reason I ask whether this could be done with a factor variable is because faceting seems to be more general than the available packages that address this issue, where, for example with faceting, facet labels can be turned on or off, and also faceting has a more standard appearance where publication may be a concern. (Faceting also by default preserves the use of the same axes).
So far I haven't been able to get this to work because it seems like all faceted data have to be of the same number of dimensions (e.g., the scatterplot data is 2D, the histogram data are 1D).
I am not sure if I fully understand the question since a histogram of factor variables doesn't quite make sense to me. Also, without sample data, I will just have to use mtcars. Something like this might help. I use grid.extra in addition to ggplot2 in order to make the plots have a custom grid arrangement.
library(gridExtra)
library(ggplot2)
s_plot <- ggplot(data = mtcars, aes(x = hp, y = mpg)) + geom_point()
h1 <- ggplot(data = mtcars, aes(x = hp)) + geom_histogram()
h2 <- ggplot(data = mtcars, aes(x = mpg)) + geom_histogram()
grid.arrange(s_plot, h1, h2, layout_matrix = cbind(c(1, 1), c(2, 3)))
Note that in the layout_matrix argument in grid.arrange, I use cbind(c(1,1), c(2, 3)) because I want the first plot to be in a column all by itself and then I want the other two plots to occupy individual rows in the second column of the grid.
Consider the use of geom_rug.
ggplot(mtcars, aes(wt, mpg)) +
geom_point() + geom_rug()
Nick and Brian,
Thanks for your help with the code. I asked around and was able to get the set-up I was looking for. Basically it goes like this, as shown below. (Hopefully this might be useful to you and others in the future, as I think this is a common type of graph):
rm(list = ls())
library(ggplot2)
library(gridExtra)
df <- data.frame(
x = rnorm(100),
y = rnorm(100)
)
xrange <- range(pretty(df$x))
yrange <- range(pretty(df$y))
p.left <- ggplot(df, aes(y)) +
geom_histogram() +
lims(x = yrange) +
coord_flip() +
theme_light() +
theme(
axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
panel.grid.major.x = element_blank(),
plot.margin = unit(c(1, 0.05, 0.05, 1), "lines")
)
p.blank <- ggplot() +
theme_void() +
theme(plot.margin = unit(rep(0, 4), "lines"))
p.main <- ggplot(df, aes(x, y)) +
geom_point() +
lims(x = xrange, y = yrange) +
theme_light() +
theme(
axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
plot.margin = unit(c(1, 1, 0.05, 0.05), "lines")
)
p.bottom <- ggplot(df, aes(x)) +
geom_histogram() +
lims(x = xrange) +
theme_light() +
theme(
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.grid.major.y = element_blank(),
plot.margin = unit(c(0.05, 1, 1, 0.05), "lines")
)
lm <- matrix(1:4, nrow = 2)
grid.arrange(
p.left, p.blank, p.main, p.bottom,
layout_matrix = lm,
widths = c(1, 5),
heights = c(5, 1),
padding = unit(0.1, "line")
)

Can you get the axis of the marginal densities to line up with the axis of the scatter plot

Hi I have the plot below and the marginal density plots are slightly off. They do not line up to the x and y axis of the scatter plot so interpretation can be a bit misleading.
I can sort of play with these lines of code to try and get the margins to align for rthe marginal plots but it is very manual and frustrating.
theme0(plot.margin = unit(c(1,0,0,2.2),"lines"))
theme0(plot.margin = unit(c(0,1,1.2,0),"lines"))
Is there a way to automatically find the right margins to pass to theme0(plot.margin = unit(c(0,1,1.2,0),"lines") so that no manual work needs to be done to line up the margins? Thank you.
library(ggplot2)
library(gridExtra)
set.seed(42)
DF <- data.frame(x=rnorm(100,mean=c(1,5)),y=rlnorm(100,meanlog=c(8,6)),group=1:2)
DF
## Scatter plot
p1 <- ggplot(DF,aes(x=x,y=y)) + geom_point() +
scale_x_continuous(expand=c(0.02,0)) +
scale_y_continuous(expand=c(0.02,0)) +
theme_bw() +
theme(legend.position="none",plot.margin=unit(c(0,0,0,0),"points")) # ggplot(DF,aes(x=x,y=y,colour=factor(group))) color the gorup
theme0 <- function(...) theme( legend.position = "none",
panel.background = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.margin = unit(0,"null"),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.ticks.length = unit(0,"null"),
axis.ticks.margin = unit(0,"null"),
panel.border=element_rect(color=NA),...)
### DENSITY OF X
p2 <- ggplot(DF,aes(x=x, fill="blue")) +
geom_density(alpha=0.5) +
scale_x_continuous(breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(breaks=NULL,expand=c(0.02,0)) +
theme_bw() +
theme0(plot.margin = unit(c(1,0,0,2.2),"lines")) # to color group ggplot(DF,aes(x=x,colour=factor(group),fill=factor(group)))
### DENSITY OF Y
p3 <- ggplot(DF,aes(x=y, fill = "red")) +
geom_density(alpha=0.5) +
coord_flip() +
scale_x_continuous(labels = NULL,breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(labels = NULL,breaks=NULL,expand=c(0.02,0)) +
theme_bw() +
theme0(plot.margin = unit(c(0,1,1.2,0),"lines")) # color group ggplot(DF,aes(x=y,colour=factor(group),fill=factor(group)))
grid.arrange(arrangeGrob(p2,ncol=2,widths=c(3,1)),
arrangeGrob(p1,p3,ncol=2,widths=c(3,1)),
heights=c(1,3))

ggplot2 & facet_wrap - eliminate vertical distance between facets

I'm working with some data that I want to display as a nxn grid of plots. Edit: To be more clear, there's 21 categories in my data. I want to facet by category, and have those 21 plots in a 5 x 5 square grid (where the orphan is by itself on the fifth row). Thus facet_wrap instead of facet_grid.
I've got the following code written up for doing it (using the good old iris data set for my reproducible example):
library(ggplot2)
library(grid)
cust_theme <- theme_bw() + theme(legend.position="none",
axis.title = element_blank(), axis.ticks = element_blank(),
axis.text = element_blank(), strip.text = element_blank(),
strip.background = element_blank(), panel.margin = unit(0, "lines"),
panel.border = element_rect(size = 0.25, color = "black"),
panel.grid = element_blank())
iris.plot <- ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point() + cust_theme + facet_wrap( ~ Species, ncol = 2) +
labs(title = "Irises by species")
This gives me ALMOST what I want, but not quite:
I've still got a tiny strip of space between the top row of plots and the bottom row. I'd like to get rid of that entirely, but panel.margin is obviously not doing it. Is there a way to do this?
This might be a little late, but panel.marginis now deprecated. Inside theme use panel.spacing. To eliminate the spacing between the facets then load the grid package and use panel.spacing = unit(0, "lines")
Change the panel.margin argument to panel.margin = unit(c(-0.5,0-0.5,0), "lines"). For some reason the top and bottom margins need to be negative to line up perfectly. Here is the result:
You can also edit the grobs directly:
library(ggplot2)
library(grid)
g <- ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point() +
facet_wrap( ~ Species, ncol = 2) +
labs(title = "Irises by species") +
theme_bw() +
theme(panel.margin = unit(0, "lines")) +
theme(plot.margin = unit(c(0,0,0,0), "lines")) +
theme(strip.background = element_blank()) +
theme(plot.background = element_blank()) +
theme(strip.text = element_blank()) +
theme(axis.ticks.margin = unit(0, "lines"))
g <- ggplotGrob(p)
g$heights[[7]] = unit(0, "lines")
grid.newpage()
grid.draw(g)

scatterplot with alpha transparent histograms in R

How can scatter plots with alpha transparent, scale-less histograms can be made in R, like this figure?
looks like it's not made in ggplot2.
does anyone know what command is used?
library(ggplot2)
library(gridExtra)
set.seed(42)
DF <- data.frame(x=rnorm(100,mean=c(1,5)),y=rlnorm(100,meanlog=c(8,6)),group=1:2)
p1 <- ggplot(DF,aes(x=x,y=y,colour=factor(group))) + geom_point() +
scale_x_continuous(expand=c(0.02,0)) +
scale_y_continuous(expand=c(0.02,0)) +
theme_bw() +
theme(legend.position="none",plot.margin=unit(c(0,0,0,0),"points"))
theme0 <- function(...) theme( legend.position = "none",
panel.background = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.margin = unit(0,"null"),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.ticks.length = unit(0,"null"),
axis.ticks.margin = unit(0,"null"),
panel.border=element_rect(color=NA),...)
p2 <- ggplot(DF,aes(x=x,colour=factor(group),fill=factor(group))) +
geom_density(alpha=0.5) +
scale_x_continuous(breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(breaks=NULL,expand=c(0.02,0)) +
theme_bw() +
theme0(plot.margin = unit(c(1,0,0,2.2),"lines"))
p3 <- ggplot(DF,aes(x=y,colour=factor(group),fill=factor(group))) +
geom_density(alpha=0.5) +
coord_flip() +
scale_x_continuous(labels = NULL,breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(labels = NULL,breaks=NULL,expand=c(0.02,0)) +
theme_bw() +
theme0(plot.margin = unit(c(0,1,1.2,0),"lines"))
grid.arrange(arrangeGrob(p2,ncol=2,widths=c(3,1)),
arrangeGrob(p1,p3,ncol=2,widths=c(3,1)),
heights=c(1,3))
Edit:
I couldn't find out what causes the space below the densities geoms. You can fiddle with the plot margins to avoid it, but I don't really like that.
p2 <- ggplot(DF,aes(x=x,colour=factor(group),fill=factor(group))) +
geom_density(alpha=0.5) +
scale_x_continuous(breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(breaks=NULL,expand=c(0.00,0)) +
theme_bw() +
theme0(plot.margin = unit(c(1,0,-0.48,2.2),"lines"))
p3 <- ggplot(DF,aes(x=y,colour=factor(group),fill=factor(group))) +
geom_density(alpha=0.5) +
coord_flip() +
scale_x_continuous(labels = NULL,breaks=NULL,expand=c(0.02,0)) +
scale_y_continuous(labels = NULL,breaks=NULL,expand=c(0.00,0)) +
theme_bw() +
theme0(plot.margin = unit(c(0,1,1.2,-0.48),"lines"))
I have no idea whether there is a package that does that directly, but I'm sure this can be done in R. Transparency is easy: you add another two digits to the RGB specification of a color for a given transparency:
#FF0000 # red
#FF0000FF # full opacity
#FF000000 # full transparency
Combining different plots is also easy using the layout function. As for the vertical density plot, it is just the same as the horizontal plot with x and y switched. The example given here can easily be expanded to include colors, smaller margins etc. I can try to come up with a more elaborate example if this description is not sufficient.

Resources