R - ggplot2: Fill only the area between a line and a reference value

Viewed 62

I want to use ggplot2 to produce a graph like the one below, where the area between the line and a reference value is shaded, and the color of the shading depends on whether y is greater or less than the reference value.

CDC graph of 2020 primary and secondary syphilis cases relative to 2019 cases by reporting week

Here's a reprex using the LakeHuron dataset, setting 579 as the reference value, and using geom_ribbon() to do the shading. This gets close, but I only want to fill the area between the line and the reference line.

huron <- data.frame(year = 1875:1972, level = as.vector(LakeHuron))

ggplot(data = huron, aes(x = year)) + 
  geom_ribbon(aes(ymin = 579, ymax = level, fill = level > 579)) + 
  geom_line(aes(y = level)) +
  geom_hline(yintercept = 579)

huron example

Does anybody have any ideas? Thanks in advance.

2 Answers

A second option would be to use ggh4x::stat_difference which IMHO is one of the hidden gems of the ggh4x package:

huron <- data.frame(year = 1875:1972, level = as.vector(LakeHuron))

library(ggplot2)
library(ggh4x)

ggplot(huron, aes(x = year)) +
  ggh4x::stat_difference(aes(ymin = 579, ymax = level), levels = c("Above average", "Below Average")) +
  geom_line(aes(y = level)) +
  geom_hline(yintercept = 579) +
  scale_fill_manual(limits = c("Above average", "Below Average"), values = rev(scales::hue_pal()(2))) +
  labs(fill = NULL)

You need two ribbons if you want two different fills:

ggplot(data = huron, aes(x = year)) + 
  geom_ribbon(aes(ymin = 579, ymax = ifelse(level > 579, level, 579),
                  fill = "Above average")) +
  geom_ribbon(aes(ymax = 579, ymin = ifelse(level > 579, 579, level),
                  fill = "Below average")) +
  geom_line(aes(y = level)) +
  geom_hline(yintercept = 579) +
  scale_fill_manual(values = c("#86b2d8", "#bf311a"), name = NULL) +
  theme_classic(base_size = 16)

enter image description here

Related