kassambara / ggpubr

'ggplot2' Based Publication Ready Plots
https://rpkgs.datanovia.com/ggpubr/
1.14k stars 165 forks source link

ggarrange(common.legend = TRUE) returns a misleading legend #347

Open GegznaV opened 4 years ago

GegznaV commented 4 years ago

Function ggarrange(common.legend = TRUE) returns a misleading legend. Note the reversed order of factor levels in iris_1 and iris_3 and continuous legend in iris_2.

library(tidyverse)

iris_1 <-
  ggplot(iris, aes(x = Sepal.Length, fill = Species, color = Species)) +
  geom_density(alpha = 0.3, adjust = 1.5)

iris_2 <-
  ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Sepal.Width)) +
  geom_point()

iris_3 <-
  ggplot(iris, aes(x = Species, y = Sepal.Width, fill = fct_rev(Species))) +
  geom_boxplot()

ggpubr::ggarrange(iris_1, iris_2, iris_3, nrow = 1, common.legend = FALSE)


ggpubr::ggarrange(iris_1, iris_2, iris_3, nrow = 1, common.legend = TRUE)

Created on 2020-10-14 by the reprex package (v0.3.0)

kassambara commented 4 years ago

Do you want to merge the legends like below?

library(tidyverse)
suppressPackageStartupMessages(library(ggpubr))

iris_1 <-
  ggplot(iris, aes(x = Sepal.Length, fill = Species, color = Species)) +
  geom_density(alpha = 0.3, adjust = 1.5)

iris_2 <-
  ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Sepal.Width)) +
  geom_point()

iris_3 <-
  ggplot(iris, aes(x = Species, y = Sepal.Width, fill = fct_rev(Species))) +
  geom_boxplot()

# Merging legends
legend_1 <- get_legend(iris_1)
legend_2 <- get_legend(iris_2)
legend_3 <- get_legend(iris_3)
legends <- ggarrange(legend_1, legend_2, legend_3, nrow=3)

# Combining plots
rm_legend <- function(p){p + theme(legend.position = "none")}
plots <- ggarrange(rm_legend(iris_1), rm_legend(iris_2), rm_legend(iris_3), nrow = 1)

# plots + merged legends
ggarrange(plots, legends, widths = c(0.75, 0.25))

Created on 2020-10-14 by the reprex package (v0.3.0.9001)

GegznaV commented 4 years ago

In this particular case, where a new legend misrepresents data, separate legends would be a more correct solution.

But in case, when the legends can be merged, a single legend should be preferred. E.g., I like this output:

library(tidyverse)

iris_1 <-
  ggplot(iris, aes(x = Sepal.Length, fill = Species, color = Species)) +
  geom_density(alpha = 0.3, adjust = 1.5)

iris_2 <-
  ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point()

iris_3 <-
  ggplot(iris, aes(x = Species, y = Sepal.Width, fill = Species)) +
  geom_boxplot()

ggpubr::ggarrange(iris_1, iris_2, iris_3, nrow = 1, common.legend = TRUE)

Created on 2020-10-14 by the reprex package (v0.3.0)

Even more challenging is the situation, where I expect some legends to be merged and some – not. E.g., here I expect 2 legends (one for discrete colors and the other one for continuous colors) but now the legend for continuous colors is missing:

library(tidyverse)

iris_1 <-
  ggplot(iris, aes(x = Sepal.Length, fill = Species, color = Species)) +
  geom_density(alpha = 0.3, adjust = 1.5)

iris_2 <-
  ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Sepal.Width)) +
  geom_point()

iris_3 <-
  ggplot(iris, aes(x = Species, y = Sepal.Width, fill = Species)) +
  geom_boxplot()

ggpubr::ggarrange(iris_1, iris_2, iris_3, nrow = 1, common.legend = TRUE)

Created on 2020-10-14 by the reprex package (v0.3.0)

kassambara commented 4 years ago

OK, I can see the issue know; you want to keep the first and the second legends in the arranged plots.

The current behavior of the option common.legend is to keep only the legend of the first plot. A possible improvement would be to be able to specify something like common.legend = c(1, 2), so that the legends of the first and the second plots are kept.

GegznaV commented 4 years ago

I thought that the function should check and merge the values in several legends to create a single common legend. But now I see that the user decides if all plots can be represented by a legend of the first plot and common.legend = TRUE actually means discard all the legends but the first one.

In no checking is performed and the legends are not actually merged, then common.legend = c(1, 2) would be a nice alternative that enables to keep the legends of interest.

stragu commented 2 years ago

I believe that a first quick step to resolve this issue would be to properly document the current behaviour. In version 0.4.0, the argument's description is as follows:

common.legend: logical value. Default is FALSE. If TRUE, a common unique legend will be created for arranged plots.

It really does make it sound like a common legend is created to accurately describe all plots included.

This issue is particularly problematic for combined plots that share the same geometry and colour scale, for example:

library(ggplot2)
library(palmerpenguins)
peng_simple <- ggplot(penguins,
                      aes(x = bill_length_mm,
                          y = bill_depth_mm,
                          colour = bill_depth_mm)) +
  geom_point()
# same, but bill depth is multiplied by 3
peng_triple <-ggplot(penguins,
                     aes(x = bill_length_mm,
                         y = bill_depth_mm * 3,
                         colour = bill_depth_mm * 3)) +
  geom_point()

ggpubr::ggarrange(peng_simple, peng_triple,
                  nrow = 1, common.legend = TRUE)

Created on 2022-02-16 by the reprex package (v2.0.1)

fthielen commented 2 years ago

I believe that a first quick step to resolve this issue would be to properly document the current behaviour. In version 0.4.0, the argument's description is as follows:

common.legend: logical value. Default is FALSE. If TRUE, a common unique legend will be created for arranged plots.

It really does make it sound like a common legend is created to accurately describe all plots included.

Even more so, it is not true that a "common unique legend" is created because only the first legend is chosen. In my case, I have missing data (NA) for one group in the first plot, but this group is present in all subsequent plots. With common.legend and the current description I expected that all unique groups would be represented instead of only those of the first plot.

nikbpetrov commented 2 years ago

Following.

dariotommasini commented 1 year ago

Any updates on this? Is "common.legend" still using the legend of the first plot rather than a merged legend?

mariofiorini commented 1 year ago

Any updates on this? Is "common.legend" still using the legend of the first plot rather than a merged legend?

I think you can use "legend.grob" to specify which plot to use for the legend.

megardn commented 1 year ago

anyone found a good workaround for this? I have two plots that have the same continuous color scale, and the legend i get using ggarrange is just totally inaccurate for one of them