Добавьте общую легенду для комбинированных графиков ggplots

У меня есть два графика 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")))

Как мне извлечь легенду из любого из этих графиков и добавить ее в нижнюю / центральную часть объединенного графика?


person jO.    schedule 30.11.2012    source источник
comment
Иногда у меня возникает такая проблема. Если вы не хотите обрабатывать график, самое простое решение, которое я знаю, - просто сохранить его с легендой, а затем использовать Photoshop / Ilustrator, чтобы вставить его на пустые графики легенды. Я знаю, что это неэлегантно, но практично быстро и легко.   -  person Stephen Henderson    schedule 30.11.2012
comment
@StephenHenderson Это ответ. Фасет или пост-обработка с помощью редактора gfx.   -  person Brandon Bertelsen    schedule 30.11.2012


Ответы (9)


Обновление 2021-март

Этот ответ все еще имеет некоторую, но в основном историческую ценность. Спустя годы, прошедшие с момента публикации, лучшие решения стали доступны в виде пакетов. Вам следует рассмотреть новые ответы, опубликованные ниже.

Обновление 2015-февраль

См. ответ Стивена ниже


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

Вот итоговый график: 2 графика с общей легендой

person Roland    schedule 30.11.2012
comment
оба ответа указывают на ту же страницу вики который можно обновлять по мере того, как новые версии ggplot2 нарушают код. - person baptiste; 23.06.2016
comment
Более чем через шесть лет этот ответ решил мою проблему. Спасибо! - person SPK.z; 10.01.2019
comment
Это может быть простым для некоторых / большинства людей, но я не сразу понял это, поэтому подумал, что прокомментирую. Если вы хотите, чтобы общая легенда находилась в верхней части графика (а не ниже), все, что вам нужно сделать, это переключить аргументы. В приведенном выше примере mylegend идет перед arrangeGrob(). Вам также необходимо изменить высоту (т.е. heights=c(1,10) - person ljh2001; 09.07.2020
comment
Есть ли способ центрировать нижнюю легенду или немного сдвинуть ее вправо? - person wolfsatthedoor; 25.03.2021
comment
@wolfsatthedoor theme(legend.position="bottom", legend.justification = c(0.75, 0.5)) Значение 0,5 - центр, большие значения сдвигаются вправо / вниз. - person Roland; 26.03.2021
comment
Я пытаюсь реализовать этот код, но получаю сообщение об ошибке в g_legend (): Ошибка в tmp $ grobs [[leg]]: попытаться выбрать менее одного элемента в get1index. В чем может быть проблема? - person user1916067; 12.04.2021

Вы также можете использовать ggarrange из ggpubr и установите" 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")

введите описание изображения здесь

person Huiyan Wan    schedule 27.10.2017
comment
Возможно ли, что это не работает внутри блестящего приложения (или гибкой панели инструментов) с помощью renderPlot ()? Он отлично работает в обычном сценарии R с обычными графиками. Но когда я делаю то же самое с графиками, созданными с помощью renderPlot () на моей гибкой панели управления, ничего не появляется. - person Tingolfin; 26.01.2018
comment
Спасибо за это - я думаю, что это было самое простое решение для того, что я искал. - person Komal Rathi; 01.02.2018
comment
Это круто! Спасибо! - person yanes; 02.05.2018
comment
@Tingolfin Мне приходилось иногда оборачивать print(ggarrangeobject) один из моих ggarrange объектов, когда мне нужно было построить график какой-либо другой функцией, которая может быть похожа на решение для вашего renderPlot()? - person Brandon; 11.02.2020
comment
common.legend = TRUE это все, что мне нужно! - person Aryo; 02.06.2020
comment
Большой. Если бы я хотел сделать обычную легенду одной горизонтальной линией, было бы это возможно? Итак, вместо двух строк для ясности, только одну? - person Miranda; 09.07.2020
comment
Как сделать так, чтобы легенды отображались в несколько строк? - person Aryo; 13.08.2020
comment
что, если у меня есть две легенды: одна общая для всех графиков (тип линий), а другая нет (цвет)? Я просто хочу, чтобы легенда о типах линий была общей для всех. - person Alvaro Morales; 14.07.2021

Ответ Роланда нуждается в обновлении. См .: 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_gtable и ggplot_build. Вместо этого используется ggplotGrob. Этот пример немного более запутан, чем приведенное выше решение, но он все же решил его для меня.

person Steven Lockton    schedule 18.02.2015
comment
Привет, у меня есть 6 графиков, и я хотел бы расположить 6 графиков как 2 ряда × 3 столбца и нарисовать легенду вверху, так как же изменить функцию grid_arrange_shared_legend? Спасибо! - person just_rookie; 09.05.2015
comment
@just_rookie, вы нашли решение, как изменить функцию, чтобы можно было использовать разные схемы ncol и nrow вместо только ncol = 1? - person Giuseppe; 06.01.2016
comment
Привет, я попробовал это решение, оно работает хорошо, однако при его печати у меня было 2 страницы pdf вместо одной, первая пустая, а вторая содержит мой сюжет, почему у меня такое поведение? Благодарность, - person HanniBaL90; 21.06.2017
comment
для всех, кто может получить такой же isse, как и я, вот обходной путь: stackoverflow.com/questions/12481267/ - person HanniBaL90; 21.06.2017
comment
Отличный материал здесь. Есть идеи, как можно добавить одно название для всех участков? - person Pertinax; 13.05.2018
comment
@Giuseppe код для этого, позволяя вам определять количество строк и столбцов, находится в github.com/tidyverse/ggplot2/wiki/ - person carbocation; 01.08.2019
comment
что, если графики разделяют одни из значений легенды, а другие разные? Я не хочу использовать одну легенду для всех, так как у других могут быть разные - person Mariya; 08.04.2021

Новое привлекательное решение - использовать 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")

Создано 13 декабря 2019 г. пакетом REPEX (v0.2.1)

person MSR    schedule 13.12.2019
comment
Если вы немного измените порядок команд, вы можете сделать это одной строкой: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom") - person mlcyo; 24.04.2020
comment
Почему бы теперь не лоскутно разместить легенду между сюжетами? Похоже, что это было сделано с этим примером, но когда я пробую это с помощью разрабатываемой версии лоскутного шитья, он продолжает размещать легенду на левом поле, а не в центре. Спасибо. - person jaydoc; 07.02.2021
comment
Используя последнюю версию программы patchwork (1.1.1) для CRAN, я по-прежнему получаю тот же результат, что и в ответе здесь. Что касается версии для разработки, я не уверен, но полагаю, вы могли бы открыть проблему в репозитории с представителем, чтобы спросить, предназначено ли то, что вы получаете. Я ничего не вижу об этом в файле NEWS, так что думаю, что этого может и не быть. - person MSR; 08.02.2021

Я предлагаю использовать коровий заговор. Из их R виньетки:

# 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

комбинированные графики с легендой внизу

person Gregor Sturm    schedule 26.06.2017
comment
Это был единственный способ добавить легенду вручную в мой график с помощью annotate_figure(ggarrange()), используя legend_b (). Большое вам спасибо, да благословит вас Бог! - person Jean Karlos; 08.06.2020

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

}

Дополнительные аргументы nrow и ncol управляют компоновкой упорядоченных графиков:

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)

введите здесь описание изображения  введите здесь описание изображения

person epsilone    schedule 17.07.2016
comment
Как и в случае с другим решением, я пробовал его, он работает хорошо, однако при его печати у меня было 2 страницы pdf вместо одной, первая пустая, а вторая содержит мой сюжет, почему у меня такое поведение? Благодарность, - person HanniBaL90; 21.06.2017
comment
для тех, кто может получить такой же isse, как и я, вот обходной путь: stackoverflow.com/questions/12481267/ - person HanniBaL90; 21.06.2017
comment
Может кто-нибудь объяснить мне решение? Как я могу разместить легенду вверху, а не внизу? Спасибо - person HanniBaL90; 23.06.2017

Если вы строите одни и те же переменные на обоих графиках, самым простым способом было бы объединить фреймы данных в один, а затем использовать 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)

«Сюжет

Другой пример с использованием набора данных ромбов. Это показывает, что вы даже можете заставить его работать, если у вас есть только одна переменная, общая для ваших графиков.

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

«Сюжет

Единственная сложность во втором примере заключается в том, что факторные переменные приводятся к числовым значениям, когда вы объединяете все в один фрейм данных. Поэтому в идеале вы будете делать это в основном, когда все интересующие вас переменные будут одного типа.

person hmgeiger    schedule 15.12.2017

@ Гуизеппе:

Я понятия не имею о 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))
person Jack    schedule 16.01.2016

Если легенда одинакова для обоих графиков, существует простое решение с использованием grid.arrange (при условии, что вы хотите, чтобы легенда выровнялась с обоими графиками по вертикали или горизонтали). Просто сохраните легенду для самого нижнего или самого правого графика, опуская легенду для другого. Однако добавление легенды только к одному графику изменяет размер одного участка относительно другого. Чтобы избежать этого, используйте команду heights, чтобы вручную отрегулировать и сохранить их одинаковый размер. Вы даже можете использовать grid.arrange для создания общих заголовков осей. Обратите внимание, что для этого потребуется library(grid) в дополнение к library(gridExtra). Для вертикальных графиков:

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

Вот результат аналогичного графика для проекта, над которым я работал:  введите описание изображения здесь

person Wesley Lozano    schedule 21.07.2020