如何在 R 中使用 facet_grid 在每个单独的图中添加标题?

How to add captions in each individual plot using facet_grid in R?

我正在使用 facet_grid 绘制多个绘图,我想知道如何在每个单独的绘图中添加一些额外信息作为标题。

我设法在每个图的标题中添加信息(为了添加 Kruskal-Wallis p-value),但我想在每个图下方添加更多信息(作为标题) .

这是一个可重现的例子:

library(ggplot2)
library(dplyr)
set.seed(1234)
Gene <- floor(runif(25, min=0, max=101))
Age <- floor(runif(25, min=18, max=75))
Group <- c("Group1", "Group1", "Group3", "Group2", "Group1", "Group3", "Group2", "Group2", "Group2", "Group1", "Group1", "Group3", "Group1", "Group2", "Group1", "Group2", "Group3", "Group1", "Group3", "Group3", "Group2", "Group1", "Group3", "Group3","Group2")


df <- data.frame(Gene, Age, Group)
df$Group <- as.factor(df$Group)

mybreaks <- seq(min(df$Age)-1, to=max(df$Age)+10, by=10)
df$groups_age <- cut(df$Age, breaks = mybreaks, by=10)

bp <- ggplot(df, aes(x=groups_age, y=Gene, group=groups_age)) + 
  geom_boxplot(aes(fill=groups_age)) + 
  facet_grid(. ~ Group)

bp

pval <- df %>%
  group_by(Group) %>%
  summarize(Kruskal_pvalue = kruskal.test(Gene ~ groups_age)$p.value)

# This is to create new labels for the facetgrid where we can show the phenotype and the KW pvalue.
labels <- c(paste('Group 1\n KW p-val:', signif(subset(pval$Kruskal_pvalue, pval$Group=="Group1"), digits = 3)),
            paste('Group 2\n  KW p-val:', signif(subset(pval$Kruskal_pvalue, pval$Group=="Group2"), digits = 3)),
            paste('Group 3\n  KW p-val:', signif(subset(pval$Kruskal_pvalue, pval$Group=="Group3"), digits = 3)))

df$KW <- factor(df$Group, levels = levels(df$Group), labels = labels)


bp <- ggplot(df, aes(x=groups_age, y=Gene, group=groups_age)) + 
  geom_boxplot(aes(fill=groups_age)) + 
  facet_grid(. ~ KW) +
  theme(legend.position="none")
bp

这是上面代码的结果:

如果我想添加有关每个图的信息作为标题,这是我能想到的唯一方法。

df_group1 <- df[df$Group == "Group1",]
df_group2 <- df[df$Group == "Group2",]
df_group3 <- df[df$Group == "Group3",]

myfunction <- function(DF){
  df <- as.data.frame(table(DF$groups_age))
  # This is to add  ": n = " to the first column
  df$Var1 <- paste(df$Var1, ": n = ", sep = "")
  # We join both columns in one to have the result together.
  df$X <- paste(df$Var1, df$Freq)
  # We save that column into a variable 
  vec <-  df[["X"]]
  return(vec)
}

numb_group1 <- myfunction(df_group1)
numb_group1 <- paste(numb_group1, collapse = "; ") 

numb_group2 <- myfunction(df_group2)
numb_group2 <- paste(numb_group2, collapse = "; ") 

numb_group3 <- myfunction(df_group3)
numb_group3 <- paste(numb_group3, collapse = "; ") 

numb_all <- c(numb_group1, numb_group2, numb_group3)


bp <- bp + labs(caption = paste0("Group 1: n = ", nrow(subset(df, df$Group=="Group1")), 
                                 "\n", 
                                 "           Groups: ", numb_all[1],
                                 "\n",
                                 "\n",
                                 "Group 2: n = ", nrow(subset(df, df$Group=="Group2")), 
                                 "\n",
                                 "           Groups: ", numb_all[2],
                                 "\n",
                                 "\n",
                                 "Group 3: n = ", nrow(subset(df, df$Group=="Group3")), 
                                 "\n",
                                 "           Groups:", numb_all[3]
)) +  theme(legend.position="none",
            plot.caption = element_text(hjust = 0, face= "italic")) #Default is hjust=1
bp

这是它的样子:

但是,我想改进我的代码并找到另一种方法(如果存在的话)将每个信息放在每个单独的图下方。

有人知道它能做什么吗?

非常感谢

Generally-speaking multi-faceted 地块上的地块标题:

  • 如果您想要单个标题,即低于所有情节,您应该使用theme(plot.caption = ...) .

  • 如果您希望相同的标题出现在每个方面下方,您可以使用[=14] =] 并关闭剪辑。

  • 如果您想要不同的标题出现在每个方面下方,您需要一些能够映射到数据集(因此您可以为每个构面指定不同的文本)。在这种情况下,我建议使用 geom_text() 并巧妙地格式化以适合标题。

  • 每个图都有不同标题的替代方法是创建带有标题的单独图,并通过 grid.arrange()patchwork 或 [=18= 将它们 link 在一起]...

这里是使用geom_text()mtcars的第三种情况的例子。我希望你能把它应用到你自己的数据集上。

基本剧情

这是我们将用于添加标题的基本情节:

library(ggplot2)
p <- ggplot(mtcars, aes(qsec, mpg)) + geom_point() +
        facet_wrap(~cyl)

字幕数据框

要制作标题图,我们首先需要为每个方面定义文本。最好在与批量数据分开的 单独数据框 中执行此操作。这确保了文本 geom 没有任何过度绘制(多次在同一位置绘制),因为 数据框中的每个观察绘制一个文本 geom。这是我们的字幕数据框:

caption_df <- data.frame(
  cyl = c(4,6,8),
  txt = c("carb=4", "carb=6", "carb=8, OMG!")
)

带字幕绘图

为了制作情节,我们需要对情节进行一些调整。

  • 添加标题。 添加 geom_text() 并映射到 caption_df。我们将映射文本,但位置将在 x 和 y 中固定。 x 值设置为我们原始数据的最小值,但我们也可以手动设置。 y 值需要设置为 低于原始图 .

    的值
  • 限制绘图的范围。 由于我们将文本 geom 放置在 原始绘图区域下方,如果我们没有限制绘图区域的限制,ggplot2 只会扩展 y 限制以适应新文本。我们需要保持原来的 y 限制,以确保我们添加的 geom_text() 的 y 值保持 低于 这个区域。

  • 关闭剪裁。为了真正看到新的字幕,您需要关闭剪裁。您可以在任何 coord_*() 函数中执行此操作,因此我们将使用 coord_cartesian() 来执行此操作并设置 y 限制。

  • 增加下边距。为了确保我们在最终图像中看到标题,我们需要通过 theme(plot.margin=...) 增加图下方的边距.

这是所有这些的最终结果。

ggplot(mtcars, aes(qsec, mpg)) + geom_point() + facet_wrap(~cyl) +
  coord_cartesian(clip="off", ylim=c(10, 40)) +
  geom_text(
    data=caption_df, y=5, x=min(mtcars$qsec),
    mapping=aes(label=txt), hjust=0,
    fontface="italic", color="red"
  ) +
  theme(plot.margin = margin(b=25))

在使用 facet_grid 和字幕尝试了很多事情之后,我创建了一些帖子,在这些帖子中我得到了非常好的答案,可以帮助解决这个问题的人。

这是主要的解决方案:

虽然这在我尝试自动化代码时给我带来了一些问题: and

但是,我意识到对于这种情况,最好将观测值的数量放在每个箱线图上方。它更直观,不需要很多代码。

myFreqs <- df %>%  
  group_by(Group, groups_age) %>% 
  summarise(Freq = n()) 
myFreqs 


bp + stat_summary(geom = 'text', label = paste("n = ", myFreqs$Freq), fun = max, vjust = -1, position = position_dodge(width=0.7))