为合并的ggplots添加一个公共图例

207

我有两个ggplots,我用grid.arrange将它们水平对齐。我查看了许多论坛帖子,但是我尝试的所有命令似乎都已经更新并命名为其他名称。

我的数据长这样:

# Data plot 1                                   
        axis1     axis2   
group1 -0.212201  0.358867
group2 -0.279756 -0.126194
group3  0.186860 -0.203273
group4  0.417117 -0.002592
group1 -0.212201  0.358867
group2 -0.279756 -0.126194
group3  0.186860 -0.203273
group4  0.186860 -0.203273

# Data plot 2   
        axis1     axis2
group1  0.211826 -0.306214
group2 -0.072626  0.104988
group3 -0.072626  0.104988
group4 -0.072626  0.104988
group1  0.211826 -0.306214
group2 -0.072626  0.104988
group3 -0.072626  0.104988
group4 -0.072626  0.104988

#And I run this:
library(ggplot2)
library(gridExtra)


groups=c('group1','group2','group3','group4','group1','group2','group3','group4')

x1=data1[,1]
y1=data1[,2]

x2=data2[,1]
y2=data2[,2]

p1=ggplot(data1, aes(x=x1, y=y1,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)

p2=ggplot(data2, aes(x=x2, y=y2,colour=groups)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)

#Combine plots
p3=grid.arrange(
p1 + theme(legend.position="none"), p2+ theme(legend.position="none"), nrow=1, widths = unit(c(10.,10), "cm"), heights = unit(rep(8, 1), "cm")))

我应该如何从这些图表中提取图例并将其添加到组合图的底部/中心?


4
我偶尔会遇到这个问题。如果你不想处理图形,我知道的最简单的解决方案就是保存一个带有图例的图表,然后使用Photoshop / Ilustrator将其粘贴到空白图例图表上。我知道这并不是很优雅,但很实用、快捷和容易操作。 - Stephen Henderson
1
@StephenHenderson 这是一个答案。可以使用图形编辑器进行细节处理或后期制作。 - Brandon Bertelsen
9个回答

187

您还可以使用ggpubr包中的ggarrange函数,并设置 "common.legend = TRUE":

library(ggpubr)

dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity) 

ggarrange(p1, p2, p3, p4, ncol=2, nrow=2, common.legend = TRUE, legend="bottom")

在此输入图片描述


1
这个在shiny应用程序(或flexdashboard)中使用renderPlot()可能不起作用,这是可能的吗? 在普通的R脚本中使用普通图表时,它可以完美地工作。但是当我使用renderPlot()创建的图表在我的flexdashboard中进行相同的操作时,什么也没有出现。 - Tingolfin
3
谢谢您 - 我认为这是我所寻找的最简单的解决方案。 - Komal Rathi
这太棒了!谢谢你! - yanes
1
@Tingolfin,有时我需要将print(ggarrangeobject)和我的ggarrange对象包装在一起,这样它就可以被其他一些函数绘制了,这可能类似于您的renderPlot()的解决方案? - Brandon
如果我有两个图例:一个对所有绘图都是通用的(线型),另一个则不是(颜色)?我只想让线型图例对所有图表都是通用的。 - Alvaro Morales
显示剩余3条评论

124

更新 2021 年 3 月

这个回答仍然有一些价值,但大部分已经成为历史性的。自本回答发布以来,随着包的使用更好的解决方案已经出现。您应该考虑下面发布的更新答案。

更新 2015 年 2 月

请查看 Steven 在下面发布的回答:Steven's answer below


df1 <- read.table(text="group   x     y   
group1 -0.212201  0.358867
group2 -0.279756 -0.126194
group3  0.186860 -0.203273
group4  0.417117 -0.002592
group1 -0.212201  0.358867
group2 -0.279756 -0.126194
group3  0.186860 -0.203273
group4  0.186860 -0.203273",header=TRUE)

df2 <- read.table(text="group   x     y   
group1  0.211826 -0.306214
group2 -0.072626  0.104988
group3 -0.072626  0.104988
group4 -0.072626  0.104988
group1  0.211826 -0.306214
group2 -0.072626  0.104988
group3 -0.072626  0.104988
group4 -0.072626  0.104988",header=TRUE)


library(ggplot2)
library(gridExtra)

p1 <- ggplot(df1, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8) + theme(legend.position="bottom")

p2 <- ggplot(df2, aes(x=x, y=y,colour=group)) + geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8)

#extract legend
#https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
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)}

mylegend<-g_legend(p1)

p3 <- grid.arrange(arrangeGrob(p1 + theme(legend.position="none"),
                         p2 + theme(legend.position="none"),
                         nrow=1),
             mylegend, nrow=2,heights=c(10, 1))

这是最终的图表: 两个共同图例的图表


3
两个答案都指向同一个维基页面,该页面可以随着ggplot2的新版本而更新,并修复代码。 - baptiste
六年多后,这个答案解决了我的问题。谢谢! - SPK.z
1
这对于一些/大多数人来说可能很简单,但我没有立即理解,所以想发表评论。如果你想要在图形顶部(而不是下方)放置常见的图例,你需要做的就是切换参数。在上面的示例中,我的图例在arrangeGrob()之前。你还需要颠倒高度(即heights=c(1,10))。 - ljh2001
有没有一种方法来使底部的图例居中或向右移动一点? - wolfsatthedoor
@wolfsatthedoor theme(legend.position="bottom", legend.justification = c(0.75, 0.5)) 中的 0.5 值为中心,更大的值会向右或向下移动。 - Roland
我正在尝试实现这段代码,但在 g_legend() 上出现了错误:"Error in tmp$grobs[[leg]] : attempt to select less than one element in get1index"。可能的问题是什么? - user1916067

91

一种新的、有吸引力的解决方案是使用{{patchwork}}。语法非常简单:

library(ggplot2)
library(patchwork)

p1 <- ggplot(df1, aes(x = x, y = y, colour = group)) + 
  geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)
p2 <- ggplot(df2, aes(x = x, y = y, colour = group)) + 
  geom_point(position = position_jitter(w = 0.04, h = 0.02), size = 1.8)

combined <- p1 + p2 & theme(legend.position = "bottom")
combined + plot_layout(guides = "collect")

这段内容是由reprex包(v0.2.1)于2019年12月13日创建的。


16
如果你稍微调整命令的顺序,就可以在一行中完成这个操作:combined <- p1 + p2 + plot_layout(guides = "collect") + theme(legend.position = "bottom") - mlcyo
为什么Patchwork现在不会将图例放置在图形之间?它似乎在这个例子中已经这么做了,但是当我尝试使用最新版本的Patchwork时,它仍然将图例放置在左边界而不是中心。谢谢。 - jaydoc
使用最新的 CRAN 版本 Patchwork (1.1.1),我仍然得到与此处答案相同的输出。至于开发版本,我不确定,但我想你可以在 repo 中打开一个问题,并提供一个 reproducible example 来询问你得到的是否是预期结果。我在 NEWS 文件中没有看到任何关于此事的信息,所以我认为可能并非如此。 - MSR
在Patchwork中,是否可以将两个图的x轴标签合并为一个共同的标签? - Nip
1
@Nip:不像传说那样简单。请参见此问题 - MSR

67

Roland的回答需要更新。请参见:https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs

此方法已更新至ggplot2 v1.0.0。

library(ggplot2)
library(gridExtra)
library(grid)


grid_arrange_shared_legend <- function(...) {
    plots <- list(...)
    g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
    legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
    lheight <- sum(legend$height)
    grid.arrange(
        do.call(arrangeGrob, lapply(plots, function(x)
            x + theme(legend.position="none"))),
        legend,
        ncol = 1,
        heights = unit.c(unit(1, "npc") - lheight, lheight))
}

dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data=dsamp, colour=clarity)
p2 <- qplot(cut, price, data=dsamp, colour=clarity)
p3 <- qplot(color, price, data=dsamp, colour=clarity)
p4 <- qplot(depth, price, data=dsamp, colour=clarity)
grid_arrange_shared_legend(p1, p2, p3, p4)
请注意缺少ggplot_gtableggplot_build。改用ggplotGrob。 这个示例比上面的解决方案更为复杂,但对我仍然解决了问题。

10
你好,我有6个图表,想将它们以2行×3列的方式排列,并在顶部绘制图例。请问如何修改函数grid_arrange_shared_legend?谢谢! - just_rookie
4
@just_rookie,你找到解决方案了吗?如何更改该函数使得可以使用不同的ncol和nrow排列方式而不仅仅是'ncol=1'? - Giuseppe
对于遇到和我一样问题的任何人,这里有一个解决方法:https://dev59.com/R2cs5IYBdhLWcg3w64Ka - HanniBaL90
1
这里有很棒的内容。您知道如何为所有绘图添加一个单一的标题吗? - Pertinax
如果图表共享某些图例值,而其他图表则不同,怎么办?我不想为所有图表使用一个图例,因为其他图表可能有不同的图例。 - Mariya
显示剩余2条评论

28

我建议使用cowplot。以下是它们的R vignette

# load cowplot
library(cowplot)

# down-sampled diamonds data set
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]

# Make three plots.
# We set left and right margins to 0 to remove unnecessary spacing in the
# final plot arrangement.
p1 <- qplot(carat, price, data=dsamp, colour=clarity) +
   theme(plot.margin = unit(c(6,0,6,0), "pt"))
p2 <- qplot(depth, price, data=dsamp, colour=clarity) +
   theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")
p3 <- qplot(color, price, data=dsamp, colour=clarity) +
   theme(plot.margin = unit(c(6,0,6,0), "pt")) + ylab("")

# arrange the three plots in a single row
prow <- plot_grid( p1 + theme(legend.position="none"),
           p2 + theme(legend.position="none"),
           p3 + theme(legend.position="none"),
           align = 'vh',
           labels = c("A", "B", "C"),
           hjust = -1,
           nrow = 1
           )

# extract the legend from one of the plots
# (clearly the whole thing only makes sense if all plots
# have the same legend, so we can arbitrarily pick one.)
legend_b <- get_legend(p1 + theme(legend.position="bottom"))

# add the legend underneath the row we made earlier. Give it 10% of the height
# of one plot (via rel_heights).
p <- plot_grid( prow, legend_b, ncol = 1, rel_heights = c(1, .2))
p

底部带图例的组合图


这是唯一的方法,可以使用legend_b()在我的图中放置一个手动图例,并使用annotate_figure(ggarrange())。非常感谢你,上帝保佑你! - Jean Karlos

13

@Giuseppe,您可能想考虑使用此方法来灵活指定图形的排列方式(修改自这里):

library(ggplot2)
library(gridExtra)
library(grid)

grid_arrange_shared_legend <- function(..., nrow = 1, ncol = length(list(...)), position = c("bottom", "right")) {

  plots <- list(...)
  position <- match.arg(position)
  g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
  legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
  lheight <- sum(legend$height)
  lwidth <- sum(legend$width)
  gl <- lapply(plots, function(x) x + theme(legend.position = "none"))
  gl <- c(gl, nrow = nrow, ncol = ncol)

  combined <- switch(position,
                     "bottom" = arrangeGrob(do.call(arrangeGrob, gl),
                                            legend,
                                            ncol = 1,
                                            heights = unit.c(unit(1, "npc") - lheight, lheight)),
                     "right" = arrangeGrob(do.call(arrangeGrob, gl),
                                           legend,
                                           ncol = 2,
                                           widths = unit.c(unit(1, "npc") - lwidth, lwidth)))
  grid.newpage()
  grid.draw(combined)

}

额外的参数nrowncol控制排列的图形布局:

Translated:

额外的参数nrowncol控制排列的图形布局:

dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
p1 <- qplot(carat, price, data = dsamp, colour = clarity)
p2 <- qplot(cut, price, data = dsamp, colour = clarity)
p3 <- qplot(color, price, data = dsamp, colour = clarity)
p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 1, ncol = 4)
grid_arrange_shared_legend(p1, p2, p3, p4, nrow = 2, ncol = 2)

输入图像描述 输入图像描述


和另一个解决方案一样,我尝试了它,它的效果很好,但是当我打印时,我得到了两个PDF页面,而不是一个,第一个页面是空白的,而后面的页面包含我的图形,为什么会出现这种情况?谢谢。 - HanniBaL90
对于任何遇到和我一样问题的人,这里有一个解决方法:https://dev59.com/R2cs5IYBdhLWcg3w64Ka - HanniBaL90
有人能给我解释一下这个解决方案吗?我该如何将图例放在顶部而不是底部?谢谢。 - HanniBaL90

9
如果你在两个图中都要画相同的变量,最简单的方法是将数据框合并成一个,然后使用facet_wrap。 对于你的例子:
big_df <- rbind(df1,df2)

big_df <- data.frame(big_df,Df = rep(c("df1","df2"),
times=c(nrow(df1),nrow(df2))))

ggplot(big_df,aes(x=x, y=y,colour=group)) 
+ geom_point(position=position_jitter(w=0.04,h=0.02),size=1.8) 
+ facet_wrap(~Df)

图1

以下是钻石数据集的另一个示例。这表明,即使您的图之间仅有一个变量是共同的,您也可以使其正常工作。

diamonds_reshaped <- data.frame(price = diamonds$price,
independent.variable = c(diamonds$carat,diamonds$cut,diamonds$color,diamonds$depth),
Clarity = rep(diamonds$clarity,times=4),
Variable.name = rep(c("Carat","Cut","Color","Depth"),each=nrow(diamonds)))

ggplot(diamonds_reshaped,aes(independent.variable,price,colour=Clarity)) + 
geom_point(size=2) + facet_wrap(~Variable.name,scales="free_x") + 
xlab("")

Plot 2

第二个例子中唯一棘手的问题是当你将所有变量合并到一个数据框中时,因子变量会被强制转换为数字。因此,理想情况下,只有当你感兴趣的所有变量都是相同类型时,才应该这样做。


4

@Guiseppe:

我对于Grobs等内容一无所知,但我拼凑出了一个两个图的解决方案,应该可以扩展到任意数量,但它并不是一个高效的函数:

plots <- list(p1, p2)
g <- ggplotGrob(plots[[1]] + theme(legend.position="bottom"))$grobs
legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
lheight <- sum(legend$height)
tmp <- arrangeGrob(p1 + theme(legend.position = "none"), p2 + theme(legend.position = "none"), layout_matrix = matrix(c(1, 2), nrow = 1))
grid.arrange(tmp, legend, ncol = 1, heights = unit.c(unit(1, "npc") - lheight, lheight))

4
如果两个图的图例相同,则可以使用 grid.arrange(假设您希望将图例与两个图垂直或水平对齐)来解决。只需保留底部或右侧图的图例,而省略另一个图的图例即可。但是,向一个图添加图例会改变其相对于另一个图的大小。为避免此问题,请使用 heights 命令手动调整它们的大小并保持相同大小。您甚至可以使用 grid.arrange 制作共同的坐标轴标题。请注意,这将需要除了 library(gridExtra) 之外还要加载 library(grid) 。对于垂直方向的图:
y_title <- expression(paste(italic("E. coli"), " (CFU/100mL)"))

grid.arrange(arrangeGrob(p1, theme(legend.position="none"), ncol=1), 
   arrangeGrob(p2, theme(legend.position="bottom"), ncol=1), 
   heights=c(1,1.2), left=textGrob(y_title, rot=90, gp=gpar(fontsize=20)))

这是我正在处理的一个项目类似图表的结果: enter image description here

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