rggplot2geom-ribbon

geom_ribbon() with shape aesthetic causes differences in opacity


When using geom_ribbon() with the shape aesthetic, there appears to be a difference in the opacity on the shaded area creating blocks in the region. I have recreated the problem where I identified that these opacity changes are only present when the shape aesthetic is included.

Data set up:

alpha <- c("A","B","C","D", "E", "F", "G")
percent <- c(0.012, -0.02, 0.015, -0.01, 0.89, 0.12, -0.25)
flow <- c(-5, 2, -3, 3, 1, 4, -2)
shape <- c("D", "D", "L", "L", "L", "D", "L")

df <- data.frame(alpha,percent,flow, shape)

x_min = min(df$percent)
x_min = round(x_min/0.01)*0.01 - 0.01

x_max = max(df$percent)
x_max = round(x_max/0.01)*0.01 + 0.01

y_min = min(df$flow)
y_min = round(y_min)

y_max = max(df$flow)
y_max = round(y_max)

n_row = nrow(df)

Chart with no shape aesthetic, geom_ribbon() works:

df %>%
  ggplot(aes(x = percent, y = flow, label = alpha)) + 
  geom_point() + 
  geom_text_repel(show.legend = FALSE, size = 3) +
  scale_size_continuous(labels = scales::percent) + 
  theme_bw() + 
  scale_x_continuous(labels = scales::percent_format(accuracy = 0.1L)) + 
  scale_y_continuous(labels = scales::dollar_format(negative_parens = TRUE, suffix = "m")) + 
  geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = 0, ymax = y_max), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "green") + 
  geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = y_min, ymax = 0), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "red")

No shape aes, opacity fine

Chart with shape aesthetic, geom_ribbon() seems not to work:

df %>%
  ggplot(aes(x = percent, y = flow, label = alpha, shape = shape)) + 
  geom_point() + 
  geom_text_repel(show.legend = FALSE, size = 3) +
  scale_size_continuous(labels = scales::percent) + 
  theme_bw() + 
  scale_x_continuous(labels = scales::percent_format(accuracy = 0.1L)) + 
  scale_y_continuous(labels = scales::dollar_format(negative_parens = TRUE, suffix = "m")) + 
  geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = 0, ymax = y_max), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "green") + 
  geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = y_min, ymax = 0), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "red")

Shape aes included, opacity problem


Solution

  • The issue is some kind of overplotting and the way you add the background fills via geom_ribbon. Basically, by adding shape as a global aesthetic your data gets grouped and your ribbons are drawn multiple times, once for each group. To solve this issue make shape a local aes by moving it inside geom_point:

    library(ggplot2)
    
    ggplot(df, aes(x = percent, y = flow, label = alpha)) + 
      geom_point(aes(shape = shape)) + 
      ggrepel::geom_text_repel(show.legend = FALSE, size = 3) +
      scale_size_continuous(labels = scales::percent) + 
      theme_bw() + 
      scale_x_continuous(labels = scales::percent_format(accuracy = 0.1L)) + 
      scale_y_continuous(labels = scales::dollar_format(negative_parens = TRUE, suffix = "m")) + 
      geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = 0, ymax = y_max), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "green") + 
      geom_ribbon(aes(x = seq(x_min, x_max + 0.01, length.out = n_row), ymin = y_min, ymax = 0), alpha = 0.04, linetype = 0, show.legend = FALSE, fill = "red")
    

    However, instead of making use of geom_ribbon in my opinion you could get your result much easier and less error prone by adding your background rectangles via annotate, which depending on your desired result does not even require to compute the min and max values. Instead you could simply use -Inf and Inf:

    ggplot(df, aes(x = percent, y = flow, label = alpha, shape = shape)) + 
      geom_point() + 
      ggrepel::geom_text_repel(show.legend = FALSE, size = 3) +
      scale_size_continuous(labels = scales::percent) + 
      theme_bw() + 
      scale_x_continuous(labels = scales::percent_format(accuracy = 0.1)) + 
      scale_y_continuous(labels = scales::dollar_format(negative_parens = TRUE, suffix = "m")) + 
      annotate(geom = "rect", xmin = -Inf, xmax = Inf, ymin = 0, ymax = Inf, alpha = 0.04, fill = "green") + 
      annotate(geom = "rect", xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0, alpha = 0.04, fill = "red")