0

I'm plotting 2 densities and trying to add a few annotations that align horizontally while text is rotated 90 degrees but I can't seem to get them to line up when the annotations are of different character lengths.

library(ggplot2)

n <- 10000 
mu_a <- .089
mu_b <- .099
s_a <- .0092
s_b <- .004

df <- data.frame(
  variant = factor(c(rep("A", n),rep("B", n))),
  p = c(rnorm(n = n, mean = mu_a, sd = s_a), rnorm(n = n, mean = mu_b, sd = s_b)))


ggplot(df, aes(x = p, fill = variant)) +
  geom_density() +
  scale_x_continuous(labels = scales::percent) +
  scale_y_continuous(expand = expansion(mult = c(0, .1))) +
  annotate("text",
      x = c(mu_a,mu_b), 
      y = Inf,
     vjust = "center",
     hjust = 6, 
     label = c("5char","06char"),
     angle = 90
     )

Created on 2021-05-12 by the reprex package (v0.3.0)

Plot image at https://i.stack.imgur.com/89PjI.png

I've tried changing the y axis to a 0:1 scale with scale_y_continuous(y = ..scaled..) and then setting the annotation y values to fixed positions like y = 0.2 but then the densities aren't sized appropriately. Have tried all manner of combinations of hjust and vjust. I thought that these were supposed to work like percentages of the plot. So vjust = 0.2 means 20% up the plot, but it's not working like that for me. I was not expecting that by rotating the text 90 degrees, that hjust and vjust would swap, but that seems to be what happened.

Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • Using inspiration found here https://stackoverflow.com/questions/7705345/how-can-i-extract-plot-axes-ranges-for-a-ggplot2-object I tried grabbing the y scale limits from the plot and just setting the annotation y value relative to that. So if the plot object were `g` this would grab upper range of scale `label_y <- ggplot_build(g)$layout$panel_scales_y[[1]]$range$range[[2]] * 0.2`. Seems to work placing the label at 20% of plot height consistently. Better way other than adding annotation after building the plot? – alphanumerritt May 13 '21 at 16:27

2 Answers2

1

The best answers I've found here are:

  1. Use annotation_custom() with the grid package and the textGrob() function. This does allow positioning of annotations by % of x and y axis. Problem is you can't mix methods like setting x to point on the scale and y to % of scale like I'm trying to do.
  2. Calculate the upper end of the range of values in the plot. You can get this from a ggplot object like so ggplot_build(.)$layout$panel_scales_y[[1]]$range$range[[2]] or you can get it from a density function like so d <- density(.) then d$y[which.max(d$y)]. Once you have the upper end of the range, you can continue to build the plot by using a proportion of that upper end for the y placement.

Setting the y scale to ..scaled.. does indeed set the y scale to max 1, however, when plotting multiple densities, it sets both to their own scale rather than scaling accurately to each other. So wide and narrow densities will have the same height.

0

I'm not sure I'm 100% following, but try this:

ggplot(df, aes(x = p, ..scaled.., fill = variant)) +
  geom_density(alpha = 0.8, adjust = 0.2) +
  scale_x_continuous(labels = scales::percent) +
  scale_y_continuous(expand = expansion(mult = c(0, .1))) +
  annotate("text",
           x = c(mu_a,mu_b), 
           y = 0.5,
           hjust = 0.5, 
           label = c("5char","20charxxxxxxxxxxxxxx"),
           angle = 90
  )

The main callouts:

  • Set y to just be 0.5. It's a density plot, so it can be scaled so that the max y is always 1 using ..scaled.. in the mapping (see geom_density y-axis goes above 1)

  • Removed the vjust

  • Changed the hjust to be 0.5. While values >1 are accepted, it's easiest to just think of 0 as left-justified, 1 as right-justified, and 0 as center-justified. The reason it's hjust rather than vjust is because the justification is from the perspective of the text—not the orientation of the plot (this makes some sense—consider an angle that is anything other than 0 or 90).

  • I threw an alpha value into the`geom_density() function so that the full curve would show for both (which wasn't part of the question at all, but I couldn't help myself)

This should return the following:

Plot using the code above

  • It's the first callout upon which the rest hinges, right? If I change `s_a <- .00092` and `s_b <- .0004` then the max value becomes 1000. Not what I expected from a density plot either but obv. something I don't understand here. – alphanumerritt May 13 '21 at 15:43