My mental model of how alpha works is wrong

My mental model of alpha when plotting

When plotting overlapping elements with a defined alpha (opacity), I had always thought about it as an additive process with results clamped to not exceed 1.

Thus, without every bothering to check, my mental model was: three overlapping black rectangles, each with an opacity of 0.4, would result in a black rectangle since their combined opacity is 1.2, and this is clamped the maximum possible value of 1.

My mental model is wrong.

What actually happens with overlapping alpha

In each sub-plot, I’ve plotted the overlap of a number of rectangles, each with opacity of 0.4.

The combined opacity increases with each overlap but doesn’t quite reach an opacity of 1.

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Simple function to create a plot with N overlapping rectangles with 
# each having an alpha of 0.4
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stack_alpha <- function(N) {
  plot_df <- tibble(
    x       = seq(0.45, 0.55, length.out = N),
    y       = seq(0.45, 0.55, length.out = N), 
    opacity = 0.4
  )
  
  ggplot(plot_df) + 
    geom_tile(aes(x, y, alpha = I(opacity)), fill = 'black', 
              width=0.5, height = 0.5, colour = NA) + 
    theme_void() + 
    coord_equal()
}

plots <- lapply(1:4, stack_alpha)

patchwork::wrap_plots(plots, ncol = 4)

Correcting my thinking

Rather than talking about opacity, I should think in terms of transmissivity (i.e. 1-opacity) - and transmissivity is a multiplicative property.

So, for a sequence of N layers with a transmissivity t, the transmissivity of the result is t^N, and thus the opacity is (1 - t^N)

The following plots show the original 4 plots from above, and below them plots of my estimation of the combined alpha using multiplicative transmissivity

N opacity transmissivity cumulative transmissivity cumulative alpha
1 0.4 0.6 0.6 1 - 0.6 = 0.4
2 0.4 0.6 0.6 * 0.6 = 0.36 1 - 0.36 = 0.64
3 0.4 0.6 0.6 * 0.6 * 0.6 = 0.216 1 - 0.216 = 0.784
4 0.4 0.6 0.6 ^ 4 = 0.1296 1 - 0.1296 = 0.8704
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Manually calculate the total transmissivity for 1-4 overlapping rectangles.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
manually_stack_alpha <- function(N) {
  plot_df <- tibble(
    N = N,
    x = 0.5,
    y = 0.5, 
    tranmissivity = (1 - 0.4)^N,
    opacity       = 1 - tranmissivity
  )
  
  ggplot(plot_df) + 
    geom_tile(aes(x, y, alpha = I(opacity)), fill = 'black', 
              width=0.5, height = 0.5, colour = NA) + 
    theme_void() + 
    coord_equal() + 
    facet_wrap(~N + opacity, labeller = label_both)
}


manual_plots <- lapply(1:4, manually_stack_alpha)

patchwork::wrap_plots(c(plots, manual_plots), ncol = 4)

Summary

  • Opacity isn’t additive.
  • Transmissivity = 1 - Opacity.
  • Transmissivity is multiplicative