How to include labels for a segmented geom_smooth in r?

Viewed 141

I would like to label my geom_smooth in r but the labels are closer to the actual points, and not to the lines.

Data:

df <- structure(list(t = c(45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 
47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 50, 
50, 50, 50, 50, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 53, 53, 
53, 53, 53, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 
56, 56, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 59, 59, 59, 59, 
59, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 
63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65), 
    x = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), name = c("P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4", "P1", "P2", "P2+", "P3", "P4", "P1", "P2", 
    "P2+", "P3", "P4"), value = c(48L, 132L, 111L, 115L, 2L, 
    58L, 126L, 82L, 74L, 0L, 45L, 119L, 78L, 87L, 0L, 56L, 106L, 
    105L, 88L, 1L, 52L, 78L, 91L, 107L, 1L, 35L, 96L, 86L, 98L, 
    1L, 61L, 118L, 90L, 108L, 2L, 45L, 114L, 93L, 98L, 2L, 55L, 
    108L, 78L, 76L, 7L, 44L, 97L, 94L, 96L, 0L, 40L, 111L, 93L, 
    88L, 1L, 43L, 78L, 66L, 113L, 2L, 20L, 57L, 84L, 41L, 0L, 
    17L, 51L, 81L, 34L, 0L, 40L, 55L, 64L, 32L, 0L, 25L, 67L, 
    71L, 37L, 0L, 16L, 67L, 60L, 57L, 0L, 23L, 46L, 62L, 47L, 
    1L, 34L, 75L, 68L, 39L, 0L, 34L, 60L, 85L, 24L, 0L, 20L, 
    58L, 63L, 37L, 1L)), row.names = c(NA, -105L), class = c("tbl_df", 
"tbl", "data.frame"))

My code:

df %>% 
  group_by(name) %>% 
  # New column to label the first and last values
  mutate(label =
           case_when(
             t == min(t) | t == max(t) ~ name,
             TRUE ~ NA_character_)) %>% 
  ggplot(aes(x = t, y = value, color = name, group = interaction(x, name), shape = name)) +
  geom_smooth(se = F) +
  geom_vline(xintercept = 57, linetype = "dashed", lwd = 0.3) + 
  scale_color_discrete(guide = 'none') +
  geom_label_repel(aes(label = label))

My graph: enter image description here

Also, is it possible to get rid of the lines pointing to the smoothed loess curves?

1 Answers

One option to achieve your desired result would be to use stat="smooth" in ggrepel::geom_label_repel. Doing so will put the labels on the fitted points instead of on the data points. However, to make this work you have to add the labels on the fly inside geom_label_repel using e.g. after_stat(ifelse(x %in% range(x), color, NA_character_)):

library(ggplot2)
library(ggrepel)

ggplot(df, aes(x = t, y = value, color = name, group = interaction(x, name), shape = name)) +
  geom_smooth(se = F) +
  geom_vline(xintercept = 57, linetype = "dashed", lwd = 0.3) +
  scale_color_discrete(guide = "none") +
  ggrepel::geom_label_repel(aes(label = after_stat(ifelse(x %in% range(x), color, NA_character_))), stat = "smooth")

EDIT There are probably different routes to put the labels on the left (right) at the (lower) upper end of the data range. My approach is probably not elegant. But after I had to prepare a lot of charts lately with labels at the endpoints I decided doing this via two text layers is in my opinion the easiest way to achieve this as it allows one to make us of nudging to shift the labels to the left or right. To make this work I increased the expansion of the x scale to make room for the labels. Additionally I removed the segments in the edit and set .direction = "y" so that labels are repelled only in the y direction.

library(ggplot2)
library(ggrepel)

ggplot(df, aes(x = t, y = value, color = name, group = interaction(x, name), shape = name)) +
  geom_smooth(se = F) +
  geom_vline(xintercept = 57, linetype = "dashed", lwd = 0.3) +
  scale_color_discrete(guide = "none") +
  scale_x_continuous(expand = c(.1, .1)) +
  ggrepel::geom_label_repel(aes(label = after_stat(ifelse(x %in% range(x)[1], color, NA_character_))), 
                            stat = "smooth",
                            nudge_x = -1,
                            min.segment.length = Inf,
                            direction = "y") +
  ggrepel::geom_label_repel(aes(label = after_stat(ifelse(x %in% range(x)[2], color, NA_character_))), 
                            stat = "smooth",
                            nudge_x = 1,
                            min.segment.length = Inf,
                            direction = "y")

Related