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.