rggplot2density-plotridgeline-plot

Add a y-axis with density to ridgeline plot


I struggle to add a y-axis to my ridgeline plot which depicts the density. If I got it right a ridgeline plot is a kind of density plot. I used it in instead of a boxplot and also included quantile lines. I like it in the way it is, however my supervisor doesn’t see the benefits compared to a boxplot and always wanted me to include a y-axis with the density. Is it possible to add it or do I have to use density plots and do I have the possibility to arrange them as beautiful as geom_density_ridges does? Here one of my plots: enter image description here Here a reproduceable example:

ggplot(diamonds, aes(x = price, y = cut, fill = cut)) +
  geom_density_ridges(scale = 0.9) +
  theme_ridges() + 
  theme(legend.position = "none")

Thanks a lot!


Solution

  • I'm just going to go ahead and say this is an awful and hacky solution, but I've already spend the time so why the hell not post it? Who knows; such hacks might be convenient for someone else.

    The plan is: we're semi-manually going to replicate what ggridges does, but in vanilla ggplot2 and on a continuous scale. To do this, we'll be abusing the after_stat() function to (1) fill a ribbon with the densities and (2) superassign ourselves the maximum density, which we'll use as an offset.

    library(ggplot2)
    
    g <- ggplot(diamonds, aes(x = price, fill = cut)) +
      stat_density(
        geom = "ribbon", position = "identity",
        aes(ymax = after_stat(density + (group - 1) * max(density)),
            ymin = after_stat((group - 1) * {yoffset <<- max(density)}))
      )
    

    The flaw in this whole plan is that we don't know the offset in advance, so we'd need to build the plot at least once before we can actually use it.

    build <- ggplot_build(g)
    

    Lastly, we'll very manually assign the right values on the scales. We'll be using the primary for the discrete labels and the secondary for the continuous labels. You can look at the yoffset value that now should be in your global environment to get a sense of what the right breaks and labels are.

    g + scale_y_continuous(
        breaks = yoffset * (4:0), labels = levels(diamonds$cut),
        sec.axis = dup_axis(
          breaks = rep(yoffset * (0:4), 4) + rep(seq(0, 0.0003, by = 0.0001), each = 5),
          labels = rep(seq(0, 0.0003, by = 0.0001), each = 5)
        )
      )
    

    Created on 2021-07-15 by the reprex package (v1.0.0)

    A slightly more generalisable way of setting the breaks/labels:

    levels <- levels(diamonds$cut)
    labels <- scales::extended_breaks(3)(c(0, yoffset * 0.8))
    offsets <- yoffset * (seq(length(levels), 1) - 1)
    
    g + scale_y_continuous(
        breaks = offsets, labels = levels,
        sec.axis = dup_axis(
          breaks = rep(offsets, length(labels)) + rep(labels, each = length(offsets)),
          labels = rep(labels, each = length(offsets))
        )
      )