Search code examples
rggplot2

Shading regions of a plot based on whether a condition is satisfied


I'm creating lineplots using ggplot() and geom_line() for a corridor of values that develops over time.

It may happen sometimes that the upper bound is below the lower bound (which I'll call "inversion"), and I would like to highlight regions where this happens in my plot, say by using a different background color.

Searching both Google and StackOverflow has not led me anywhere.

Here is an artificial example:

library(tidyverse)
library(RcppRoll)

set.seed(42)
N <- 100
l <- 5
a <- rgamma(n = N, shape = 2)
d <- tibble(x = 1:N, upper = roll_maxr(a, n = l), lower = roll_minr(a + lag(a), n = l)) %>% mutate(inversion = upper < lower)
dl <- pivot_longer(d, cols = c("upper", "lower"), names_to = "Bounds", values_to = "bound_vals")

ggplot(dl, mapping = aes(x = x, y = bound_vals, color = Bounds)) + geom_line(linewidth = 1) + theme_light()

This produces the following plot:

example plot

As you can see, inversion occurs in a few places, e.g. around x = 50. I would like for the plot to have a darker (say gray) background where it does, based on the inversion column already in the tibble. How can I do this?

Thank you very much for the help!


Solution

  • Answering myself, the following worked for me in the end (also using actual data and plots grouped with facet_wrap()); h/t to @stefan, whose approach with geom_rect() I recycled:

    library(tidyverse)
    library(RcppRoll)
    
    set.seed(42)
    N <- 100
    l <- 5
    a <- rgamma(n = N, shape = 2)
    d <- tibble(x = 1:N, upper = roll_maxr(a, n = l), lower = roll_minr(a + lag(a), n = l)) %>%
        mutate(inversion = upper < lower,
               inversionLag = if_else(is.na(lag(inversion)), FALSE, lag(inversion)),
                inversionLead = if_else(is.na(lead(inversion)), FALSE, lead(inversion)),
            inversionStart = inversion & !inversionLag,
            inversionEnd = inversion & !inversionLead
        )
    dl <- pivot_longer(d, cols = c("upper", "lower"), names_to = "Bounds", values_to = "bound_vals")
    
    iS <- d %>% filter(inversionStart) %>% select(x) %>% rowid_to_column() %>% rename(iS = x)
    iE <- d %>% filter(inversionEnd) %>% select(x) %>% rowid_to_column() %>% rename(iE = x)
    iD <- iS %>% full_join(iE, by = c("rowid"))
    
    g <- ggplot(dl, mapping = aes(x = x, y = bound_vals, color = Bounds)) +
        geom_line(linewidth = 1) +
        geom_rect(data = iD, mapping = aes(xmin = iS, xmax = iE, fill = "Inversion"), ymin = -Inf, ymax = Inf, alpha = 0.3, inherit.aes = FALSE) +
        scale_fill_manual(name = "Inversions", values = "darkgray") +
        theme_light()
    g
    

    This gives

    sample output

    which is pretty much what I was after.