Setting colours in ggplot conditional on value



I have a pretty basic bar chart but I want to have the colours be conditional on the value. i.e. between 17-20 it's green, 14-16 it's yellow etc. I believe that ggplot assigns colours in order, so they won't always match up as I'm producing several reports for different department. Some sample code:

factor <- c ("Factor1", "Factor2", "Factor3", "Factor4", "Factor5")
mean <- c (9, 18, 14, 19, 17)
df <- data.frame (factor, mean)
#Add cutpoints
df <- df %>%
  mutate (factor = as.factor(factor),
          concern = cut (mean, 
                      breaks = c(-Inf, 9, 13, 17, Inf),
                      labels = c("Serious", "Significant", "Minimal", "Strengths"),
                      right = TRUE)
#COlour Palette
pal <- c("forestgreen", "yellow", "orange", "red")

df %>%
  ggplot (aes (x = factor, y = mean, fill = concern)) + 
  geom_col() + 
  coord_flip() +
  scale_x_discrete(limits = rev(levels(df$factor))) +
  scale_fill_manual(values = pal)

Produces this:

I want Factor1 to be red, Factor2 and 4 to be green, etc. I also need it to assign colours based on the column "mean" as the next chart will have different results.

Hope this makes sense


Try making your palette a named vector, with names matching your factor levels, in order to control which color gets matched to what. Per the docs:

If this is a named vector, then the values will be matched based on the names. If unnamed, values will be matched in order (usually alphabetical) with the limits of the scale.

The named vector approach is the recommended one (but this advice is a bit buried — it's mentioned in a comment to one of the examples in the docs).

So in your case:

#Colour Palette
pal <- c(
  "Serious" = "red",
  "Significant" = "orange", 
  "Minimal" = "yellow", 
  "Strengths" = "forestgreen" 

df %>%
  ggplot(aes(x = factor, y = mean, fill = concern)) + 
  geom_col() + 
  coord_flip() +
  scale_x_discrete(limits = rev(levels(df$factor))) +
    values = pal,
    limits = names(pal)

Adding limits to scale_fill_manual ensures that all the levels will appear in the legend, even if they don't all appear in the data (as in this case).


If you want your legend to be in the reverse order (Strengths –> Serious), then either change the order of the elements in pal, or add this to the end of your ggplot() call:

+ guides(fill = guide_legend(reverse = TRUE))

I'm not sure I follow this bit — is it an additional thing you want the plot to do that it doesn't do, or just an explanation of your approach so far? (if the latter, I say :+1:)


Worked perfectly. Thanks so much!