Aggiungi una legenda comune per i ggplot combinati


138

Ho due ggplot con cui mi allineo orizzontalmente grid.arrange. Ho esaminato molti post del forum, ma tutto ciò che provo sembra essere un comando che ora è stato aggiornato e chiamato con un altro nome.

I miei dati si presentano così;

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

Come estrarrei la legenda da una di queste trame e la aggiungerei in fondo / al centro della trama combinata?


2
Occasionalmente ho questo problema. Se non vuoi sfaccettare la trama, la soluzione più semplice che conosco è quella di salvarne una con una legenda, quindi usa Photoshop / Ilustrator per incollarla nelle trame della legenda vuote. Inelegante lo so - ma pratico facile e veloce.
— Stephen Henderson,

@StephenHenderson Questa è una risposta. Facet o post-process con l'editor gfx.
— Brandon Bertelsen,

Risposte:


107

Aggiornamento 2015-febbraio

Vedi la risposta di Steven di seguito


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

Ecco la trama risultante: 2 trame con legenda comune


2
entrambe le risposte puntano alla stessa pagina wiki che può essere aggiornata quando le nuove versioni di ggplot2 infrangono il codice.
— Baptista,

Più di sei anni dopo questa risposta ha risolto il mio problema. Grazie!
— SPK.z,

Questo può essere semplice per alcune / molte persone, ma non l'ho capito subito, quindi ho pensato di commentare. Se vuoi la legenda comune in cima alla trama (piuttosto che in basso), tutto ciò che devi fare è cambiare argomento. Nell'esempio sopra, mylegend va prima arrangeGrob(). È inoltre necessario invertire le altezze (cioèheights=c(1,10)
— ljh2001

113

Puoi anche usare ggarrange dal pacchetto ggpubr e impostare "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")

inserisci qui la descrizione dell'immagine


1
È possibile che non funzioni all'interno di un'applicazione brillante (o flexdashboard) con renderPlot ()? Funziona perfettamente in uno script R normale con trame normali. Ma quando faccio esattamente la stessa cosa con i grafici realizzati con renderPlot () nella mia lavagna flessibile, non appare nulla.
— Tingolfin,

1
Grazie per questo - penso che questa sia stata di gran lunga la soluzione più semplice per quello che stavo cercando
— Komal Rathi,

Questo e spettacolare! Grazie!
— yanes,

@Tingolfin Ho dovuto a volte avvolgere print(ggarrangeobject)uno dei miei ggarrangeoggetti quando ne avevo bisogno per essere tracciato da un'altra funzione, che potrebbe essere simile alla soluzione per il tuo renderPlot()?
— Brandon,

common.legend = TRUEè tutto ciò di cui ho bisogno!
— Aryo,

62

La risposta di Roland deve essere aggiornata. Vedi: https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs

Questo metodo è stato aggiornato per 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)

Nota la mancanza di ggplot_gtablee ggplot_build. ggplotGrobviene invece utilizzato. Questo esempio è un po 'più complicato della soluzione di cui sopra, ma lo ha ancora risolto per me.


10
Ciao, ho 6 grafici e vorrei disporre i 6 grafici come 2 righe × 3 col e disegnare la legenda in alto, quindi come modificare la funzione grid_arrange_shared_legend? Grazie!
— just_rookie,

4
@just_rookie hai trovato una soluzione su come modificare la funzione in modo da poter utilizzare diverse disposizioni ncol e nrow anziché solo ncol = 1?
— Giuseppe,

Ciao, ho provato questa soluzione, funziona bene, tuttavia durante la stampa ho 2 pagine pdf anziché solo 1, la prima è vuota mentre la seconda contiene la mia trama, perché ho avuto un simile comportamento? grazie,
— HanniBaL90,

per tutti coloro che come ottenere lo stesso ISSE come me, qui è una soluzione: stackoverflow.com/questions/12481267/...
— HanniBaL90

1
Roba fantastica qui. Qualche idea su come si possa aggiungere un singolo titolo per tutte le trame?
— Pertinax,

27

Una nuova soluzione interessante è quella di utilizzare patchwork. La sintassi è molto semplice:

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

Creato il 13-12-2019 dal pacchetto reprex (v0.2.1)


2
Se cambi leggermente l'ordine dei comandi puoi farlo in una riga: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom")
— mlcyo il

17

Suggerisco di usare il cowplot. Dalla loro vignetta 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

trame combinate con legenda in basso


Questo è stato l'unico modo che ha reso possibile mettere una legenda manuale nella mia trama con annotate_figure(ggarrange()), usando un legend_b (). Grazie mille, Dio ti benedica!
— Jean Karlos,

12

@Giuseppe, potresti considerare questo per una specifica flessibile della disposizione dei grafici (modificata da qui ):

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)

}

Argomenti extra nrowe ncolcontrollo del layout dei grafici organizzati:

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)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


Come per l'altra soluzione, l'ho provato, funziona bene, tuttavia quando lo stampo, ho 2 pagine pdf invece di solo 1, la prima è vuota mentre la seconda contiene la mia trama, perché ho avuto un simile comportamento? grazie,
— HanniBaL90,

per tutti coloro che come ottenere lo stesso ISSE come me, qui è una soluzione: stackoverflow.com/questions/12481267/...
— HanniBaL90

Qualcuno può spiegarmi la soluzione? Come posso collocare la legenda in alto anziché in basso? Grazie
— HanniBaL90 il

8

Se si tracciano le stesse variabili in entrambi i grafici, il modo più semplice sarebbe quello di combinare i frame di dati in uno, quindi utilizzare facet_wrap.

Per il tuo esempio:

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)

Trama 1

Un altro esempio che utilizza il set di dati di diamanti. Ciò dimostra che puoi persino farlo funzionare se hai solo una variabile comune tra i tuoi grafici.

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

Trama 2

L'unica cosa delicata con il secondo esempio è che le variabili dei fattori vengono costrette a diventare numeriche quando si combina tutto in un frame di dati. Quindi idealmente, lo farai principalmente quando tutte le tue variabili di interesse sono dello stesso tipo.


1

@Guiseppe:

Non ho idea di Grobs ecc., Ma ho hackerato insieme una soluzione per due trame, dovrebbe essere possibile estendere a un numero arbitrario ma non è in una funzione sexy:

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

Se la legenda è la stessa per entrambi i grafici, è possibile utilizzare una soluzione semplice grid.arrange(presupponendo che la legenda si allinei con entrambi i grafici in verticale o in orizzontale). Conserva semplicemente la legenda per la trama più in basso o più a destra mentre ometti la legenda per l'altra. L'aggiunta di una legenda a una sola trama, tuttavia, modifica la dimensione di una trama rispetto all'altra. Per evitare ciò, utilizzare il heightscomando per regolare manualmente e mantenerli delle stesse dimensioni. Puoi persino usare grid.arrangeper creare titoli di assi comuni. Si noti che ciò richiederà library(grid)oltre a library(gridExtra). Per grafici verticali:

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

Ecco il risultato per un grafico simile per un progetto a cui stavo lavorando: inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.