Add a vector of labels to multiple plots in ggplot [duplicate] - r
I want to annotate some text on last facet of the plot with the following code:
library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p <- p + facet_grid(. ~ cyl)
p <- p + annotate("text", label = "Test", size = 4, x = 15, y = 5)
print(p)
But this code annotates the text on every facet. How can I get the annotated text on only one facet?
Function annotate() adds the same label to all panels in a plot with facets. If the intention is to add different annotations to each panel, or annotations to only some panels, a geometry has to be used instead of annotate(). To use a geometry, such as geom_text() we need to assemble a data frame containing the text of the labels in one column and columns for the variables to be mapped to other aesthetics, as well as the variable(s) used for faceting.
Typically you'd do something like this:
ann_text <- data.frame(mpg = 15,wt = 5,lab = "Text",
cyl = factor(8,levels = c("4","6","8")))
p + geom_text(data = ann_text,label = "Text")
It should work without specifying the factor variable completely, but will probably throw some warnings:
Function annotate() adds the same label to all panels in a plot with facets. If the intention is to add different annotations to each panel, or annotations to only some panels, a geometry has to be used instead of annotate(). To use a geometry, such as geom_text() we need to assemble a data frame containing the text of the labels in one column and columns for the variables to be mapped to other aesthetics, as well as the variable(s) used for faceting. This answer exemplifies this for both facet_wrap() and facet_grid().
Here's the plot without text annotations:
library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
facet_grid(. ~ cyl) +
theme(panel.spacing = unit(1, "lines"))
p
Let's create an additional data frame to hold the text annotations:
dat_text <- data.frame(
label = c("4 cylinders", "6 cylinders", "8 cylinders"),
cyl = c(4, 6, 8)
)
p + geom_text(
data = dat_text,
mapping = aes(x = -Inf, y = -Inf, label = label),
hjust = -0.1,
vjust = -1
)
Alternatively, we can manually specify the position of each label:
dat_text <- data.frame(
label = c("4 cylinders", "6 cylinders", "8 cylinders"),
cyl = c(4, 6, 8),
x = c(20, 27.5, 25),
y = c(4, 4, 4.5)
)
p + geom_text(
data = dat_text,
mapping = aes(x = x, y = y, label = label)
)
We can also label plots across two facets:
dat_text <- data.frame(
cyl = c(4, 6, 8, 4, 6, 8),
am = c(0, 0, 0, 1, 1, 1)
)
dat_text$label <- sprintf(
"%s, %s cylinders",
ifelse(dat_text$am == 0, "automatic", "manual"),
dat_text$cyl
)
p +
facet_grid(am ~ cyl) +
geom_text(
size = 5,
data = dat_text,
mapping = aes(x = Inf, y = Inf, label = label),
hjust = 1.05,
vjust = 1.5
)
Notes:
You can use -Inf and Inf to position text at the edges of a panel.
You can use hjust and vjust to adjust the text justification.
The text label data frame dat_text should have a column that works with your facet_grid() or facet_wrap().
If anyone is looking for an easy way to label facets for reports or publications, the egg (CRAN) package has pretty nifty tag_facet() & tag_facet_outside() functions.
library(ggplot2)
p <- ggplot(mtcars, aes(qsec, mpg)) +
geom_point() +
facet_grid(. ~ am) +
theme_bw(base_size = 12)
# install.packages('egg', dependencies = TRUE)
library(egg)
Tag inside
Default
tag_facet(p)
Note: if you want to keep the strip text and background, try adding strip.text and strip.background back in theme or remove theme(strip.text = element_blank(), strip.background = element_blank()) from the original tag_facet() function.
tag_facet <- function(p, open = "(", close = ")", tag_pool = letters, x = -Inf, y = Inf,
hjust = -0.5, vjust = 1.5, fontface = 2, family = "", ...) {
gb <- ggplot_build(p)
lay <- gb$layout$layout
tags <- cbind(lay, label = paste0(open, tag_pool[lay$PANEL], close), x = x, y = y)
p + geom_text(data = tags, aes_string(x = "x", y = "y", label = "label"), ..., hjust = hjust,
vjust = vjust, fontface = fontface, family = family, inherit.aes = FALSE)
}
Align top right & use Roman numerals
tag_facet(p, x = Inf, y = Inf,
hjust = 1.5,
tag_pool = as.roman(1:nlevels(factor(mtcars$am))))
Align bottom left & use capital letters
tag_facet(p,
x = -Inf, y = -Inf,
vjust = -1,
open = "", close = ")",
tag_pool = LETTERS)
Define your own tags
my_tag <- c("i) 4 cylinders", "ii) 6 cyls")
tag_facet(p,
x = -Inf, y = -Inf,
vjust = -1, hjust = -0.25,
open = "", close = "",
fontface = 4,
size = 5,
family = "serif",
tag_pool = my_tag)
Tag outside
p2 <- ggplot(mtcars, aes(qsec, mpg)) +
geom_point() +
facet_grid(cyl ~ am, switch = 'y') +
theme_bw(base_size = 12) +
theme(strip.placement = 'outside')
tag_facet_outside(p2)
Edit: adding another alternative using the stickylabeller package
- `.n` numbers the facets numerically: `"1"`, `"2"`, `"3"`...
- `.l` numbers the facets using lowercase letters: `"a"`, `"b"`, `"c"`...
- `.L` numbers the facets using uppercase letters: `"A"`, `"B"`, `"C"`...
- `.r` numbers the facets using lowercase Roman numerals: `"i"`, `"ii"`, `"iii"`...
- `.R` numbers the facets using uppercase Roman numerals: `"I"`, `"II"`, `"III"`...
# devtools::install_github("rensa/stickylabeller")
library(stickylabeller)
ggplot(mtcars, aes(qsec, mpg)) +
geom_point() +
facet_wrap(. ~ am,
labeller = label_glue('({.l}) am = {am}')) +
theme_bw(base_size = 12)
Created by the reprex package (v0.2.1)
I think for the answer above lab="Text" is useless, the code below is also ok.
ann_text <- data.frame(mpg = 15,wt = 5,
cyl = factor(8,levels = c("4","6","8")))
p + geom_text(data = ann_text,label = "Text" )
However if you want to label differently in different sub-graphs, it will be ok in this way:
ann_text <- data.frame(mpg = c(14,15),wt = c(4,5),lab=c("text1","text2"),
cyl = factor(c(6,8),levels = c("4","6","8")))
p + geom_text(data = ann_text,aes(label =lab) )
Expanding slightly on joran's excellent answer, to clarify how the label dataframe works.
You can think of "mpg" and "wt" as the x and y coordinates, respectively (I find it easier to keep track of the original variable names than renaming them, as in Kamil's also-excellent answer). You need one row per label, and the "cyl" column shows which facet each row is associated with.
ann_text<-data.frame(mpg=c(25,15),wt=c(3,5),cyl=c(6,8),label=c("Label 1","Label 2"))
ann_text
> mpg wt cyl label
> 25 3 6 Label 1
> 15 5 8 Label 2
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p <- p + facet_grid(. ~ factor(cyl))
p + geom_text(data = ann_text,label=ann_text$label)
I did not know about the egg package,
so here is a plain ggplot2 package solution
library(tidyverse)
library(magrittr)
Data1=data.frame(A=runif(20, min = 0, max = 100), B=runif(20, min = 0, max = 250), C=runif(20, min = 0, max = 300))
Data2=data.frame(A=runif(20, min = -10, max = 50), B=runif(20, min = -5, max = 150), C=runif(20, min = 5, max = 200))
bind_cols(
Data1 %>% gather("Vars","Data_1"),
Data2 %>% gather("Vars","Data_2")
) %>% select(-Vars1) -> Data_combined
Data_combined %>%
group_by(Vars) %>%
summarise(r=cor(Data_1,Data_2),
r2=r^2,
p=(pt(abs(r),nrow(.)-2)-pt(-abs(r),nrow(.)-2))) %>%
mutate(rlabel=paste("r:",format(r,digits=3)),
plabel=paste("p:",format(p,digits=3))) ->
label_df
label_df %<>% mutate(x=60,y=190)
Data_combined %>%
ggplot(aes(x=Data_1,y=Data_2,color=Vars)) +
geom_point() +
geom_smooth(method="lm",se=FALSE) +
geom_text(data=label_df,aes(x=x,y=y,label=rlabel),inherit.aes = FALSE) +
geom_text(data=label_df,aes(x=x,y=y-10,label=plabel),inherit.aes = FALSE) +
facet_wrap(~ Vars)
Related
How to center the text of a label made with geom_label() whose vertical position was changed with vjust? (ggplot2 package)
I plotted columns with the ggplot2 package and the geom_col() function. I put a label at the top of each column with the respective values with the geom_label() function. Inside the geom_label() function I modified the text size (size = 3) and label position (vjust = -1), but the result showed the label in the desired position but with the text off-center. How can I fix this issue? library(ggplot2) Factor <- c('A', 'B') Y <- c(5, 10) DF <- data.frame(Factor, Y) ggplot(data = DF, aes(x = Factor, y = Y)) + geom_col() + geom_label(aes(label = Y), vjust = -1, size = 3) + scale_y_continuous(limits = c(0, 15))
Instead of shifting the labels via vjust (or hjust) I would suggest to make use of nudge_y. library(ggplot2) Factor <- c('A', 'B') Y <- c(5, 10) DF <- data.frame(Factor, Y) p <- ggplot(data = DF, aes(x = Factor, y = Y)) + geom_col() + scale_y_continuous(limits = c(0, 15)) p + geom_label(aes(label = Y), nudge_y = 1, size = 3) A second option would be to make use of ggtext::geom_richtext which allows to add some margin between the data point and the label: p + ggtext::geom_richtext(aes(label = Y), vjust = 0, size = 3, label.margin = unit(5, "pt"))
Showing median value in grouped boxplot in R
I have created boxplots using ggplot2 with this code. plotgraph <- function(x, y, colour, min, max) { plot1 <- ggplot(dims, aes(x = x, y = y, fill = Region)) + geom_boxplot() #plot1 <- plot1 + scale_x_discrete(name = "Blog Type") plot1 <- plot1 + labs(color='Region') + geom_hline(yintercept = 0, alpha = 0.4) plot1 <- plot1 + scale_y_continuous(breaks=c(seq(min,max,5)), limits = c(min, max)) plot1 <- plot1 + labs(x="Blog Type", y="Dimension Score") + scale_fill_grey(start = 0.3, end = 0.7) + theme_grey() plot1 <- plot1 + theme(legend.justification = c(1, 1), legend.position = c(1, 1)) return(plot1) } plot1 <- plotgraph (Blog, Dim1, Region, -30, 25) A part of data I use is reproduced here. Blog,Region,Dim1,Dim2,Dim3,Dim4 BlogsInd.,PK,-4.75,13.47,8.47,-1.29 BlogsInd.,PK,-5.69,6.08,1.51,-1.65 BlogsInd.,PK,-0.27,6.09,0.03,1.65 BlogsInd.,PK,-2.76,7.35,5.62,3.13 BlogsInd.,PK,-8.24,12.75,3.71,3.78 BlogsInd.,PK,-12.51,9.95,2.01,0.21 BlogsInd.,PK,-1.28,7.46,7.56,2.16 BlogsInd.,PK,0.95,13.63,3.01,3.35 BlogsNews,PK,-5.96,12.3,6.5,1.49 BlogsNews,PK,-8.81,7.47,4.76,1.98 BlogsNews,PK,-8.46,8.24,-1.07,5.09 BlogsNews,PK,-6.15,0.9,-3.09,4.94 BlogsNews,PK,-13.98,10.6,4.75,1.26 BlogsNews,PK,-16.43,14.49,4.08,9.91 BlogsNews,PK,-4.09,9.88,-2.79,5.58 BlogsNews,PK,-11.06,16.21,4.27,8.66 BlogsNews,PK,-9.04,6.63,-0.18,5.95 BlogsNews,PK,-8.56,7.7,0.71,4.69 BlogsNews,PK,-8.13,7.26,-1.13,0.26 BlogsNews,PK,-14.46,-1.34,-1.17,14.57 BlogsNews,PK,-4.21,2.18,3.79,1.26 BlogsNews,PK,-4.96,-2.99,3.39,2.47 BlogsNews,PK,-5.48,0.65,5.31,6.08 BlogsNews,PK,-4.53,-2.95,-7.79,-0.81 BlogsNews,PK,6.31,-9.89,-5.78,-5.13 BlogsTech,PK,-11.16,8.72,-5.53,8.86 BlogsTech,PK,-1.27,5.56,-3.92,-2.72 BlogsTech,PK,-11.49,0.26,-1.48,7.09 BlogsTech,PK,-0.9,-1.2,-2.03,-7.02 BlogsTech,PK,-12.27,-0.07,5.04,8.8 BlogsTech,PK,6.85,1.27,-11.95,-10.79 BlogsTech,PK,-5.21,-0.89,-6,-2.4 BlogsTech,PK,-1.06,-4.8,-8.62,-2.42 BlogsTech,PK,-2.6,-4.58,-2.07,-3.25 BlogsTech,PK,-0.95,2,-2.2,-3.46 BlogsTech,PK,-0.82,7.94,-4.95,-5.63 BlogsTech,PK,-7.65,-5.59,-3.28,-0.54 BlogsTech,PK,0.64,-1.65,-2.36,-2.68 BlogsTech,PK,-2.25,-3,-3.92,-4.87 BlogsTech,PK,-1.58,-1.42,-0.38,-5.15 Columns,PK,-5.73,3.26,0.81,-0.55 Columns,PK,0.37,-0.37,-0.28,-1.56 Columns,PK,-5.46,-4.28,2.61,1.29 Columns,PK,-3.48,2.38,12.87,3.73 Columns,PK,0.88,-2.24,-1.74,3.65 Columns,PK,-2.11,4.51,8.95,2.47 Columns,PK,-10.13,10.73,9.47,-0.47 Columns,PK,-2.08,1.04,0.11,0.6 Columns,PK,-4.33,5.65,2,-0.77 Columns,PK,1.09,-0.24,-0.92,-0.17 Columns,PK,-4.23,-4.01,-2.32,6.26 Columns,PK,-1.46,-1.53,9.83,5.73 Columns,PK,9.37,-1.32,1.27,-4.12 Columns,PK,5.84,-2.42,-5.21,1.07 Columns,PK,8.21,-9.36,-5.87,-3.21 Columns,PK,7.34,-7.3,-2.94,-5.86 Columns,PK,1.83,-2.77,1.47,-4.02 BlogsInd.,PK,14.39,-0.55,-5.42,-4.7 BlogsInd.,US,22.02,-1.39,2.5,-3.12 BlogsInd.,US,4.83,-3.58,5.34,9.22 BlogsInd.,US,-3.24,2.83,-5.3,-2.07 BlogsInd.,US,-5.69,15.17,-14.27,-1.62 BlogsInd.,US,-22.92,4.1,5.79,-3.88 BlogsNews,US,0.41,-2.03,-6.5,2.81 BlogsNews,US,-4.42,8.49,-8.04,2.04 BlogsNews,US,-10.72,-4.3,3.75,11.74 BlogsNews,US,-11.29,2.01,0.67,8.9 BlogsNews,US,-2.89,0.08,-1.59,7.06 BlogsNews,US,-7.59,8.51,3.02,12.33 BlogsNews,US,-7.45,23.51,2.79,0.48 BlogsNews,US,-12.49,15.79,-9.86,18.29 BlogsTech,US,-11.59,6.38,11.79,-7.28 BlogsTech,US,-4.6,4.12,7.46,3.36 BlogsTech,US,-22.83,2.54,10.7,5.09 BlogsTech,US,-4.83,3.37,-8.12,-0.9 BlogsTech,US,-14.76,29.21,6.23,9.33 Columns,US,-15.93,12.85,19.47,-0.88 Columns,US,-2.78,-1.52,8.16,0.24 Columns,US,-16.39,13.08,11.07,7.56 Even though I have tried to add detailed scale on y-axis, it is hard for me to pinpoint exact median score for each boxplot. So I need to print median value within each boxplot. There was another answer available (for faceted boxplot) which does not work for me as the printed values are not within the boxes but jammed together in the middle. It will be great to be able to print them within (middle and above the median line of) boxplots. Thanks for your help. Edit: I make a grouped graph as below. Add
library(dplyr) dims=dims%>% group_by(Blog,Region)%>% mutate(med=median(Dim1)) plotgraph <- function(x, y, colour, min, max) { plot1 <- ggplot(dims, aes(x = x, y = y, fill = Region)) + geom_boxplot()+ labs(color='Region') + geom_hline(yintercept = 0, alpha = 0.4)+ scale_y_continuous(breaks=c(seq(min,max,5)), limits = c(min, max))+ labs(x="Blog Type", y="Dimension Score") + scale_fill_grey(start = 0.3, end = 0.7) + theme_grey()+ theme(legend.justification = c(1, 1), legend.position = c(1, 1))+ geom_text(aes(y = med,x=x, label = round(med,2)),position=position_dodge(width = 0.8),size = 3, vjust = -0.5,colour="blue") return(plot1) } plot1 <- plotgraph (Blog, Dim1, Region, -30, 25) Which gives (the text colour can be tweaked to something less tacky): Note: You should consider using non-standard evaluation in your function rather than having it require the use of attach() Edit: One liner, not as clean I wanted it to be since I ran into problems with dplyr not properly aggregating the data even though it says the grouping was performed. This function assume the dataframe is always called dims library(ggplot2) library(reshape2) plotgraph <- function(x, y, colour, min, max) { plot1 <- ggplot(dims, aes_string(x = x, y = y, fill = colour)) + geom_boxplot()+ labs(color=colour) + geom_hline(yintercept = 0, alpha = 0.4)+ scale_y_continuous(breaks=c(seq(min,max,5)), limits = c(min, max))+ labs(x="Blog Type", y="Dimension Score") + scale_fill_grey(start = 0.3, end = 0.7) + theme_grey()+ theme(legend.justification = c(1, 1), legend.position = c(1, 1))+ geom_text(data= melt(with(dims, tapply(eval(parse(text=y)),list(eval(parse(text=x)),eval(parse(text=colour))), median)),varnames=c("Blog","Region"),value.name="med"), aes_string(y = "med",x=x, label = "med"),position=position_dodge(width = 0.8),size = 3, vjust = -0.5,colour="blue") return(plot1) } plot1 <- plotgraph ("Blog", "Dim1", "Region", -30, 25)
Assuming that Blog is your dataframe, the following should work: min <- -30 max <- 25 meds <- aggregate(Dim1~Region, Blog, median) plot1 <- ggplot(Blog, aes(x = Region, y = Dim1, fill = Region)) + geom_boxplot() plot1 <- plot1 + labs(color='Region') + geom_hline(yintercept = 0, alpha = 0.4) plot1 <- plot1 + scale_y_continuous(breaks=c(seq(min,max,5)), limits = c(min, max)) plot1 <- plot1 + labs(x="Blog Type", y="Dimension Score") + scale_fill_grey(start = 0.3, end = 0.7) + theme_grey() plot1 + theme(legend.justification = c(1, 1), legend.position = c(1, 1)) + geom_text(data = meds, aes(y = Dim1, label = round(Dim1,2)),size = 5, vjust = -0.5, color='white')
ggplot2 stat_sum: Label points with percentage
I'd like to ask if it's possible to label each of the points plotted by stat_sum with the percentage (i.e. the proportion) of the observations that that point represents. Ideally I would like the label to be in percent format rather than decimal. Many thanks for your time. Edit: Minimal reproducible example library("ggplot2") library("scales") ggplot(diamonds, aes(x = cut, y = clarity)) + stat_sum(aes(group = 1)) + scale_size_continuous(labels=percent) Image of the resulting plot So my question is, how (if possible) to label each of those summary points with their 'prop' percentage value.
There are a few options. I'll assume that the legend is not needed given that the points are labelled with percentage counts. One option is to add another stat_sum() function that contains a label aesthetic and a "text" geom. For instance: library("ggplot2") ggplot(diamonds, aes(x = cut, y = clarity, group = 1)) + stat_sum(geom = "point", show.legend = FALSE) + stat_sum(aes(label = paste(round(..prop.. * 100, 2), "%", sep = "")), size = 3, hjust = -0.4, geom = "text", show.legend = FALSE) Or, there may be no need for the points. The labels can do all the work - show location and size: ggplot(diamonds, aes(x = cut, y = clarity, group = 1)) + stat_sum(aes(label = paste(round(..prop.. * 100, 2), "%", sep = "")), geom = "text", show.legend = FALSE) + scale_size(range=c(2, 8)) Sometimes it is easier to create a summary table outside ggplot: library(plyr) df = transform(ddply(diamonds, .(cut, clarity), "nrow"), percent = round(nrow/sum(nrow)*100, 2)) ggplot(df, aes(x = cut, y = clarity)) + geom_text(aes(size = percent, label = paste(percent, "%", sep = "")), show.legend = FALSE) + scale_size(range = c(2, 8))
Condition a ..count.. summation on the faceting variable
I'm trying to annotate a bar chart with the percentage of observations falling into that bucket, within a facet. This question is very closely related to this question: Show % instead of counts in charts of categorical variables but the introduction of faceting introduces a wrinkle. The answer to the related question is to use stat_bin w/ the text geom and then have the label be constructed as so: stat_bin(geom="text", aes(x = bins, y = ..count.., label = paste(round(100*(..count../sum(..count..)),1), "%", sep="") ) This works fine for an un-faceted plot. However, with facets, this sum(..count..) is summing over the entire collection of observations without regard for the facets. The plot below illustrates the issue---note that the percentages do not sum to 100% within a panel. Here the actually code for the figure above: g.invite.distro <- ggplot(data = df.exp) + geom_bar(aes(x = invite_bins)) + facet_wrap(~cat1, ncol=3) + stat_bin(geom="text", aes(x = invite_bins, y = ..count.., label = paste(round(100*(..count../sum(..count..)),1), "%", sep="") ), vjust = -1, size = 3) + theme_bw() + scale_y_continuous(limits = c(0, 3000)) UPDATE: As per request, here's a small example re-producing the issue: df <- data.frame(x = c('a', 'a', 'b','b'), f = c('c', 'd','d','d')) ggplot(data = df) + geom_bar(aes(x = x)) + stat_bin(geom = "text", aes( x = x, y = ..count.., label = ..count../sum(..count..)), vjust = -1) + facet_wrap(~f)
Update geom_bar requires stat = identity. Sometimes it's easier to obtain summaries outside the call to ggplot. df <- data.frame(x = c('a', 'a', 'b','b'), f = c('c', 'd','d','d')) # Load packages library(ggplot2) library(plyr) # Obtain summary. 'Freq' is the count, 'pct' is the percent within each 'f' m = ddply(data.frame(table(df)), .(f), mutate, pct = round(Freq/sum(Freq) * 100, 1)) # Plot the data using the summary data frame ggplot(data = m, aes(x = x, y = Freq)) + geom_bar(stat = "identity", width = .7) + geom_text(aes(label = paste(m$pct, "%", sep = "")), vjust = -1, size = 3) + facet_wrap(~ f, ncol = 2) + theme_bw() + scale_y_continuous(limits = c(0, 1.2*max(m$Freq)))
It is possible to create inset graphs?
I know that when you use par( fig=c( ... ), new=T ), you can create inset graphs. However, I was wondering if it is possible to use ggplot2 library to create 'inset' graphs. UPDATE 1: I tried using the par() with ggplot2, but it does not work. UPDATE 2: I found a working solution at ggplot2 GoogleGroups using grid::viewport().
Section 8.4 of the book explains how to do this. The trick is to use the grid package's viewports. #Any old plot a_plot <- ggplot(cars, aes(speed, dist)) + geom_line() #A viewport taking up a fraction of the plot area vp <- viewport(width = 0.4, height = 0.4, x = 0.8, y = 0.2) #Just draw the plot twice png("test.png") print(a_plot) print(a_plot, vp = vp) dev.off()
Much simpler solution utilizing ggplot2 and egg. Most importantly this solution works with ggsave. library(ggplot2) library(egg) plotx <- ggplot(mpg, aes(displ, hwy)) + geom_point() plotx + annotation_custom( ggplotGrob(plotx), xmin = 5, xmax = 7, ymin = 30, ymax = 44 ) ggsave(filename = "inset-plot.png")
Alternatively, can use the cowplot R package by Claus O. Wilke (cowplot is a powerful extension of ggplot2). The author has an example about plotting an inset inside a larger graph in this intro vignette. Here is some adapted code: library(cowplot) main.plot <- ggplot(data = mpg, aes(x = cty, y = hwy, colour = factor(cyl))) + geom_point(size = 2.5) inset.plot <- main.plot + theme(legend.position = "none") plot.with.inset <- ggdraw() + draw_plot(main.plot) + draw_plot(inset.plot, x = 0.07, y = .7, width = .3, height = .3) # Can save the plot with ggsave() ggsave(filename = "plot.with.inset.png", plot = plot.with.inset, width = 17, height = 12, units = "cm", dpi = 300)
I prefer solutions that work with ggsave. After a lot of googling around I ended up with this (which is a general formula for positioning and sizing the plot that you insert. library(tidyverse) plot1 = qplot(1.00*mpg, 1.00*wt, data=mtcars) # Make sure x and y values are floating values in plot 1 plot2 = qplot(hp, cyl, data=mtcars) plot(plot1) # Specify position of plot2 (in percentages of plot1) # This is in the top left and 25% width and 25% height xleft = 0.05 xright = 0.30 ybottom = 0.70 ytop = 0.95 # Calculate position in plot1 coordinates # Extract x and y values from plot1 l1 = ggplot_build(plot1) x1 = l1$layout$panel_ranges[[1]]$x.range[1] x2 = l1$layout$panel_ranges[[1]]$x.range[2] y1 = l1$layout$panel_ranges[[1]]$y.range[1] y2 = l1$layout$panel_ranges[[1]]$y.range[2] xdif = x2-x1 ydif = y2-y1 xmin = x1 + (xleft*xdif) xmax = x1 + (xright*xdif) ymin = y1 + (ybottom*ydif) ymax = y1 + (ytop*ydif) # Get plot2 and make grob g2 = ggplotGrob(plot2) plot3 = plot1 + annotation_custom(grob = g2, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) plot(plot3) ggsave(filename = "test.png", plot = plot3) # Try and make a weird combination of plots g1 <- ggplotGrob(plot1) g2 <- ggplotGrob(plot2) g3 <- ggplotGrob(plot3) library(gridExtra) library(grid) t1 = arrangeGrob(g1,ncol=1, left = textGrob("A", y = 1, vjust=1, gp=gpar(fontsize=20))) t2 = arrangeGrob(g2,ncol=1, left = textGrob("B", y = 1, vjust=1, gp=gpar(fontsize=20))) t3 = arrangeGrob(g3,ncol=1, left = textGrob("C", y = 1, vjust=1, gp=gpar(fontsize=20))) final = arrangeGrob(t1,t2,t3, layout_matrix = cbind(c(1,2), c(3,3))) grid.arrange(final) ggsave(filename = "test2.png", plot = final)
'ggplot2' >= 3.0.0 makes possible new approaches for adding insets, as now tibble objects containing lists as member columns can be passed as data. The objects in the list column can be even whole ggplots... The latest version of my package 'ggpmisc' provides geom_plot(), geom_table() and geom_grob(), and also versions that use npc units instead of native data units for locating the insets. These geoms can add multiple insets per call and obey faceting, which annotation_custom() does not. I copy the example from the help page, which adds an inset with a zoom-in detail of the main plot as an inset. library(tibble) library(ggpmisc) p <- ggplot(data = mtcars, mapping = aes(wt, mpg)) + geom_point() df <- tibble(x = 0.01, y = 0.01, plot = list(p + coord_cartesian(xlim = c(3, 4), ylim = c(13, 16)) + labs(x = NULL, y = NULL) + theme_bw(10))) p + expand_limits(x = 0, y = 0) + geom_plot_npc(data = df, aes(npcx = x, npcy = y, label = plot)) Or a barplot as inset, taken from the package vignette. library(tibble) library(ggpmisc) p <- ggplot(mpg, aes(factor(cyl), hwy, fill = factor(cyl))) + stat_summary(geom = "col", fun.y = mean, width = 2/3) + labs(x = "Number of cylinders", y = NULL, title = "Means") + scale_fill_discrete(guide = FALSE) data.tb <- tibble(x = 7, y = 44, plot = list(p + theme_bw(8))) ggplot(mpg, aes(displ, hwy, colour = factor(cyl))) + geom_plot(data = data.tb, aes(x, y, label = plot)) + geom_point() + labs(x = "Engine displacement (l)", y = "Fuel use efficiency (MPG)", colour = "Engine cylinders\n(number)") + theme_bw() The next example shows how to add different inset plots to different panels in a faceted plot. The next example uses the same example data after splitting it according to the century. This particular data set once split adds the problem of one missing level in one of the inset plots. As these plots are built on their own we need to use manual scales to make sure the colors and fill are consistent across the plots. With other data sets this may not be needed. library(tibble) library(ggpmisc) my.mpg <- mpg my.mpg$century <- factor(ifelse(my.mpg$year < 2000, "XX", "XXI")) my.mpg$cyl.f <- factor(my.mpg$cyl) my_scale_fill <- scale_fill_manual(guide = FALSE, values = c("red", "orange", "darkgreen", "blue"), breaks = levels(my.mpg$cyl.f)) p1 <- ggplot(subset(my.mpg, century == "XX"), aes(factor(cyl), hwy, fill = cyl.f)) + stat_summary(geom = "col", fun = mean, width = 2/3) + labs(x = "Number of cylinders", y = NULL, title = "Means") + my_scale_fill p2 <- ggplot(subset(my.mpg, century == "XXI"), aes(factor(cyl), hwy, fill = cyl.f)) + stat_summary(geom = "col", fun = mean, width = 2/3) + labs(x = "Number of cylinders", y = NULL, title = "Means") + my_scale_fill data.tb <- tibble(x = c(7, 7), y = c(44, 44), century = factor(c("XX", "XXI")), plot = list(p1, p2)) ggplot() + geom_plot(data = data.tb, aes(x, y, label = plot)) + geom_point(data = my.mpg, aes(displ, hwy, colour = cyl.f)) + labs(x = "Engine displacement (l)", y = "Fuel use efficiency (MPG)", colour = "Engine cylinders\n(number)") + scale_colour_manual(guide = FALSE, values = c("red", "orange", "darkgreen", "blue"), breaks = levels(my.mpg$cyl.f)) + facet_wrap(~century, ncol = 1)
In 2019, the patchwork package entered the stage, with which you can create insets easily by using the inset_element() function: require(ggplot2) require(patchwork) gg1 = ggplot(iris, aes(Sepal.Length, Sepal.Width)) + geom_point() gg2 = ggplot(iris, aes(Sepal.Length)) + geom_density() gg1 + inset_element(gg2, left = 0.65, bottom = 0.75, right = 1, top = 1)