Додайте загальну легенду для комбінованих ggplots


138

У мене є два 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")))

Як я можу витягти легенду з будь-якого з цих сюжетів і додати її до нижньої / центральної частини комбінованого сюжету?


2
У мене періодично виникає ця проблема. Якщо ви не хочете вирішити сюжет, найпростіше рішення, яке я знаю, - це просто зберегти його з легендою, а потім скористайтеся Photoshop / Ilustrator, щоб вставити його на порожні сюжетні сюжети. Неелегантний я знаю - але практичний швидко і легко.
Стівен Хендерсон

@StephenHenderson Це відповідь. Фасетка або пост-процес за допомогою редактора gfx.
Брендон Бертелсен

Відповіді:


107

Оновлення 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 сюжети із загальною легендою


2
обидві відповіді вказують на одну і ту ж сторінку wiki, яку можна оновити, оскільки нові версії ggplot2 порушують код.
баптист

Через шість років ця відповідь вирішила мою проблему. Дякую!
SPK.z

Це може бути простим для деяких / більшості людей, але я цього не зрозумів одразу, тому подумав, що буду коментувати. Якщо ви хочете, щоб загальна легенда була зверху сюжету (а не нижче), все, що вам потрібно зробити, - це переключити аргументи. У наведеному вище прикладі мілегенд йде раніше arrangeGrob(). Вам також потрібно змінити висоту (тобтоheights=c(1,10)
ljh2001

113

Ви також можете використовувати 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")

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


1
Чи можливо, що це не працює всередині блискучої програми (або flexdashboard) з renderPlot ()? Це прекрасно працює в звичайному сценарії R з нормальними сюжетами. Але коли я роблю точно те ж саме із сюжетами, зробленими з renderPlot () на моїй флеш-панелі, нічого не з’являється.
Tingolfin

1
Дякую за це - я думаю, що це було далеко найпростішим рішенням того, що я шукав
Komal Rathi

Це круто! Дякую!
yanes

@Tingolfin Мені доводилося іноді обертатися print(ggarrangeobject)навколо одного з моїх ggarrangeоб'єктів, коли мені потрібно, щоб він побудував якусь іншу функцію, яка може бути схожа на рішення для вашої renderPlot()?
Брендон

common.legend = TRUEце все, що мені потрібно!
Арьо

62

Відповідь Роланда потребує оновлення. Дивіться: 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використовується замість цього. Цей приклад дещо складніший, ніж наведене вище рішення, але він все-таки вирішив його для мене.


10
Привіт, у мене 6 сюжетів, і я хотів би оформити 6 сюжетів як 2 ряди × 3 стовпчика та намалювати легенду зверху, так як змінити функцію grid_arrange_shared_legend? Дякую!
just_rookie

4
@just_rookie Ви знайшли рішення, як змінити функцію, щоб можна було використовувати різні порядок ncol і nrow замість лише ncol = 1?
Джузеппе

Привіт, я спробував це рішення, воно працює добре, проте при його друку я отримав 2 сторінки у форматі PDF замість лише 1, перша порожня, а пізніше містить мій сюжет, чому я мав таку поведінку? дякую,
HanniBaL90

для тих , хто , як отримати той же ISSE , як і я, ось обхідний шлях: stackoverflow.com/questions/12481267 / ...
HanniBaL90

1
Чудові речі тут. Будь-яка ідея, як можна додати одну назву для всіх сюжетів?
Pertinax

27

Нове, привабливе рішення - використовувати 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")

Створено 2019-12-13 пакетом reprex (v0.2.1)


2
Якщо ви трохи змінили порядок команд, ви можете зробити це в одному рядку: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom")
mlcyo

17

Я пропоную скористатися коров'ячою водою. Від їхньої 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

комбіновані сюжети з легендою внизу


Це був єдиний спосіб, який зробив можливим помістити в мою сюжет ручну легенду annotate_figure(ggarrange()), використовуючи legend_b (). Дякую тобі, Бог благослови!
Жан Карлос

12

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

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


Так само, як і для іншого рішення, я спробував це, він працює добре, проте при його друку я отримав 2 сторінки у форматі PDF замість лише 1, перша порожня, а пізніше містить мій сюжет, чому я мав таку поведінку? дякую,
HanniBaL90

для тих , хто , як отримати той же ISSE , як і я, ось обхідний шлях: stackoverflow.com/questions/12481267 / ...
HanniBaL90

Може хтось пояснить мені рішення? Як я можу розмістити легенду вгорі, а не знизу? Спасибі
HanniBaL90

8

Якщо ви плануєте однакові змінні на обох графіках, найпростішим способом було б поєднання кадрів даних в одне ціле, а потім використовувати 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("")

Сюжет 2

Єдиний складний момент у другому прикладі - це те, що факторні змінні примушуються до числових, коли ви об'єднуєте все в один кадр даних. Тому в ідеалі ви будете робити це головним чином, коли всі ваші змінні величини, що цікавлять, є одним типом.


1

@Guiseppe:

Я не маю уявлення про Гробів тощо, але я зламав рішення для двох сюжетів, повинно бути можливим розширення до довільного числа, але воно не в сексуальній функції:

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

1

Якщо легенда однакова для обох сюжетів, існує просте рішення з використанням 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)))

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.