ggplot2 & facet_wrap - eliminate vertical distance between facets - r

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)

Related

Legend overlaps the plot in patchwork when using guide_area

guides = "collect" does its job but it locates the legend in a way that it overlaps the plot. I would like it to be placed right in the middle of the empty bottom right corner, but it appears to be difficult since theme(legend.position = c(X,Y)) does not work with patchwork
Illustration of the issue:
This is the code I have for assembling 4 graphs I have using patchwork. Pretty sure there is a more elegant way to use theme() but I am quite new to patchwork and it worked for me so far, except for the legend positioning.
A similar issue was resolved here but it does not seem to help in my case.
#first panel
s_wpanels_final <- (dots & theme_bw() & theme(axis.title.x =
element_blank(), panel.grid.minor.y = element_blank())) +
#second panel
(g_box_tmax & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y=element_blank(), axis.ticks.y=element_blank(),
axis.title.x = element_blank(), legend.position = "none")) +
#third panel
(g_box_t0 & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y=element_blank(), axis.ticks.y=element_blank(),
axis.title.x = element_blank(), legend.position = "none")) +
#fourth panel
(tmax_box & theme_bw() & theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank(), axis.text.y=element_blank(),
axis.ticks.y=element_blank(), axis.title.y = element_blank(),
legend.position = "none")) +
guide_area()+
plot_layout(ncol=3, guides = "collect", widths=c(6,1,1), heights=c(6,1)) &
theme(legend.direction = "vertical", legend.box = "horizontal")
There are two issues with your code. First using + to glue your plots together and setting ncol=3 will place the guide_area in the second column of the second row. To center the legend I would suggest to use the design argument to specify the layout of the plot. Second, while the plot panels will adjust to the space set via the height and width arguments and the size of your plotting device, the legend will not, i.e. if the legend will not fit into the space given it will overlap with the surrounding panels. To fix that I would suggest to increase the widths of the second and third columns and the height of the second row. But as I said this also depends on the size of the plotting device.
Using some fake example plot based on mtcars(see below) let's first reproduce your issue:
library(ggplot2)
library(patchwork)
list(
dots,
g_box_tmax,
g_box_t0,
tmax_box,
guide_area()
) |>
wrap_plots() +
plot_layout(guides = "collect", widths = c(6, 1, 1), heights = c(6, 1), ncol = 3) &
theme(legend.direction = "vertical", legend.box = "horizontal")
However, specifying the layout via the design argument and increasing the height of the second row as well as the widths of the second and third columns works fine and centers the legend in the guide area:
design <-
"
ABC
DEE
"
list(
dots,
g_box_tmax,
g_box_t0,
tmax_box,
guide_area()
) |>
wrap_plots() +
plot_layout(guides = "collect", widths = c(6, 1.5, 1.5), heights = c(6, 1.5), design = design) &
theme(legend.direction = "vertical", legend.box = "horizontal")
PLOTS
dots <- ggplot(mtcars, aes(mpg, hp, color = factor(cyl), size = qsec)) +
geom_point() +
theme_bw() +
theme(
axis.title.x = element_blank(),
panel.grid.minor.y = element_blank()
)
g_box_tmax <- g_box_t0 <- ggplot(mtcars, aes(factor(cyl), hp, fill = factor(cyl))) +
geom_boxplot() +
theme_bw() +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(), panel.grid.minor.y = element_blank(),
axis.text.y = element_blank(), axis.ticks.y = element_blank(),
axis.title.x = element_blank(), legend.position = "none"
)
tmax_box <- ggplot(mtcars, aes(mpg, factor(cyl), fill = factor(cyl))) +
geom_boxplot() +
theme_bw() +
theme(
axis.text.x = element_blank(),
axis.ticks.x = element_blank(), axis.text.y = element_blank(),
axis.ticks.y = element_blank(), axis.title.y = element_blank(),
legend.position = "none"
)
What causes the legend box (which is too big for the plot dimension) to be positioned there, is probably some quite clever patchwork code, and is related to guide_area (therefore my question title edit).
The below is a slightly unsatisfactory, but effective hack to modify the position. It's a bit of a trial and error. Simply give a negative margin to the legend box to the right and it will "drag" the box accordingly.
I've removed all the legend.position = "none" from your plots as this is not necessary with guides = "collect"
library(ggplot2)
library(patchwork)
p1 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p2 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p3 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p4 <- ggplot(iris) + geom_point(aes(Sepal.Length, Sepal.Width, color = Species, size = Petal.Length))
p1 + p2 + p3 + p4 +
guide_area()+
plot_layout(ncol=3, guides = "collect", widths=c(6,1,1), heights=c(6,1)) &
theme(legend.direction = "vertical", legend.box = "horizontal",
legend.box.margin = margin(r = -1, unit = "in"))

How to avoid axis line disappearing when using ggarrange?

I'd like to arrange two facet plots using ggarrange (in order to get x axes aligned).
library(egg)
library(ggplot2)
p1 <- ggplot(warpbreaks) +
geom_bar(aes(x = wool)) +
facet_wrap(~tension, ncol = 2, scales = "free_x") +
theme_bw() +
theme(axis.line = element_line(colour = "black", size = .1),
panel.border = element_blank(),
strip.background = element_blank())
p2 <- ggplot(warpbreaks) +
geom_bar(aes(x = tension)) +
facet_wrap(~wool) +
theme_bw() +
theme(axis.line = element_line(colour = "black", size = .1),
panel.border = element_blank(),
strip.background = element_blank())
ggarrange(p1, p2, ncol = 2)
Works great, but unfortunately the vertical axis lines disappeared. This does not happen when using grid.arrange, but at least for my real data there the x axes are not aligned, hence my wish to use ggarrange. Is there a way to keep the axis lines?
tl;dr: setting panel.background = element_blank() should restore the axes.
I think it's a combination of a clipping issue in ggplot2 (the y axis line can be clipped by the plot panel, cutting its width in half), and egg::gtable_frame placing the axis below the plot panel.
library(egg)
library(ggplot2)
p1 <- ggplot(warpbreaks) +
geom_bar(aes(x = wool)) +
facet_wrap(~tension, ncol = 2, scales = "free_x") +
theme_bw() +
theme(axis.line = element_line(colour = alpha("red", 0.5), size = 5),
panel.border = element_blank(),
panel.background = element_rect(fill = alpha("white", 0.5),
linetype = 2, colour = "black"),
strip.background = element_blank())
p1
g1 <- ggplotGrob(p1)
gg <- gtable_frame(g1)
grid.newpage()
grid.draw(gg)

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

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.

Remove grid, background color, and top and right borders from ggplot2

I would like to reproduce the plot immediately below by using ggplot2. I can come close, but cannot remove the top and right borders. Below I present several attempts using ggplot2, including several suggestions found on or via Stackoverflow. Unfortunately I have not been able to get those suggestions to work.
I am hoping someone may be able to correct one or more of the code snippets below.
Thank you for any suggestions.
# desired plot
a <- seq(1,20)
b <- a^0.25
plot(a,b, bty = "l")
library(ggplot2)
df <- as.data.frame(cbind(a,b))
# 1. ggplot2 default
ggplot(df, aes(x = a, y = b)) + geom_point()
# 2. removes background color
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black'))
# 3. also removes gridlines
none <- theme_blank()
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black')) + opts(panel.grid.major = none, panel.grid.minor = none)
# 4. does not remove top and right border
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black')) + opts(panel.grid.major = none, panel.grid.minor = none) + opts(panel.border = none)
# 5. does not remove top and right border
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black')) + opts(panel.grid.major = none, panel.grid.minor = none) + opts(axis.line = theme_segment())
# 6. removes x and y axis in addition to top and right border
# http://stackoverflow.com/questions/5458409/remove-top-and-right-border-from-ggplot2
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black')) + opts(panel.grid.major = none, panel.grid.minor = none) + opts(panel.background=theme_rect(colour=NA))
# 7. returns error when attempting to remove top and right border
# https://groups.google.com/group/ggplot2/browse_thread/thread/f998d113638bf251
#
# Error in el(...) : could not find function "polylineGrob"
#
theme_L_border <- function(colour = "black", size = 1, linetype = 1) {
structure(
function(x = 0, y = 0, width = 1, height = 1, ...) {
polylineGrob(
x=c(x+width, x, x), y=c(y,y,y+height), ..., default.units = "npc",
gp=gpar(lwd=size, col=colour, lty=linetype),
)
},
class = "theme",
type = "box",
call = match.call()
)
}
ggplot(df, aes(x = a, y = b)) + geom_point() + opts(panel.background = theme_rect(fill='white', colour='black')) + opts(panel.grid.major = none, panel.grid.minor = none) + opts( panel.border = theme_L_border())
EDIT Ignore this answer. There are now better answers. See the comments. Use + theme_classic()
EDIT
This is a better version. The bug mentioned below in the original post remains (I think). But the axis line is drawn under the panel. Therefore, remove both the panel.border and panel.background to see the axis lines.
library(ggplot2)
a <- seq(1,20)
b <- a^0.25
df <- data.frame(a,b)
ggplot(df, aes(x = a, y = b)) + geom_point() +
theme_bw() +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank())
Original post
This gets close. There was a bug with axis.line not working on the y-axis (see here), that appears not to be fixed yet. Therefore, after removing the panel border, the y-axis has to be drawn in separately using geom_vline.
library(ggplot2)
library(grid)
a <- seq(1,20)
b <- a^0.25
df <- data.frame(a,b)
p = ggplot(df, aes(x = a, y = b)) + geom_point() +
scale_y_continuous(expand = c(0,0)) +
scale_x_continuous(expand = c(0,0)) +
theme_bw() +
opts(axis.line = theme_segment(colour = "black"),
panel.grid.major = theme_blank(),
panel.grid.minor = theme_blank(),
panel.border = theme_blank()) +
geom_vline(xintercept = 0)
p
The extreme points are clipped, but the clipping can be undone using code by baptiste.
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Or use limits to move the boundaries of the panel.
ggplot(df, aes(x = a, y = b)) + geom_point() +
xlim(0,22) + ylim(.95, 2.1) +
scale_x_continuous(expand = c(0,0), limits = c(0,22)) +
scale_y_continuous(expand = c(0,0), limits = c(.95, 2.2)) +
theme_bw() +
opts(axis.line = theme_segment(colour = "black"),
panel.grid.major = theme_blank(),
panel.grid.minor = theme_blank(),
panel.border = theme_blank()) +
geom_vline(xintercept = 0)
Recent updates to ggplot (0.9.2+) have overhauled the syntax for themes. Most notably, opts() is now deprecated, having been replaced by theme(). Sandy's answer will still (as of Jan '12) generates a chart, but causes R to throw a bunch of warnings.
Here's updated code reflecting current ggplot syntax:
library(ggplot2)
a <- seq(1,20)
b <- a^0.25
df <- as.data.frame(cbind(a,b))
#base ggplot object
p <- ggplot(df, aes(x = a, y = b))
p +
#plots the points
geom_point() +
#theme with white background
theme_bw() +
#eliminates background, gridlines, and chart border
theme(
plot.background = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank()
) +
#draws x and y axis line
theme(axis.line = element_line(color = 'black'))
generates:
An alternative to theme_classic() is the theme that comes with the cowplot package, theme_cowplot() (loaded automatically with the package). It looks similar to theme_classic(), with a few subtle differences. Most importantly, the default label sizes are larger, so the resulting figures can be used in publications without further modifications needed (in particular if you save them with save_plot() instead of ggsave()). Also, the background is transparent, not white, which may be useful if you want to edit the figure in illustrator. Finally, faceted plots look better, in my opinion.
Example:
library(cowplot)
a <- seq(1,20)
b <- a^0.25
df <- as.data.frame(cbind(a,b))
p <- ggplot(df, aes(x = a, y = b)) + geom_point()
save_plot('plot.png', p) # alternative to ggsave, with default settings that work well with the theme
This is what the file plot.png produced by this code looks like:
Disclaimer: I'm the package author.
I followed Andrew's answer, but I also had to follow https://stackoverflow.com/a/35833548 and set the x and y axes separately due to a bug in my version of ggplot (v2.1.0).
Instead of
theme(axis.line = element_line(color = 'black'))
I used
theme(axis.line.x = element_line(color="black", size = 2),
axis.line.y = element_line(color="black", size = 2))
The above options do not work for maps created with sf and geom_sf(). Hence, I want to add the relevant ndiscr parameter here. This will create a nice clean map showing only the features.
library(sf)
library(ggplot2)
ggplot() +
geom_sf(data = some_shp) +
theme_minimal() + # white background
theme(axis.text = element_blank(), # remove geographic coordinates
axis.ticks = element_blank()) + # remove ticks
coord_sf(ndiscr = 0) # remove grid in the background
Simplification from the above Andrew's answer leads to this key theme to generate the half border.
theme (panel.border = element_blank(),
axis.line = element_line(color='black'))
Here's an extremely simple answer
yourPlot +
theme(
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.line = element_line(colour = "black")
)
It's that easy. Source: the end of this article
You may be check also panel.background as well.
theme(
panel.background = element_rect(fill = "black"),
panel.grid.major = element_blank(), panel.grid.minor = element_blank()

Resources