Finding the middle of the y axis in a ggplot with facets and free scales

50 Views Asked by At

I have a facetted density plot with free scales, to which I would like to overlay a geom_pointrange to represent the summary of the distribution. However, I'd like this point to be fixed in the middle of my y axis for each facets (with the x varying according to the summary value). Here is what my code looks like:

library(ggplot2)
library(dplyr)

data <- iris %>% 
  mutate(grouping1 = rep(1:3, length.out = nrow(iris)),
         variable = Sepal.Length) 

data_summ <- data %>% 
  group_by(grouping1) %>% 
  summarise(variable = mean(Sepal.Length), 
            lower = quantile(Sepal.Length, 0.025), 
            upper = quantile(Sepal.Length, 0.975))

ggplot(data) +
  geom_density(aes(x = variable,
                   fill = grouping1)) +
  facet_wrap(~ grouping1, scales = "free")

## What I would like to add
+ geom_pointrange(data = data_summ,
                  aes(x = variable,
                      xmin = lower, xmax = upper,
                      y = mid_y))

My plot looks like this: 1

And I would like it to result to looking something like this: 2

I've tried to obtain the y axis limis after building my ggplot, but haven't managed to extract the right ones (possibly due to each facet having multiple grouping1 variables?). Another idea was to create a secondary y axis and map to that but as that axis needs to be transformed based on the original one I'd still need the max value in each facet...

Any idea would be really appreciated!

2

There are 2 best solutions below

1
thothal On BEST ANSWER

You could construct the plot first and then use the corresponding slots to find the center:

data <- iris %>% 
  mutate(grouping1 = rep(1:3, length.out = nrow(iris)),
         variable = Sepal.Length)
pl <- ggplot(data) +
  geom_density(aes(x = variable,
                   fill = grouping1)) +
  facet_wrap(~ grouping1, scales = "free")

plob <- ggplot_build(pl)
data_summ <- data %>% 
  group_by(grouping1) %>% 
  summarise(variable = mean(Sepal.Length), 
            lower = quantile(Sepal.Length, 0.025), 
            upper = quantile(Sepal.Length, 0.975)) %>%
  mutate(mid_y = sapply(plob$layout$panel_scales_y, \(y) mean(y$get_limits())))

pl + geom_pointrange(data = data_summ,
                  aes(x = variable,
                      xmin = lower, xmax = upper,
                      y = mid_y))

Facetted density plot with a pointrange in the middle of each facet

1
teunbrand On

This is a pain to do with current ggplot2, however, the development version has a nice trick to do relative, data-agnostic placement of things. In the development version you can do y = I(0.5) to set it in the vertical middle of each panel.

# pak::pak("tidyverse/ggplot2") # installs development version of ggplot2
library(ggplot2)
library(dplyr)

iris <- iris %>% mutate(grouping2=rep(c(1:3), length.out=nrow(iris))) 
data_summ <- iris %>% 
  group_by(grouping2) %>% 
  summarise(variable = mean(Sepal.Length), 
            lower = quantile(Sepal.Length, 0.025), 
            upper = quantile(Sepal.Length, 0.975)) 

ggplot(iris) + 
  geom_density(aes(x=Sepal.Length, fill=Species)) + 
  geom_pointrange(
    data = data_summ,
    aes(x = variable, xmin = lower, xmax = upper, y = I(0.5))
  ) +
  facet_wrap(
    ~grouping2, 
    scales="free"
  )

Created on 2023-12-08 with reprex v2.0.2

(Disclaimer: I authored the PR that added this)