Ajouter une légende commune pour les ggplots combinés

138

J'ai deux ggplots avec lesquels je m'aligne horizontalement grid.arrange. J'ai parcouru de nombreux messages sur le forum, mais tout ce que j'essaye semble être des commandes qui sont maintenant mises à jour et nommées autre chose.

Mes données ressemblent à ceci;

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

Comment extraire la légende de l'un de ces tracés et l'ajouter au bas / au centre du tracé combiné?

jO.
la source
2
J'ai parfois ce problème. Si vous ne voulez pas modifier le tracé, la solution la plus simple que je connaisse est simplement d'en enregistrer un avec une légende, puis d'utiliser Photoshop / Ilustrator pour le coller sur les tracés de légende vides. Inélégant je sais - mais pratique rapide et facile.
Stephen Henderson
@StephenHenderson C'est une réponse. Facette ou post-traitement avec l'éditeur gfx.
Brandon Bertelsen

Réponses:

107

Mise à jour 2015-février

Voir la réponse de Steven ci-dessous


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

Voici le graphique résultant: 2 parcelles avec légende commune

Roland
la source
2
les deux réponses pointent vers la même page wiki qui peut être mise à jour lorsque de nouvelles versions de ggplot2 cassent le code.
baptiste le
Plus de six ans plus tard, cette réponse a résolu mon problème. Merci!
SPK.z
Cela peut être simple pour certains / la plupart des gens, mais je ne l'ai pas compris tout de suite, alors j'ai pensé à commenter. Si vous voulez la légende commune au-dessus du tracé (plutôt qu'en dessous), tout ce que vous avez à faire est de changer les arguments. Dans l'exemple ci-dessus, mylegend précède arrangeGrob(). Vous devez également inverser les hauteurs (c'estheights=c(1,10)
ljh2001
113

Vous pouvez également utiliser ggarrange du package ggpubr et définir "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")

entrez la description de l'image ici

Huiyan Wan
la source
1
Est-il possible que cela ne fonctionne pas dans une application brillante (ou flexdashboard) avec renderPlot ()? Cela fonctionne parfaitement bien dans un script R normal avec des tracés normaux. Mais quand je fais exactement la même chose avec les tracés réalisés avec renderPlot () dans mon flexdashboard, rien n'apparaît.
Tingolfin
1
Merci pour cela - je pense que c'était de loin la solution la plus simple pour ce que je cherchais
Komal Rathi
C'est génial! Je vous remercie!
yanes
@Tingolfin J'ai dû parfois enrouler print(ggarrangeobject)autour d'un de mes ggarrangeobjets quand j'avais besoin qu'il soit tracé par une autre fonction, qui peut être similaire à la solution pour votre renderPlot()?
Brandon le
common.legend = TRUEc'est tout ce dont j'ai besoin!
Aryo
62

La réponse de Roland doit être mise à jour. Voir: https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs

Cette méthode a été mise à jour pour 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)

Notez le manque de ggplot_gtableet ggplot_build. ggplotGrobest utilisé à la place. Cet exemple est un peu plus compliqué que la solution ci-dessus, mais il l'a toujours résolu pour moi.

Steven Lockton
la source
10
Bonjour, j'ai 6 parcelles, et je voudrais organiser les 6 parcelles en 2 rangées × 3 colonnes et dessiner la légende en haut, alors comment changer la fonction grid_arrange_shared_legend? Je vous remercie!
just_rookie
4
@just_rookie avez-vous trouvé une solution pour changer la fonction afin que l'on puisse utiliser différents arrangements ncol et nrow au lieu de seulement ncol = 1?
Giuseppe
Salut, j'ai essayé cette solution, cela fonctionne bien, mais lors de son impression, j'ai obtenu 2 pages pdf au lieu de seulement 1, la première est vide tandis que la dernière contient mon intrigue, pourquoi j'ai un tel comportement? merci,
HanniBaL90
pour n'importe qui comment obtenir la même chose que moi, voici une solution de contournement: stackoverflow.com/questions/12481267/…
HanniBaL90
1
Super truc ici. Une idée comment on peut ajouter un seul titre pour toutes les parcelles?
Pertinax
27

Une nouvelle solution attrayante consiste à utiliser patchwork. La syntaxe est très 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")

Créé le 13/12/2019 par le package reprex (v0.2.1)

MSR
la source
2
Si vous modifiez légèrement l'ordre des commandes, vous pouvez le faire en une seule ligne: combined <- p1 + p2 + plot_layout(guides = "collect") & theme(legend.position = "bottom")
mlcyo
17

Je suggère d'utiliser cowplot. De leur vignette 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

parcelles combinées avec légende en bas

Gregor Sturm
la source
C'était le seul moyen, qui permettait de mettre une légende manuelle dans mon intrigue avec annotate_figure(ggarrange()), en utilisant une legend_b (). Merci beaucoup, que Dieu vous bénisse!
Jean Karlos
12

@Giuseppe, vous voudrez peut-être considérer ceci pour une spécification flexible de la disposition des parcelles (modifiée à partir d' ici ):

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)

}

Arguments supplémentaires nrowet ncolcontrôlez la disposition des tracés arrangés:

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)

entrez la description de l'image ici entrez la description de l'image ici

epsilone
la source
Idem que pour l'autre solution, je l'ai essayé, cela fonctionne bien, cependant lors de l'impression, j'ai eu 2 pages pdf au lieu de seulement 1, la première est vierge tandis que la dernière contient mon intrigue, pourquoi j'ai un tel comportement? merci,
HanniBaL90
pour n'importe qui comment obtenir la même chose que moi, voici une solution de contournement: stackoverflow.com/questions/12481267/…
HanniBaL90
Quelqu'un peut-il m'expliquer la solution? Comment placer la légende en haut plutôt qu'en bas? Thanks
HanniBaL90
8

Si vous tracez les mêmes variables dans les deux tracés, le moyen le plus simple serait de combiner les blocs de données en un seul, puis d'utiliser facet_wrap.

Pour votre exemple:

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)

Parcelle 1

Un autre exemple utilisant l'ensemble de données des diamants. Cela montre que vous pouvez même le faire fonctionner si vous n'avez qu'une seule variable commune entre vos parcelles.

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

Parcelle 2

La seule chose délicate avec le deuxième exemple est que les variables de facteur sont forcées à être numériques lorsque vous combinez tout dans une seule trame de données. Donc, idéalement, vous le ferez principalement lorsque toutes vos variables d'intérêt sont du même type.

hmgeiger
la source
1

@Guiseppe:

Je n'ai aucune idée de Grobs, etc., mais j'ai piraté ensemble une solution pour deux parcelles, devrait être possible d'étendre à un nombre arbitraire mais ce n'est pas dans une fonction 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))
Jack
la source
1

Si la légende est la même pour les deux tracés, il existe une solution simple utilisant grid.arrange(en supposant que vous voulez que votre légende s'aligne avec les deux tracés verticalement ou horizontalement). Conservez simplement la légende du tracé le plus bas ou le plus à droite tout en omettant la légende de l'autre. L'ajout d'une légende à un seul tracé, cependant, modifie la taille d'un tracé par rapport à l'autre. Pour éviter cela, utilisez la heightscommande pour ajuster manuellement et conserver la même taille. Vous pouvez même utiliser grid.arrangepour créer des titres d'axes communs. Notez que cela nécessitera library(grid)en plus de library(gridExtra). Pour les tracés verticaux:

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

Voici le résultat d'un graphique similaire pour un projet sur lequel je travaillais: entrez la description de l'image ici

Wesley Lozano
la source