Search code examples
rggplot2data-visualizationribbon

Shaded area between two curves with different x values in ggplot?


I know that making a shaded area between two y curves with the same x values is as follows:

    geom_ribbon(data=dataframe,aes(ymin = y_lwr, ymax = y_upr), fill = "grey")

However, does anyone knows how we can plot the shaded area between two curves with different x values? When the lower curve is defined by (x_lwr, y_lwr) and the upper curve is defined by (x_upr, y_upr) The full data set is supposed to generate a graph as follows:

enter image description here

The sample data and code I have is as follows:

> head(df)
            y1     x1  y_lwr  x_lwr  y_upr  x_upr
    #> 1 11.60  67.01   4.97  86.28  14.54  58.17
    #> 2 11.32  68.57   4.51  88.99  13.74  61.67
    #> 3 10.76  71.63   4.15  91.29  13.00  64.74
    #> 4 10.19  75.52   3.82  92.69  12.35  67.83
    #> 5  9.91  77.33   3.60  94.19  11.71  70.84
    #> 6  9.62  79.14   3.46  94.90  11.21  73.33

    pltS <- ggplot(data=df, aes(x=df[,2], y=df[,1]))+
            ylab("log(y)")+ xlab("x")

    pltS <- pltS + geom_point(pch = 16, col="black", size=1)

    # upper and lower bands
    plt <- plt + geom_line(aes(x=df[,4], y=df[,3]), col="grey", size=1)
    plt <- plt + geom_line(aes(x=df[,6], y=df[,5]), col="grey", size=1)

    # x-axis & y-axis specifications
    plt <- plt + theme(aspect.ratio=1)+
                 scale_y_continuous(trans = 'log10')+
                 annotation_logticks(sides="l")+
                 scale_x_continuous(labels = function(x) paste0(x, "%"))
    plt

Solution

  • My initial thought was also geom_polygon, but actually, the easiest way to do this is to use geom_ribbon after reshaping your data.

    Suppose you have something like this:

    library(tidyverse)
    
    x1 <- seq(0, 2 * pi, 0.01)
    x2 <- x1 + 0.005
    y1 <- sin(x1)
    y2 <- cos(x2)
    df <- data.frame(x1, x2, y1, y2)
    
    head(df)
    #>     x1    x2          y1        y2
    #> 1 0.00 0.005 0.000000000 0.9999875
    #> 2 0.01 0.015 0.009999833 0.9998875
    #> 3 0.02 0.025 0.019998667 0.9996875
    #> 4 0.03 0.035 0.029995500 0.9993876
    #> 5 0.04 0.045 0.039989334 0.9989877
    #> 6 0.05 0.055 0.049979169 0.9984879
    

    Where you have two sets of x values and two sets of y values. You can simply convert to long format:

    df2 <- pivot_longer(df, c("x1", "x2"))
    
    head(df2)
    #> # A tibble: 6 x 4
    #>       y1    y2 name  value
    #>    <dbl> <dbl> <chr> <dbl>
    #> 1 0       1.00 x1    0    
    #> 2 0       1.00 x2    0.005
    #> 3 0.0100  1.00 x1    0.01 
    #> 4 0.0100  1.00 x2    0.015
    #> 5 0.0200  1.00 x1    0.02 
    #> 6 0.0200  1.00 x2    0.025
    

    Which then allows you to use geom_ribbon as normal:

    ggplot(df2, aes(x = value)) + 
      geom_ribbon(aes(ymax = y1, ymin = y2), alpha = 0.2, colour = "black")
    


    Edit

    Now that the OP has linked to the data, it is simpler to see where the problem lies. The rows contain 3 sets of x/y values representing points on the minimum line, points on the maximum line, and points on the mid line. However, the three sets of points are not grouped by x value and are not otherwise ordered. They therefore do not "belong" together in rows, and need to be separated into 3 groups which can then be left-joined back together into logical rows of x value, y value, y_min and y_max:

    library(tidyverse)
    
    df_mid   <- df %>% transmute(x = round(x1, 1), y = y1) %>% arrange(x)
    df_upper <- df %>% transmute(x = round(x_upr, 1), y_upr = y_upr)
    df_lower <- df %>% transmute(x = round(x_lwr, 1), y_lwr = y_lwr)
    
    left_join(df_mid, df_lower, by = "x")                    %>% 
      left_join(df_upper, by = "x")                          %>%
      filter(!duplicated(x) & !is.na(y_lwr) & !is.na(y_upr)) %>%
      ggplot(aes(x, y)) + 
       geom_line() +
       geom_ribbon(aes(ymax = y_lwr, ymin = y_upr), alpha = 0.2) +
       theme_bw() +
       theme(aspect.ratio = 1) +
       scale_y_continuous(trans = 'log10') +
       annotation_logticks(sides="l") +
       scale_x_continuous(labels = function(x) paste0(x, "%")) +
       ylab("log(y)") + xlab("x") 
    

    enter image description here