rggplot2geom-ribbon

Is there a ggplot geom to fill between two paths, not lines?


I have cyclic data that I am trying to plot a mean overlaid on a standard deviation band. Using geom_ribbon() has the same issue as geom_line() as it connects the nearest x values, not the order of the data. Is there a way to fill data between two paths, not lines?

Here is an example. As the resulting figure shows the standard deviation lines from geom_ribbon() are connected by nearest x whereas geom_path() correctly plots the lines. The desired graphic should fill the area between the red and blue standard deviation lines.

library(tidyverse)

DF <- tibble(
  Order = seq(1,20),
  x = c(seq(0,18,2), seq(19, 1, -2)),
  y = sin(4.5*(Order-1)*pi/180),
  sd1 = y + 1,
  sd2 = y - 1
)

p <- ggplot(DF, aes(x=x, y=y)) +
  geom_ribbon(aes(ymin = sd1, ymax = sd2), fill = 'lightblue', alpha = 0.2) +
  geom_path() +
  geom_path(aes(y=sd1), col = 'blue') +
  geom_path(aes(y=sd2), col = 'red')
    
ggsave('plot.png', p)

enter image description here


Solution

  • library(tidyverse)
    library(zoo)
    
    DF <- tibble(
      Order = seq(1,20),
      x = c(seq(0,18,2), seq(19, 1, -2)),
      y = sin(4.5*(Order-1)*pi/180),
      sd1 = y + 1,
      sd2 = y - 1
    )
    
    DF %>% 
      mutate(sl1 = (sd1 - lag(sd1, default = first(sd1))) / (x - lag(x, default = 1)),
             sl2 = (sd2 - lag(sd2, default = first(sd2))) / (x - lag(x, default = 1))) %>% 
      {full_join(filter(., sl1 < 0) %>% select(-sd2, -sl1, -sl2),
                 filter(., sl2 >= 0) %>% select(-sd1, -sl1, -sl2))} %>% 
      arrange(x) %>% 
      mutate(sd1_app = zoo::na.spline(sd1, na.rm = F),
             sd2_app = zoo::na.spline(sd2, na.rm = F)) -> DF1
    
    ggplot(DF, aes(x=x, y=y)) +
      geom_ribbon(data = DF1, aes(ymin = sd1_app, ymax = sd2_app), 
                  fill = 'lightblue', alpha = 0.3) +
      geom_path() +
      geom_path(aes(y=sd1), col = 'blue') +
      geom_path(aes(y=sd2), col = 'red')
    

    Created on 2024-06-25 with reprex v2.0.2