Pseudo-log transform still removes 0s from plot, how to avoid?

Viewed 1077

I have a data-set that includes 0s and ranges up to 1

set.seed(190)

b<-runif(10000,min=1e-15,1)

#Create 0s
b[sample(NROW(b),200)]<-0

I'm trying to recreate something like this that was made in python's Seaborn but using ggplot on a log10 scale. It appears that ggplot cannot first calculate the percentiles including the 0s and instead excludes them before transforming the axis. How can I replicate these plots?

:enter image description here

tibble(value=b, name=rep(1,1e4)) %>%
  ggplot(aes(x=name,y=b))+
  geom_boxplot(outlier.alpha=0.1)+
  scale_y_continuous(trans=scales::pseudo_log_trans(base = 10),limits=c(10^-12, 10^0), breaks=10^seq(-12,0,2))+
  stat_summary(fun=mean, geom="point", shape=4, size=3, color="black")

But this produces the following box plot and warns of removed non-finite rows. How do you avoid ggplot removing the log10(0)?

enter image description here

NB: I took the following stack question as inspiration.

How to deal with zero in log plot

2 Answers

Since the boxplot is base on percentiles, you can set values that are equal to 0 into a near-zero value, so the percentile is well calculated. Another thing is that you use a "scale_y" function, this functions in R do not "zoom in" but rather jack knife the data, so you should use a "coord_" function to "zoom in". The mean you should calculate outside the plot and then add it. This will do the trick:

set.seed(190)

b<-runif(10000,min=1e-15,1)

#Create 0s
b[sample(NROW(b),200)]<-0

val <- min(b[b != 0])

mn_val <- mean(b)

b_aux <- b
b_aux[b_aux == 0] <- val / 10

tibble(value=b_aux, name=rep(1,1e4)) %>%
  ggplot(aes(x=name,y=value))+
  coord_trans(y = 'log10', ylim = c(10^-12, 10^0)) +
  scale_y_continuous(breaks=10^seq(-12,0,2)) +
  geom_boxplot() +
  geom_point(y = mn_val,x = 1, shape=4, size=3, color="black")

You could set the breaks you would like to display and adjust the sigma parameter of pseudo_log_trans to the number of breaks.

You need to do this manually because there seems to be a pending issue on pseudo_log_trans breaks.

nbreaks <- 7
breaks <- 10^-(nbreaks:1)
breaks <- c(0,breaks)

tibble(name=rep(1,1e4),value=b) %>%
  ggplot(aes(x=name,y=b))+
  geom_boxplot()+
  scale_y_continuous(trans = pseudo_log_trans(sigma = 10^(-nbreaks), base = 10), breaks=breaks)+ 
  geom_point(aes(x=name,y=mean(b)), color = 'black', shape = 4, size=3)

enter image description here

#EDIT# Following rightful comment by @dww below, here is the corrected version for the whiskers:

# Function to use boxplot.stats to set the box-and-whisker locations
# see https://stackoverflow.com/a/38757310/13513328   
mybxp = function(x) {
  bxp = log10(boxplot.stats(10^x)[["stats"]])
  names(bxp) = c("ymin","lower", "middle","upper","ymax")
  return(bxp)
}  

tibble(name=rep(1,1e4),value=b) %>%
  ggplot(aes(x=name,y=b))+
  stat_summary(fun.data=mybxp, geom="boxplot") +
  scale_y_continuous(trans = pseudo_log_trans(sigma = 10^(-nbreaks), base = 10), breaks=breaks)+ 
  geom_point(aes(x=name,y=mean(b)), color = 'black', shape = 4, size=3)

enter image description here

Related