Adding a grob to patchwork plot R - r

I have a function that returns a patchwork plot and I can't make any changes to. I would like to add a rectGrob() on top of it. When I try to do this I remove two of the plots.
library(gridExtra)
library(patchwork)
library(ggplot2)
p1 <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point()
p2 <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + geom_point()
p3 <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width), col = 'blue') + geom_point()
p <- p1 + p2 + p3
grob_top <- grobTree(rectGrob(gp=gpar(fill='#F0F0F0',col= 'black')), textGrob('P1,P2, P3'))
grid.arrange(grob_top, p, heights = c(0.1, 0.9))

Using patchwork::wrap_elements() works better than gridExtra::grid.arrange()
patchwork::wrap_elements(grob_top) /
patchwork::wrap_elements(p) /
patchwork::wrap_elements(p) +
patchwork::plot_layout(ncol = 1, heights = c(0.1, 0.45, 0.45))

I think you want:
grid.arrange(grob_top,
p,
nrow = 2,
heights = c(0.1, 0.9))

Related

R adding word in the middle of grid arrange

I am writing R ggplot, and I am arranging multiple plots with grid.arrange.
Is there a way to add some words in in between two plots?
I want the output to be like the red word.
Thank you for your help :)
library(ggplot2)
library(gridExtra)
P1 <- ggplot(mtcars, aes(x = mpg)) +
geom_histogram()
P2 <- ggplot(mtcars, aes(x = wt)) +
geom_histogram()
grid.arrange(P1, *I want to add some information here*,P2, ncol = 1, nrow = 2)
You could use the grid.text function from grid library as follows
### Libraries
library(grid)
library(ggplot2)
library(gridExtra)
### Data
data(cars)
### Initiating plots
P1 <- ggplot(mtcars, aes(x = mpg)) +
geom_histogram()
P2 <- ggplot(mtcars, aes(x = wt)) +
geom_histogram()
### Display plots
grid.arrange(P1, P2, ncol = 1, nrow = 2)+
grid.text("I want to add some information here",
x=unit(0.25, "npc"),
y=unit(.52, "npc"),
gp=gpar(fontsize=20, col="red"))
One approach would be to create another ggplot with only text that you want and use it in cowplot::plot_grid
library(ggplot2)
P1 <- ggplot(mtcars, aes(x = mpg)) + geom_histogram()
P2 <- ggplot() +
annotate("text", x = 4, y = 25, size=8,
label = "This is some text in the middle", color = "red") +
theme_void()
P3 <- ggplot(mtcars, aes(x = wt)) + geom_histogram()
cowplot::plot_grid(P1, P2, P3, rel_heights = c(1/2, 1/12, 1/2),
align = "v", nrow = 3)

Combine and merge legends in ggplot2 with patchwork (discrete/categorical data)

I arranged 3 ggplot2 plots into a single figure by using the functionality of package patchwork. I tried to collect the legends and they appeared one next to the other. But still, they are 3 separate legends and I expected a single legend. So how can I merge the legends that contain identical values of the same factor variable into a single legend?
Notes:
And I do not want to remove the legends of separate plots by using, e.g., theme(legend.position = "none") in case some additional factor level appears. I expect patchwork specific solution.
A similar question was answered in Combine and merge legends in ggplot2 with patchwork but the data was continuous. And in my case, I have categorical data.
The code:
library(ggplot2)
library(patchwork)
iris_1 <-
ggplot(iris, aes(x = Sepal.Length, fill = Species, color = Species)) +
geom_density(alpha = 0.3, adjust = 1.5)
iris_2 <-
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
geom_point()
iris_3 <-
ggplot(iris, aes(x = Species, y = Sepal.Width, fill = Species)) +
geom_boxplot()
(iris_1 + iris_2 + iris_3) + plot_layout(guides = "collect")
Created on 2020-10-14 by the reprex package (v0.3.0)
Update
I tried using the same aesthetic mappings (fill = Species and color = Species) as it was proposed in the comments below but it had no effect:
library(tidyverse)
library(patchwork)
iris_1 <-
ggplot(iris, aes(x = Sepal.Length, color = Species, fill = Species)) +
geom_density(alpha = 0.3, adjust = 1.5)
iris_2 <-
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species, fill = Species)) +
geom_point()
iris_3 <-
ggplot(iris, aes(x = Species, y = Sepal.Width, color = Species, fill = Species)) +
geom_boxplot(color = "black")
(iris_1 + iris_2 + iris_3) + plot_layout(guides = "collect")
Created on 2020-10-14 by the reprex package (v0.3.0)
Unfortunately setting the same aes is only one condition. patchwork will merge legends only if they are identical. Therefore we have to ensure that the legends are the same for each plot. To this end I add a guides layer which makes the look of each legend the same by setting color, shape, size and alpha. Additionally we have to choose the same glyph for each geom using argument key_glyph. After these adjustments the three legends get merged into one.
library(ggplot2)
library(patchwork)
g <- guides(fill = guide_legend(override.aes = list(color = scales::hue_pal()(3),
shape = c(16, 16, 16),
size = c(1, 1, 1),
alpha = c(1, 1, 1)),))
iris_1 <-
ggplot(iris, aes(x = Sepal.Length)) +
geom_density(aes(fill = Species, color = Species), key_glyph = "point", alpha = 0.3, adjust = 1.5) +
g
iris_2 <-
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point(aes(fill = Species, color = Species), key_glyph = "point") +
g
iris_3 <-
ggplot(iris, aes(x = Species, y = Sepal.Width)) +
geom_boxplot(aes(fill = Species, color = Species), key_glyph = "point") +
scale_color_manual(values = c("black", "black", "black")) +
g
(iris_1 + iris_2 + iris_3) + plot_layout(guides = "collect")

Creating a facet theme/design plot in ggplot2 without using facet_

Is there any possibility to create a facet_wrap looking plot in ggplot2 without using facet_wrap() The reason I would like to achieve this is to match some other design. In the plot without_facet below, can I somehow add "Setosa" in the top, so it looks like the with_facet plot, without using facet_wrap.
library(ggplot2)
df <- iris[iris$Species == 'setosa', ]
with_facet <- ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() +facet_wrap(~Species)
with_facet
without_facet <- ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point()
You can try
ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
ggtitle("setosa") +
theme(plot.title = element_text(hjust = 0.5))
A more "hackish"-one could be this hardcoded approach:
ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
ggtitle("setosa") +
geom_rect(xmin = 4.225, xmax = 5.875 , ymin=4.5, ymax=4.6, fill ="lightgrey") +
coord_cartesian(clip = 'off', expand = 0.05) +
theme(plot.title = element_text(hjust = 0.5, size = 12),
plot.margin = margin(t = 30, r = 20, b = 20, l = 20, unit = "pt"))
From Paleolimbot answer on Gitbub (https://github.com/tidyverse/ggplot2/issues/2344)
element_textbox <- function(...) {
el <- element_text(...)
class(el) <- c("element_textbox", class(el))
el
}
element_grob.element_textbox <- function(element, ...) {
text_grob <- NextMethod()
rect_grob <- element_grob(calc_element("strip.background", theme_bw()))
ggplot2:::absoluteGrob(
grid::gList(
element_grob(calc_element("strip.background", theme_bw())),
text_grob
),
height = grid::grobHeight(text_grob),
width = grid::unit(1, "npc")
)
}
From my original question, I added theme_bw()
library(ggplot2)
library(gridExtra)
df <- iris[iris$Species == 'setosa', ]
with_facet <- ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() +
facet_wrap(~Species) +
theme(plot.background = element_rect(color = 'black')) + theme_bw()
without_facet <- ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
ggtitle("setosa") +
theme_bw() +
theme(
plot.title = element_textbox(
hjust = 0.5, margin = margin(t = 5, b = 5), size = 10
),
)
grid.arrange(with_facet, without_facet)
Not identical, but works for my purpose.
This might be one option:
library(ggplot2)
df <- iris[iris$Species == 'setosa', ]
# with annotate:
with_annotate <-
ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
annotate('text', x = 5, y = 4.7, label = "setosa", size = 12)
with_annotate
#or if you do not want the heading to print over the plot area
with_coord_cart <-
ggplot(df, aes(x = Sepal.Length, y = Sepal.Width)) +
geom_point() +
annotate('text', x = 5, y = 4.7, label = "setosa", size = 8)+
coord_cartesian(ylim = c(2, 4.5), clip = 'off') +
theme(plot.margin = margin(2, 1, 1, 1, "lines"))
with_coord_cart
Which gives you:
Note: I had deleted it because it seemed no longer relevant after the original question was updated.
I am not sure I understand correctly. In case you want to arrange different plots together:
library(gridExtra)
grid.arrange(without_facet,
without_facet,
without_facet,
without_facet, nrow = 2)

Draw a "grid" between arranged plots

I'm working with 4 different plots and I'm using ggarrange() from the ggpubr-package to put them in a single plot. I've prepared an example:
library(ggpubr)
library(ggplot2)
p1 <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + ggtitle("Plot 1")
p2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) + geom_point() + ggtitle("Plot 2")
p3 <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Width)) + geom_point() + ggtitle("Plot 3")
p4 <- ggplot(iris, aes(x = Petal.Length, y = Sepal.Width)) + geom_point() + ggtitle("Plot 4") +
facet_wrap(~Species)
plot.list <- list(p1, p2, p3, p4)
ggarrange(plotlist = plot.list)
Output:
I would like to draw a border around the single plots, like so:
Is there any way to draw this border? Thanks!
grid.polygon() is quite manual but I think it can do the trick:
Using RStudio
library("ggpubr")
library(ggplot2)
library(gridExtra)
library(grid)
p1 <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) + geom_point() + ggtitle("Plot 1")
p2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) + geom_point() + ggtitle("Plot 2")
p3 <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Width)) + geom_point() + ggtitle("Plot 3")
p4 <- ggplot(iris, aes(x = Petal.Length, y = Sepal.Width)) + geom_point() + ggtitle("Plot 4") +
facet_wrap(~Species)
plot.list <- list(p1, p2, p3, p4)
ggarrange(plotlist = plot.list)
x = c(0, 0.5, 1, 0.5, 0.5, 0.5)
y = c(0.5, 0.5, 0.5,0, 0.5, 1)
id = c(1,1,1,2,2,2)
grid.polygon(x,y,id)
Using Shiny (Edit)
When doing it within a shiny-app, ones needs to add the grid using annotation_custom(), as follows:
ggarrange(plotlist = plot.list) +
annotation_custom(
grid.polygon(c(0, 0.5, 1, 0.5, 0.5, 0.5),
c(0.5, 0.5, 0.5,0, 0.5, 1),
id = c(1,1,1,2,2,2),
gp = gpar(lwd = 1.5)))
Not sure if this will work for you, but you can just put borders around your individual plots. However, this includes borders on the outside of the layout. Your description seems like you're not opposed to that, but there are only inner gridlines in your example plot.
You could add the theme call when you create the plots; rather than editing the plot creation, I just did it to each plot in the list before sticking them all together.
library(ggpubr)
library(ggplot2)
#### same plot creation here ######
plot.list <- lapply(list(p1, p2, p3, p4),
function(p) p + theme(plot.background = element_rect(color = "black")))
ggarrange(plotlist = plot.list)

Drawing only boundaries of stat_smooth in ggplot2

When using stat_smooth() with geom_point is there a way to remove the shaded fit region, but only draw its outer bounds? I know I can remove the shaded region with something like:
geom_point(aes(x=x, y=y)) + geom_stat(aes(x=x, y=y), alpha=0)
but how can I make the outer bounds of it (outer curves) still visible as faint black lines?
You can also use geom_ribbon with fill = NA.
gg <- ggplot(mtcars, aes(qsec, wt))+
geom_point() +
stat_smooth( alpha=0,method='loess')
rib_data <- ggplot_build(gg)$data[[2]]
ggplot(mtcars)+
stat_smooth(aes(qsec, wt), alpha=0,method='loess')+
geom_point(aes(qsec, wt)) +
geom_ribbon(data=rib_data,aes(x=x,ymin=ymin,ymax=ymax,col='blue'),
fill=NA,linetype=1)
...and if for some reason you don't want the vertical bars, you can just use two geom_line layers:
ggplot(mtcars)+
stat_smooth(aes(qsec, wt), alpha=0,method='loess')+
geom_point(aes(qsec, wt)) +
geom_line(data = rib_data,aes(x = x,y = ymax)) +
geom_line(data = rib_data,aes(x = x,y = ymin))
There are most likely easier ways, but you may try this as a start. I grab data for the confidence interval with ggbuild, which I then use in geom_line
# create a ggplot object with a linear smoother and a CI
library(ggplot2)
gg <- ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_point() +
geom_smooth(method = "lm")
gg
# grab the data from the plot object
gg_data <- ggplot_build(gg)
str(gg_data)
head(gg_data$data[[2]])
gg2 <- gg_data$data[[2]]
# plot with 'CI-lines' and the shaded confidence area
ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE, size = 1) +
geom_line(data = gg2, aes(x = x, y = ymin), size = 0.02) +
geom_line(data = gg2, aes(x = x, y = ymax), size = 0.02)
# plot with 'CI-lines' but without confidence area
ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, size = 1) +
geom_line(data = gg2, aes(x = x, y = ymin), size = 0.02) +
geom_line(data = gg2, aes(x = x, y = ymax), size = 0.02)

Resources