在R中使用ggplot()时编写图例手册

4

我有以下的绘图penter image description here

我希望该图例看起来像这个图:enter image description here

因为p是由两个不同的绘图p1p2组合而成的,我使用了:

p<- grid.arrange(p1, p2, ncol = 3, widths = c(3,5,0))

只有p2的图例被保留下来(不包括“Collar ID”=41365´,这是我想添加的内容)。

因此,我认为最简单的方法是手动创建一个图例。如果需要的话,我使用下面的脚本创建了 p


df1 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Time,~Collar,
                      0.242, 0.173, 0.329, "Day","41361´",
                      0.216, 0.152, 0.296, "Night","41361´")


df2 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Time,~Collar,
                      0.290, 0.214, 0.381, "Day","41366´",
                      0.256, 0.186, 0.342, "Night","41366´")


df<-rbind(df1,df2)

dfnew <- df %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Time, Collar))

set.seed(2)

myjit <- ggproto("fixJitter", PositionDodge,
                 width = 0.6,
                 dodge.width = 0,
                 jit = NULL,
                 compute_panel =  function (self, data, params, scales) 
                 {

                   #Generate Jitter if not yet
                   if(is.null(self$jit) ) {
                     self$jit <-jitter(rep(0, nrow(data)), amount=self$dodge.width)
                   }

                   data <- ggproto_parent(PositionDodge, self)$compute_panel(data, params, scales)

                   data$x <- data$x + self$jit
                   #For proper error extensions
                   if("xmin" %in% colnames(data)) data$xmin <- data$xmin + self$jit
                   if("xmax" %in% colnames(data)) data$xmax <- data$xmax + self$jit
                   data
                 } )


p1<-ggplot(data = dfnew, aes(x = Time, y = Proportion, group=linegroup)) +
  geom_point(aes(shape = as.character(Collar)), size = 4, stroke = 0, 
             position = myjit)+
  geom_line(aes(group = linegroup),linetype = "dotted",size=1, position = myjit) +
  theme(axis.text=element_text(size=15),
        axis.title=element_text(size=20)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), width=0.3, size=1,
                position = myjit) + scale_shape_manual(values=c("41361´"=19,"41366´"=15)) +
  scale_color_manual(values = c("Day" = "black", 
                                "Night" = "black")) + labs(shape="Collar ID") + ylim(0.05, 0.4) + theme(legend.position = "none")

p1

df1 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Area,~Collar,
                      0.181, 0.148, 0.219, "LGCA","41361´",
                      0.289, 0.242 ,0.341 , "SNP","41361´")

df2 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Area,~Collar,
                      0.099, 0.096, 0.104, "LGCA","41365´",
                      0.224, 0.217 ,0.232 , "SNP","41365´")

df<-rbind(df1,df2)



dfnew <- df %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Area, Collar))

set.seed(2)

myjit <- ggproto("fixJitter", PositionDodge,
                 width = 0.6,
                 dodge.width = 0,
                 jit = NULL,
                 compute_panel =  function (self, data, params, scales) 
                 {

                   #Generate Jitter if not yet
                   if(is.null(self$jit) ) {
                     self$jit <-jitter(rep(0, nrow(data)), amount=self$dodge.width)
                   }

                   data <- ggproto_parent(PositionDodge, self)$compute_panel(data, params, scales)

                   data$x <- data$x + self$jit
                   #For proper error extensions
                   if("xmin" %in% colnames(data)) data$xmin <- data$xmin + self$jit
                   if("xmax" %in% colnames(data)) data$xmax <- data$xmax + self$jit
                   data
                 } )



p2<-ggplot(data = dfnew, aes(x = Area, y = Proportion, group=linegroup)) +
  geom_point(aes(shape = as.character(Collar)), size = 4, stroke = 0, 
             position = myjit)+
  geom_line(aes(group = linegroup),linetype = "dotted",size=1, position = myjit) +
  theme(axis.text=element_text(size=15),
        axis.title=element_text(size=20)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), width=0.3, size=1,
                position = myjit) + scale_shape_manual(values=c("41361´"=19,"41365´"=17)) + scale_size_manual(values=c(2,2)) +
  scale_color_manual(values = c("SNP" = "black", 
                                "LGCA" = "black")) + labs(shape="Collar ID") + ylim(0.05, 0.4) +
  theme(legend.text=element_text(size=18))+
  theme(legend.title = element_text(size=18))

#+ theme(legend.position = "none")


p2


p<- grid.arrange(p1, p2, ncol = 3, widths = c(3,5,0))

请告诉我是否有更好的解决方案。非常感谢您的帮助!

在发布之前,您已经付出了很多努力来解决这个问题,并深入挖掘了ggplot的内部。我认为这可能比这更简单:您可以做的一件事是确保两个数据子集,或者至少是用于制作图例的数据子集,具有您想要显示的所有因子水平。然后在创建图例的比例尺中添加 drop = F - camille
这里有一篇帖子,介绍了一些相关选项。如果需要统一因子水平,我建议使用forcats。还有几个包可以更轻松地排列ggplots:cowplot、patchwork、ggpubr、egg(我个人没有使用过)。 - camille
2个回答

3

以下是两种处理方法,避免了所有 ggproto 的繁琐。我优化了一些数据的创建过程,生成了一个名为 by_time 的数据框以及一个名为 by_area 的数据框,让你可以同时使用它们。我在每个数据框中添加了一个步骤,将 Collar 转换为因子,这将用于第二种方法。

library(dplyr)
library(ggplot2)
library(tidyr)

by_time <- tibble::tribble(
  ~Proportion, ~Lower,~Upper, ~Time,~Collar,
  0.242, 0.173, 0.329, "Day","41361´",
  0.216, 0.152, 0.296, "Night","41361´",
  0.290, 0.214, 0.381, "Day","41366´",
  0.256, 0.186, 0.342, "Night","41366´"
) %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Time, Collar),
         Collar = as.factor(Collar))

by_area <- tibble::tribble(
  ~Proportion, ~Lower,~Upper, ~Area,~Collar,
  0.181, 0.148, 0.219, "LGCA","41361´",
  0.289, 0.242 ,0.341 , "SNP","41361´",
  0.099, 0.096, 0.104, "LGCA","41365´",
  0.224, 0.217 ,0.232 , "SNP","41365´"
) %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Area, Collar),
         Collar = as.factor(Collar))

第一种方法是将数据框一起操作,使其呈现出你可以直接使用 facets 的形状。将每个数据框转换为长格式,其中用于分面的标记键是时间或区域。我将时间切换到第一级,使其更像你的。

##### with facets

df_long <- bind_rows(
  by_time %>% gather(key, value, Time),
  by_area %>% gather(key, value, Area)
) %>%
  mutate(key = forcats::fct_relevel(as.factor(key), "Time"))

head(df_long)
#> # A tibble: 6 x 9
#>   Proportion Lower Upper Collar   ymin  ymax linegroup    key   value
#>        <dbl> <dbl> <dbl> <chr>   <dbl> <dbl> <chr>        <fct> <chr>
#> 1      0.242 0.173 0.329 41361´ 0.069  0.571 Day 41361´   Time  Day  
#> 2      0.216 0.152 0.296 41361´ 0.064  0.512 Night 41361´ Time  Night
#> 3      0.290 0.214 0.381 41366´ 0.0760 0.671 Day 41366´   Time  Day  
#> 4      0.256 0.186 0.342 41366´ 0.07   0.598 Night 41366´ Time  Night
#> 5      0.181 0.148 0.219 41361´ 0.033  0.4   LGCA 41361´  Area  LGCA 
#> 6      0.289 0.242 0.341 41361´ 0.0470 0.63  SNP 41361´   Area  SNP

然后添加一些选项,使分面图看起来更符合您的要求,将分面条放在底部,看起来像轴标题。

ggplot(df_long, aes(x = value, y = Proportion, group = linegroup)) +
  geom_point(aes(shape = Collar), position = position_dodge(width = 0.4)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), position = position_dodge(width = 0.4), width = 0.2) +
  facet_wrap(vars(key), scales = "free_x", strip.position = "bottom") +
  labs(x = NULL) +
  theme(strip.placement = "outside",
        strip.background = element_blank())

如果出现问题,第二种方式是制作两个图,并使用cowplot::plot_grid排列它们。还有一些其他的包可以替代它(我喜欢patchwork)。这里的技巧是为一个图例添加所有因子水平;我将使用forcats::fct_expand将一个数据框的水平级别添加到另一个数据框中。由于面积图将位于右侧,因此需要调整因子级别并生成图例。在比例尺中设置drop = F以显示所有级别,即使它们不在数据中出现。

##### with plot_grid

p_time <- ggplot(by_time, aes(x = Time, y = Proportion, group = linegroup)) +
  geom_point(aes(shape = Collar), position = position_dodge(width = 0.4)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), position = position_dodge(width = 0.4), width = 0.2) +
  scale_y_continuous(limits = c(0, 0.6))

p_area <- by_area %>%
  mutate(Collar = forcats::fct_expand(as.factor(Collar), levels(by_time$Collar))) %>%
  ggplot(aes(x = Area, y = Proportion, group = linegroup)) +
  geom_point(aes(shape = Collar), position = position_dodge(width = 0.4)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), position = position_dodge(width = 0.4), width = 0.2) +
  scale_y_continuous(limits = c(0, 0.6)) +
  scale_shape_discrete(drop = F)

我还将y轴限制设置为两个图之间匹配 - 根据需要进行调整。

然后提取图例,在cowplot :: plot_grid 中将所有内容组合在一起,从各个图中删除图例。这样做的原因是,您可以使两个图的大小相同,而不必为图例腾出空间。

legend <- cowplot::get_legend(p_area)

cowplot::plot_grid(
  p_time + theme(legend.position = "none"), 
  p_area + theme(legend.position = "none"),
  legend,
  nrow = 1,
  rel_widths = c(1, 1, 0.4)
)


1
这看起来像是一个很棒的答案,一定花了不少时间才能整理出来。干得好,Camille! - halfer

1
如果您想添加一个包含所有3个id的图例,这是一种方法。您可以像之前创建图表一样,然后只需使用下面的g_legend函数来获取具有3个id的图例。然后从您的图表中删除图例,并将3个对象(2个图表和图例)放入grid.arrange中。我指定了一个layout_matrix来显示您可以进一步自定义每个对象占用多少空间。
df1 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Time,~Collar,
                       0.242, 0.173, 0.329, "Day","41361´",
                       0.216, 0.152, 0.296, "Night","41361´")


df2 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Time,~Collar,
                       0.290, 0.214, 0.381, "Day","41366´",
                       0.256, 0.186, 0.342, "Night","41366´")


df<-rbind(df1,df2)

dfnew <- df %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Time, Collar))



p1<-ggplot(data = dfnew, aes(x = Time, y = Proportion, group=linegroup)) +
  geom_point(aes(shape = as.character(Collar)), size = 4, stroke = 0)+
  geom_line(aes(group = linegroup),linetype = "dotted",size=1) +
  theme(axis.text=element_text(size=15),
        axis.title=element_text(size=20)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), width=0.3, size=1) +
  scale_shape_manual(values=c("41361´"=19,"41366´"=15)) +
  scale_color_manual(values = c("Day" = "black", 
                                "Night" = "black")) + 
  labs(shape="Collar ID") + ylim(0.05, 0.4) #+ theme(legend.position = "none")



df1 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Area,~Collar,
                       0.181, 0.148, 0.219, "LGCA","41361´",
                       0.289, 0.242 ,0.341 , "SNP","41361´")

df2 <- tibble::tribble(~Proportion, ~Lower,~Upper, ~Area,~Collar,
                       0.099, 0.096, 0.104, "LGCA","41365´",
                       0.224, 0.217 ,0.232 , "SNP","41365´")

df<-rbind(df1,df2)



dfnew <- df %>% 
  mutate(ymin = Proportion - Lower,
         ymax = Proportion + Upper,
         linegroup = paste(Area, Collar))




p2<-ggplot(data = dfnew, aes(x = Area, y = Proportion, group=linegroup)) +
  geom_point(aes(shape = as.character(Collar)), size = 4, stroke = 0)+
  geom_line(aes(group = linegroup),linetype = "dotted",size=1) +
  theme(axis.text=element_text(size=15),
        axis.title=element_text(size=20)) +
  geom_errorbar(aes(ymin = Lower, ymax = Upper), width=0.3, size=1) + scale_shape_manual(values=c("41361´"=19,"41365´"=17)) + scale_size_manual(values=c(2,2)) +
  scale_color_manual(values = c("SNP" = "black", 
                                "LGCA" = "black")) + labs(shape="Collar ID") + ylim(0.05, 0.4) +
  theme(legend.text=element_text(size=18))+
  theme(legend.title = element_text(size=18))

#+ theme(legend.position = "none")


g_legend<-function(a.gplot){
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  return(legend)}
#put in plot with 3 ids in the g_legend function
aleg <- g_legend(p1)

p1 <- p1+ theme(legend.position = "none")
p2 <- p2+ theme(legend.position = "none")



lay <- rbind(c(1,1,2,2,3),
             c(1,1,2,2,3))


gridExtra::grid.arrange(p1, p2,
                        #use layout matrix to set sizes
                        layout_matrix=lay,
                        # add legend
                        aleg)

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