Patrick
Patrick

Reputation: 291

Conditional coloring of geom_point based on the value of a geom in a different layer

Creating a bare bones number line with the min, max, and median plotted along with the average for a second layer as a point on the line. I'd like to be able to change the color of the point depending on its relative position to the median.

Here is some example data:

library(tidyverse)
df <- data.frame(
  species = rep(c('dog','cat'),5),
  trait = 'weight', 
  value = sample(5:25, 5))

df %>%
  ggplot(aes(value,trait)) +
  stat_boxplot(geom = "errorbar", width = 0.5) +
  stat_summary(fun=median, geom="segment", aes(xend=..x.., yend=1.25, y = 0.75)) +
  stat_summary(data = (df %>% filter(species == 'dog')), fun=mean,
               geom="point",  size=4,
               shape = 15, 
               color = 'green') + # i'd like to change thhis color
  theme_minimal() +
  theme(line = element_blank(), text = element_blank())

Currently this is my plot where the point is always green. I'd like to change it to red if the point was less than the median.

Below is a version that works, but I am wondering if there is anything cleaner and more efficient? Ideally something where I can directly compare the x value of the point and directly compare to the x value of the segment created in the prior stat_summary layer.

something along the lines of this pseduocode

ifelse(point.x > segment.x, 'green', 'red')

current code:

color = ifelse(mean(test %>%
                                      filter(species == 'dog') %>%
                                      pull(value)) >
                                 mean(test %>% pull(value)),
                                    'green', 'red'))

enter image description here

Upvotes: 2

Views: 504

Answers (2)

tjebo
tjebo

Reputation: 23737

You could make use of the relatively new after_stat. That said, if you’re not quite sure of what happens in the computation of your Stat, the safer option would be to follow Jon’s idea.

I used Jon's data (thank you and +1 :)

library(tidyverse)
set.seed(1)
df <- data.frame(
  species = rep(c('dog','cat'),5),
  trait = 'weight', 
  value = sample(5:25, 10))

df %>%
  ggplot(aes(value,trait)) +
  stat_boxplot(geom = "errorbar", width = 0.5) +
  stat_summary(fun=median, geom="segment", aes(xend=..x.., yend=1.25, y = 0.75)) +
  stat_summary(aes(color = after_stat(x) < median(x)), fun=mean,
               geom="point",  size=4,
               shape = 15) +
  facet_wrap(~species)

Created on 2021-03-23 by the reprex package (v1.0.0)

Upvotes: 1

Jon Spring
Jon Spring

Reputation: 66480

set.seed(1)
df <- data.frame(
  species = rep(c('dog','cat'),5),
  trait = 'weight', 
  value = sample(5:25, 10)) # changed to make dif cat and dog #s

I'd suggest calculating the by-group mean and median before ggplot to make the comparison simple.

library(dplyr)
df_sum <- df %>%
  group_by(species) %>%
  summarize(grp_median = median(value),
            grp_mean = mean(value))

df %>%  
  ggplot(aes(value,species)) +
  stat_boxplot(geom = "errorbar", width = 0.5) +
  geom_segment(data = df_sum,
               aes(y = as.numeric(as.factor(species)) - 0.1,
                   yend = as.numeric(as.factor(species)) + 0.1,
                   x = grp_median, xend = grp_median), inherit.aes = F) +
  geom_point(data = df_sum,
             aes(grp_mean, species, color = grp_mean < grp_median)) +
  scale_color_manual(values = c("green", "red"), guide = F) +
  theme_minimal() +
  theme(line = element_blank(), text = element_blank())

enter image description here

Upvotes: 2

Related Questions