ggplot: transperancy of histogram as function of stat(count)

Viewed 142

I'm trying to make a scaled histogram in a such a way, that transparency of each "column" (bin?) depends on the number of observations in a given range of x. Here is my code:

set.seed(1)
test = data.frame(x = rnorm(200, mean = 0, sd = 10),
                  y = as.factor(sample(c(0,1), replace=TRUE, size=100)))
threshold = 20 
ggplot(test,
       aes(x = x))+
  geom_histogram(aes(fill = y, alpha = stat(count) > threshold),
                 position = "fill", bins = 10)

Basically I want to make plots that will looks like this:

enter image description here

however my code generate the plots there transparency are applied based on the count after grouping that ends up with hanging column like this:

enter image description here

For this example, in order to simulate a "proper" plot I just adjust the threshold, but I need alpha to consider sum of count from both groups in a given "column"(bin).

UPDATE: I also want it to work with faceted plots in a such a way that highlighted area in each facet was independent from other facets. Approach that proposed @Stefan works perfect for the individual plot, but in faceted plot highlights the same area at all facets.

library(ggplot2)

set.seed(1)
test = data.frame(x = rnorm(1000, mean = 0, sd = 10),
                  y = as.factor(sample(c(0,1), replace=TRUE, size=1000)),
                  n = as.factor(sample(c(0,1,2), replace=TRUE, size=1000)),
                  m = as.factor(sample(c(0,1,3,4), replace=TRUE, size=1000)))
f = function(..count.., ..x..) tapply(..count.., factor(..x..), sum)[factor(..x..)]
threshold = 10 
ggplot(test,
       aes(x = x))+
  geom_histogram(aes(fill = y, alpha = f(..count.., ..x..) > threshold),
                 position = "fill", bins = 10)+
  facet_grid(rows = vars(n),
             cols = vars(m))

enter image description here

1 Answers

This could be achieved like so:

  1. As the count computed by stat_count is the number of obs after grouping we have to manually aggregate the count over groups to get the total count per bin.
  2. To aggregate the counts per bin I use tapply, where I make use of the .. notation to get the variables computed by stat_count.
  3. As the grouping variable I make use of the computed variable ..x.. which to the best of my knowledge is not documented. Basically ..x.. contains by default the midpoints of the bins and as such can be used as an identifier for the bins. However, as these are continuous values we have convert them to a factor.

Finally, to make the code more readable I use a auxilliary function to compute the aggregate counts. Additionally I double the threshold value to 20.

library(ggplot2)

set.seed(1)
test <- data.frame(
  x = rnorm(200, mean = 0, sd = 10),
  y = as.factor(sample(c(0, 1), replace = TRUE, size = 100))
)
threshold <- 20

f <- function(..count.., ..x..) tapply(..count.., factor(..x..), sum)[factor(..x..)]
p <- ggplot(
  test,
  aes(x = x)
) +
  geom_histogram(aes(fill = y, alpha = f(..count.., ..x..) > threshold),
    position = "fill", bins = 10
  )
p

enter image description here

EDIT To allow for facetting we have to pass the function the ..PANEL.. identifier as an addtional argument. Instead of using tapply I now use dplyr::group_by and dplyr::add_count to compute the total count per bin and facet panel:

library(ggplot2)
library(dplyr)

set.seed(1)
test <- data.frame(
  x = rnorm(200, mean = 0, sd = 10),
  y = as.factor(sample(c(0, 1), replace = TRUE, size = 100)),
  type = rep(c("A", "B"), each = 100)
)
threshold <- 20

f <- function(count, x, PANEL) {
  data.frame(count, x, PANEL) %>% 
    add_count(x, PANEL, wt = count) %>% 
    pull(n)
}
p <- ggplot(
  test,
  aes(x = x)
) +
  geom_histogram(aes(fill = y, alpha = f(..count.., ..x.., ..PANEL..) > threshold),
                 position = "fill", bins = 10
  ) +
  facet_wrap(~type)
p
#> Warning: Using alpha for a discrete variable is not advised.
#> Warning: Removed 2 rows containing missing values (geom_bar).

Related