10
votes

How can I make ggplot2 give a separate legend for different geoms that both use color to represent 2 different variables. I'd want 3 legends, one for z, a, and b, but a & b seem to be combined into a singe legend even though a & b represent different variables. I'd also like to be able to control the colors in each legend.

dat <- data.frame(
    y = rnorm(200),
    x = sample(c("A", "B"), 200, TRUE),
    z = sample(100:200, 200, TRUE), 
    a = sample(c("male", "female"), 200, TRUE),
    b = factor(sample(1:2, 200, TRUE))
)

ggplot(dat, aes(y = y, x = x)) +
    geom_point(aes(color = a, size = z)) + 
    geom_boxplot(fill = NA, size=.75, aes(color=b)) +
    scale_color_manual(values = c("#F8766D", "#00BFC4", "orange", "purple"))

enter image description here

3
Probably an easier way but you could do two plots and then combine the legends... stackoverflow.com/questions/26727741/…user20650
@user20650 I thought about that and will go that route if need be but it does seem like there should be an easier way.Tyler Rinker
I can see why you would want to have them separate, but for any other aesthetic, (transparency, size, shape, color bar, etc), it doesn't make sense to separate them out. eg, transparency 0-25 is one variable and the other variable is 26-50, it's the same scale. of course colors can be more discrete, so that is how it could make more sense.. just my idea as to why this isn't straight-forward. hadley is very particular about his ggplotrawr
@rawr I'm writing a section of my thesis describing a layered grammar of graphics. In reality I can't see a useful application for this as we're overloading pre-attributes to the point that nothing stands out and they become attentively processed. The beauty of the grammar is that it is flexible within the closed system to the point where a graphic follows the grammar but makes no sense.Tyler Rinker

3 Answers

13
votes

If you use a filled plotting symbol, you can map one factor to fill and the other to colour, which then separates them into two scales and, therefore, legends.

ggplot(dat, aes(y = y, x = x)) +
  geom_point(aes(fill = a, size = z), pch = 21) + 
  geom_boxplot(fill = NA, size=.75, aes(color=b)) +
  scale_color_manual(values = c("orange", "purple")) +
  scale_fill_manual(values = c("#F8766D", "#00BFC4"))

enter image description here

4
votes

Seems that the legend capture approach is the most generalizable in similar situations, though in this specific on @jennybryan's is simpler and probably what most people would want. I document the legend capture approach here as well. I first learned this approach from @Sandy Muspratt HERE.

enter image description here

dat <- data.frame(
    y = rnorm(200),
    x = sample(c("A", "B"), 200, TRUE),
    z = sample(100:200, 200, TRUE), 
    a = sample(c("male", "female"), 200, TRUE),
    b = factor(sample(1:2, 200, TRUE))
)

if (!require("pacman")) install.packages("pacman")
pacman::p_load(ggplot2, grid, gridExtra, gtable)

coldot <- ggplot(dat, aes(y = y, x = x)) +
    geom_point(aes(color = a, size = z)) + 
    #geom_boxplot(fill = NA, size=.75, aes(color=b)) +
    scale_color_manual(values = c("#F8766D", "#00BFC4"))

colbox <- ggplot(dat, aes(y = y, x = x)) +
    #geom_point(aes(color = a, size = z)) + 
    geom_boxplot(fill = NA, size=.75, aes(color=b)) +
    scale_color_manual(values = c("orange", "purple"))



leg1 <- gtable_filter(ggplot_gtable(ggplot_build(coldot)), "guide-box") 
leg1Grob <- grobTree(leg1)

leg2 <- gtable_filter(ggplot_gtable(ggplot_build(colbox)), "guide-box") 
leg2Grob <- grobTree(leg2)


noleg <- ggplot(dat, aes(y = y, x = x)) +
    geom_point(aes(color = a, size = z)) + 
    geom_boxplot(fill = NA, size=.75, aes(color=b), position=position_dodge(1)) +
    scale_color_manual(values = c("orange", "purple", "#F8766D", "#00BFC4")) +
    theme(
        plot.margin = unit(c(5.1, 4.1, 4.1, 2.1), "pt"),
        legend.position=c(1.3, 0.87)
    ) +
    guides(color = FALSE)

legs <- ggplot(data = data.frame(x=1, y=1)) +
    geom_blank(aes(x=x, y=y)) + 
    theme_minimal() + 
    ylab(NULL) + xlab(NULL) +
    theme(
        axis.text = element_blank(),
        axis.ticks = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank() 
    ) +
    annotation_custom(leg1Grob, xmin=1, xmax=1, ymin=.95, ymax=1.3) +
    annotation_custom(leg2Grob, xmin=.6, xmax=.8, ymin=.75, ymax=1) 

out <- arrangeGrob(noleg, legs, ncol=2, widths=c(.85, .15))
print(out)
3
votes

ggnewscale makes this very easy:

library(ggplot2)
library(ggnewscale)

ggplot(dat, aes(y = y, x = x)) +
  geom_point(aes(color = a, size = z)) + 
  scale_color_brewer(palette = 'Dark2') +
  new_scale_color() +
  geom_boxplot(fill = NA, aes(color = b)) +
  scale_color_brewer(palette = 'Paired')

Created on 2020-01-08 by the reprex package (v0.3.0)

Any scale_color can be used as usual. I chose scale_color_brewer for convenience