Reputation: 915
My goal is to pass separate values to change the colors used for fill aesthetics in different geoms.
For example:
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(fill = Species), color = 'black', geom = 'bar', fun.y = mean) +
geom_point(aes(fill = Species), color = 'black', shape = 21) +
scale_fill_manual(values = c('royal blue', 'red2', 'limegreen'))
In this plot, I would like to be able to use separate colors to fill the bars and points. Is this possible? I'm aware of using scale_fill_manual()
to set the colors to whatever values I want, but this will change the fills of both the bars and the points to the same colors.
Here is a semi-working example of what I am trying to do, however, the legend is off...
iris_j <- iris %>%
mutate(Species_bar = factor(paste0(Species, '_bar')))
color.groups <- c('royal blue', 'red2', 'limegreen', NA, 'royal blue', 'white')
names(color.groups) <- c(levels(iris_j$Species), levels(iris_j$Species_bar))
ggplot(iris_j, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(fill = Species_bar), color = 'black', geom = 'bar', fun.y = mean) +
geom_point(aes(fill = Species), color = 'black', shape = 21) +
scale_fill_manual(values = color.groups)
Upvotes: 1
Views: 3581
Reputation: 43344
This is one of the limitations of ggplot—an aesthetic can only be mapped to a single variable. Generally speaking, I find it a reasonable limitation, as it forestalls a lot of confusing and hard-to-read graphs. That said, with some creativity, it can be worked around, e.g. by coloring the points with the color
aesthetic, and then overplotting to add a stroke:
library(ggplot2)
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(fill = Species), color = 'black', geom = 'bar', fun.y = mean) +
geom_point(aes(color = Species)) + # add colored points
geom_point(color = 'black', shape = 21, show.legend = TRUE) + # add point strokes (including in legend)
scale_color_manual(values = c('royal blue', 'red2', 'limegreen')) + # define point colors
scale_fill_manual(values = c(NA, 'royal blue', 'white')) # define bar colors
To separate the legends, specify a different name for each. To add a stroke to the points in the legend, you'll need to effectively rebuild it in guide_legend
. (According to the docs, supplying a named vector to show.legend
should work, but in practice it fails.)
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(fill = Species), color = 'black', geom = 'bar', fun.y = mean) +
geom_point(aes(color = Species)) +
geom_point(color = 'black', shape = 21) +
scale_color_manual('points', values = c('royal blue', 'red2', 'limegreen'),
guide = guide_legend(override.aes = list(shape = 21, color = 'black',
fill = c('royal blue', 'red2', 'limegreen')))) +
scale_fill_manual('bars', values = c(NA, 'royal blue', 'white'))
Such an approach will not generalize to a plot where color is already being used otherwise.
Upvotes: 4
Reputation: 16842
Here are a few little things to try that you could build off of.
First up, if you don't need to use a filled shape, you can just map color to the species in geom_point
, so you have a color scale and a fill scale. In this case, I changed the label for fill to mark it as being the means, to show how you can split these into two legends.
library(tidyverse)
light_colors <- c("#87CEEB", "#FFB6C1", "#FF8C69")
dark_colors <- c("#22A0D6", "#E33650", "#BF411B")
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(fill = Species), geom = "bar", fun.y = mean) +
geom_point(aes(color = Species)) +
scale_fill_manual(values = light_colors) +
scale_color_manual(values = dark_colors) +
labs(fill = "Mean by Species")
Second, if you do need a filled shape, let geom_point
get a fill scale and hack the bars to have a color instead. One way to do that is by making what look like bars but are actually really big geom_segment
s. I changed the size in the legend to make the legend keys not ridiculously huge.
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
stat_summary(aes(xend = Species, yend = 0, color = Species), geom = "segment", fun.y = mean, size = 30, lineend = "butt") +
geom_point(aes(fill = Species), color = "black", shape = 21) +
scale_fill_manual(values = light_colors) +
scale_color_manual(values = dark_colors, guide = guide_legend(override.aes = list(size = 4)))
Third way, make a data frame of averages and give it a variable to denote that it's got averages, then add a variable to the original data frame to denote that it's observations. Then you can map the interaction of type with species to get separate colors in one fill scale.
avgs <- iris %>%
group_by(Species) %>%
summarise(Sepal.Length = mean(Sepal.Length)) %>%
mutate(type = "Mean")
iris %>%
select(Species, Sepal.Length) %>%
mutate(type = "Observation") %>%
ggplot(aes(x = Species, y = Sepal.Length, fill = interaction(Species, type))) +
geom_col(data = avgs) +
geom_point(color = "black", shape = 21)
Upvotes: 3
Reputation: 403
Not quite a perfect solution, but it may be a sufficient workaround
cols_1 <- c("red", "green", "blue")
cols_2 <- c("orange", "purple", "yellow")
ggplot(iris, aes(x = Species, y = Sepal.Length)) +
geom_point(aes(color = Species)) + # Using color instead of fill
stat_summary(aes(fill = Species), color = 'black', geom = 'bar', fun.y = mean, alpha = c(0.5, 0.05, 1)) +
scale_color_manual(values = cols_1) + # colors your points
scale_fill_manual(values = cols_2) # fills your Summary Bars
Adjust the colors, alpha, and other graphical parameters as you see fit.
Upvotes: 2