在ggplot2中标注单个面的文本

224

我想用以下代码在图的最后一个 facet 上注释一些文本:

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)

在这里输入图片描述

但是这段代码会为每个分面(facet)上的文本进行注释。我该如何只获取一个分面(facet)上的注释文本?


1
我认为这个功能尚未实现(https://github.com/hadley/ggplot2/issues/486),所以我怀疑您将不得不采用构建一个包含文本和分面变量列的数据框的可靠方法。 - joran
6个回答

201

函数annotate()将相同的标签添加到具有面板的绘图中。如果意图是为每个面板添加不同的注释,或者仅为某些面板添加注释,则必须使用geom_而不是annotate()。要使用geom,例如geom_text(),我们需要组装一个数据框,其中包含标签文本的一列以及要映射到其他美学的变量的列,以及用于分面的变量。

通常你会这样做:

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

不完全指定因子变量也应该可以工作,但可能会产生一些警告。

enter image description here


8
通常情况下,这是因为您错误地告诉 ggplot 在原始数据框中的每一行上绘制标签的副本(包括点、线等)。请注意,我向 geom_text 传递了一个单独的数据框,其中只有一行。 - joran
4
好的,我知道了-谢谢。如果您想在多面板图上放置3个不同的标签怎么办?我尝试使用与多面板数目相同的行数创建数据框,并在每一行中使用唯一的标签。也许我应该将这作为一个单独的问题来解决。 - Margaret
9
谢谢你的解决方案。我想知道是否也可以使用annotate()来实现这个? - polarise
2
@user3420448 相同的,您只需要为每个分面变量指定值。 - joran
显示剩余5条评论

189

annotate()函数会将相同的标签添加到具有不同分面的绘图中的所有面板。如果意图是为每个面板添加不同的注释,或仅向某些面板添加注释,则需要使用几何而不是annotate()。要使用像geom_text()这样的几何,我们需要组装一个数据框,其中一个列包含标签文本,其他美学映射的变量的列以及用于分面的变量(s)。此答案为facet_wrap()facet_grid()都提供了示例。

以下是没有文字注释的绘图:

library(ggplot2)

p <- ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  facet_grid(. ~ cyl) +
  theme(panel.spacing = unit(1, "lines"))
p

没有文本注释的图表

我们创建一个额外的数据框来保存文本注释:

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
)

带有文本标注的绘图

或者,我们可以手动指定每个标签的位置:

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

手动定位文本标签的绘图

我们也可以在两个方面上标记绘图:

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
  )

两个变量的面板图

注释:

  • 您可以使用-InfInf来定位面板边缘的文本。
  • 您可以使用hjustvjust来调整文本对齐方式。
  • 文本标签数据框dat_text应该有一列与您的facet_grid()facet_wrap()配合使用。

20
这个答案比被采纳的答案更好(显然两者之间相差了5年),因为它清晰地讲解了每一步。此外,解释更加清晰明了。 - Brandon
1
如果您想将文本添加到多行,请确保您在文本“data.frame”中的colnames()与您即将绘制的数据的列名匹配。 - Kots
当我尝试对我的某个方面进行单独操作时,注释会显示出来,但实际的点却消失了(或被遮挡了?)。 - Ben G
Ben G,你可以考虑发布一篇新帖子来分享你的代码和图表。 - Kamil Slowikowski

65
如果有人正在寻找一种简单的方法来为报告或出版物标记方面,egg (CRAN) 包具有非常好用的 tag_facet()tag_facet_outside() 函数。
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_facet(p)

注意: 如果你想保留条纹文本和背景,请尝试在theme中添加strip.textstrip.background,或从原始的tag_facet()函数中删除theme(strip.text = element_blank(), strip.background = element_blank())

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

将顶部右对齐并使用罗马数字

tag_facet(p, x = Inf, y = Inf, 
          hjust = 1.5,
          tag_pool = as.roman(1:nlevels(factor(mtcars$am))))

将底部左对齐并使用大写字母

tag_facet(p, 
          x = -Inf, y = -Inf, 
          vjust = -1,
          open = "", close = ")",
          tag_pool = LETTERS)

定义自己的标签

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)

标签外

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)

编辑:使用stickylabeller包添加另一种替代方案

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

reprex包(v0.2.1)创建


3
消息来源表示:在ggplot中添加一个虚拟文本层以标记分面,并将分面条设置为空白。因此,如果您有自定义的分面标签条,您不想丢失,请通过取消tag_facet脚本中的strip.text = element_blank()来进行编辑。 - CrunchyTopping
1
回答我上面的问题...这篇帖子很好地解释了如何保留条带:https://dev59.com/j7Pma4cB1Zd3GeqPvsT8#56064130 - efrem
1
这是一个惊人的回答 @Tung!非常有帮助! - Ecg
非常感谢 @Ecg :) - Tung
1
@Tung 这是对不同问题的一个很好的回答,因此它应该有自己的问题,而不是留在这里相对隐蔽。 - Pedro J. Aphalo
显示剩余2条评论

25

我认为上面答案中的lab="Text"是无用的,下面的代码也可以。

ann_text <- data.frame(mpg = 15,wt = 5,
                       cyl = factor(8,levels = c("4","6","8")))
p + geom_text(data = ann_text,label = "Text" )

但是如果你想在不同的子图中以不同的标签进行标记,可以按照以下方式操作:

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

8
与已经给出和接受的答案相比,你应该深入解释你的解决方案提供了什么。 - Sascha Wolf
3
谢谢,这对标记不同的子图很有用。 - Deathkill14
由于某种原因,当我这样做时,它会为因子cyl=2和cyl=3添加(空)面。 - emudrak

16
稍微扩展一下joran的出色答案,以澄清标签数据框是如何工作的。
你可以将"mpg"和"wt"分别视为x和y坐标(我发现保持原始变量名比重新命名它们更容易跟踪,就像Kamil的同样出色的回答中所示)。 你需要每个标签一个行,其中"cyl"列显示每个行与哪个面相对应。
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)

带标签的图表


3
我之前不知道egg包,所以这里提供了一个基于ggplot2包的解决方案。
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)

1
这个解决方案相比之前的解决方案有什么优势? - Julien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接