Agregue una leyenda común para ggplots combinados

138

Tengo dos ggplots con los que alineo horizontalmente grid.arrange. He revisado muchas publicaciones en el foro, pero todo lo que intento parece ser comandos que ahora se actualizan y nombran algo más.

Mis datos se ven así;

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

¿Cómo extraería la leyenda de cualquiera de estas parcelas y la agregaría al fondo / centro de la parcela combinada?

jO.
fuente
2
De vez en cuando tengo este problema. Si no desea facetar el gráfico, la solución más fácil que conozco es simplemente guardar uno con una leyenda y luego usar Photoshop / Ilustrator para pegarlo en los gráficos de leyenda en blanco. Inelegante, lo sé, pero práctico, rápido y fácil.
Stephen Henderson el
@StephenHenderson Esa es una respuesta. Faceta o postproceso con editor gfx.
Brandon Bertelsen

Respuestas:

107

Actualización 2015-feb

Ver la respuesta de Steven a continuación


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

Aquí está la trama resultante: 2 parcelas con leyenda común

Roland
fuente
2
ambas respuestas apuntan a la misma página wiki que se puede actualizar a medida que las nuevas versiones de ggplot2 rompen el código.
Baptiste
Más de seis años después, esta respuesta resolvió mi problema. ¡Gracias!
SPK.z
Esto puede ser sencillo para algunas / la mayoría de las personas, pero no lo entendí de inmediato, así que pensé en comentar. Si desea la leyenda común en la parte superior de la trama (en lugar de debajo), todo lo que necesita hacer es cambiar los argumentos. En el ejemplo anterior, mylegend va antes arrangeGrob(). También debe invertir las alturas (es decirheights=c(1,10)
ljh2001
113

También puede usar ggarrange del paquete ggpubr y establecer "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")

ingrese la descripción de la imagen aquí

Huiyan Wan
fuente
1
¿Es posible que esto no funcione dentro de una aplicación brillante (o flexdashboard) con renderPlot ()? Funciona perfectamente bien en un script R normal con tramas normales. Pero cuando hago exactamente lo mismo con las gráficas hechas con renderPlot () en mi panel flexible, no aparece nada.
Tingolfin
1
Gracias por esto. Creo que esta fue, con mucho, la solución más fácil para lo que estaba buscando
Komal Rathi
¡Esto es asombroso! ¡Gracias!
yanes
@Tingolfin A veces he tenido que envolver print(ggarrangeobject)uno de mis ggarrangeobjetos cuando necesitaba que fuera trazado por alguna otra función, que puede ser similar a la solución para usted renderPlot().
Brandon
common.legend = TRUEes todo lo que necesito!
Aryo
62

La respuesta de Roland necesita una actualización. Ver: https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs

Este método se ha actualizado para 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)

Tenga en cuenta la falta de ggplot_gtabley ggplot_build. ggplotGrobse usa en su lugar. Este ejemplo es un poco más complicado que la solución anterior, pero todavía lo resolvió para mí.

Steven Lockton
fuente
10
Hola, tengo 6 parcelas, y me gustaría organizar las 6 parcelas como 2 filas × 3 columnas y dibujar la leyenda en la parte superior, entonces, ¿cómo cambiar la función grid_arrange_shared_legend? ¡Gracias!
just_rookie
44
@just_rookie, ¿encontró una solución para cambiar la función de modo que se puedan usar diferentes arreglos ncol y nrow en lugar de solo ncol = 1?
Giuseppe
Hola, probé esta solución, funciona bien, sin embargo, cuando la imprimí, obtuve 2 páginas pdf en lugar de solo 1, la primera está en blanco mientras que la última contiene mi gráfico, ¿por qué obtuve ese comportamiento? gracias,
HanniBaL90
para cualquiera que obtenga el mismo isse que yo, aquí hay una solución alternativa: stackoverflow.com/questions/12481267/…
HanniBaL90
1
Grandes cosas aquí. ¿Alguna idea de cómo se puede agregar un solo título para todas las tramas?
Pertinax
27

Una solución nueva y atractiva es usar patchwork. La sintaxis es muy simple:

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

Creado el 13/12/2019 por el paquete reprex (v0.2.1)

MSR
fuente
2
Si cambia ligeramente el orden de los comandos, puede hacerlo en una línea: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom")
mlcyo
17

Sugiero usar cowplot. De su viñeta 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

parcelas combinadas con leyenda en la parte inferior

Gregor Sturm
fuente
Esta fue la única manera, que hizo posible poner una leyenda manual en mi trama annotate_figure(ggarrange()), usando un legend_b (). ¡Muchas gracias, que Dios te bendiga!
Jean Karlos
12

@Giuseppe, puede considerar esto para una especificación flexible de la disposición de las parcelas (modificada desde aquí ):

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)

}

Argumentos adicionales nrowy ncolcontrolar el diseño de las parcelas organizadas:

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)

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

épsilona
fuente
Al igual que para la otra solución, lo probé, funciona bien, sin embargo, al imprimirlo, obtuve 2 páginas pdf en lugar de solo 1, la primera está en blanco mientras que la última contiene mi gráfico, ¿por qué obtuve ese comportamiento? gracias,
HanniBaL90
para cualquiera que obtenga el mismo isse que yo, aquí hay una solución alternativa: stackoverflow.com/questions/12481267/…
HanniBaL90
¿Alguien puede explicarme la solución? ¿Cómo puedo colocar la leyenda en la parte superior en lugar de en la parte inferior? Gracias
HanniBaL90
8

Si está trazando las mismas variables en ambas parcelas, la forma más simple sería combinar los marcos de datos en uno y luego usar facet_wrap.

Por su ejemplo:

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)

Parcela 1

Otro ejemplo usando el conjunto de datos de diamantes. Esto muestra que incluso puede hacerlo funcionar si solo tiene una variable común entre sus parcelas.

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

Parcela 2

Lo único complicado con el segundo ejemplo es que las variables de factor se convierten en numéricas cuando se combina todo en un marco de datos. Idealmente, lo hará principalmente cuando todas sus variables de interés sean del mismo tipo.

hmgeiger
fuente
1

@Guiseppe:

No tengo ni idea de Grobs, etc., pero heckeé una solución para dos parcelas, debería ser posible extenderla a un número arbitrario, pero no es una función atractiva:

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))
Jack
fuente
1

Si la leyenda es la misma para ambas parcelas, hay una solución simple usando grid.arrange(suponiendo que desee que su leyenda se alinee con ambas parcelas, ya sea vertical u horizontalmente). Simplemente mantenga la leyenda para la trama del extremo inferior o del extremo derecho mientras omite la leyenda para el otro. Sin embargo, agregar una leyenda a un solo diagrama altera el tamaño de un diagrama en relación con el otro. Para evitar esto, use el heightscomando para ajustarlos manualmente y mantenerlos del mismo tamaño. Incluso puede usar grid.arrangepara hacer títulos de eje comunes. Tenga en cuenta que esto requerirá library(grid)además de library(gridExtra). Para parcelas verticales:

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

Aquí está el resultado de un gráfico similar para un proyecto en el que estaba trabajando: ingrese la descripción de la imagen aquí

Wesley Lozano
fuente