2

I'm trying to get labels to line up with the values from a smooth line. While other answers I've seen suggest creating a data column of predicted values, I'm looking for a cleaner alternative that uses the data that is already being produced for the ggplot.

See example below for the problem:

require(tidyverse)
require(ggrepel)

set.seed(1)
df <- data.frame(x = rep(1:100, 5), y = c(sample(1:20, 100, T), sample(21:40, 100, T), sample(41:60, 100, T), sample(61:80, 100, T), sample(81:100, 100, T)), group = rep(letters[1:5], each = 100))
df <- tbl_df(df)

df %>% 
  ggplot(aes(x = x, y = y, label = group, color = group)) + 
  geom_smooth() +
  guides(color = F) +
  geom_text_repel(data = . %>% filter(x == max(x)), aes(x = x, y = y, label = group), nudge_x = 50)

Misaligned labels

Is there some way to get the smooth line value at max(x) without using ggplot_build() or another external, multi-step approach?

jzadra
  • 4,012
  • 2
  • 26
  • 46

1 Answers1

5

I'm not sure if this is really more elegant, but it's all in one pipe. I didn't have the "repel" version handy, but the idea is the same.

library(broom)

df %>%
  {ggplot(., aes(x, y, label = group, color = group)) + 
  geom_smooth() + 
  guides(color = F) +
  geom_text(data = group_by(., group) %>% 
                    do(augment(loess(y~x, .))) %>% 
                    filter(x == max(x)),
            aes(x, .fitted), nudge_x = 5)}

enter image description here

You need to get the prediction of the loess smoother at that final x value, so you just have to fit it twice. If the model-fitting is slow, you can do that once, higher in the dplyr chain, and just use the output for the rest of the figure.

df %>%
  group_by(group) %>% 
  do(augment(loess(y~x, .))) %>% 
  {ggplot(., aes(x, y, label = group, color = group)) + 
  geom_smooth() + 
  guides(color = F) +
  geom_text(data = filter(., x == max(x)),
            aes(x, .fitted), nudge_x = 5)}
Brian
  • 7,900
  • 1
  • 27
  • 41