3

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?

HNSKD
  • 1,614
  • 2
  • 14
  • 25
  • 1
    have you tried simply using `ggplot2::geom_label`? – tjebo Oct 27 '21 at 11:05
  • Small lines pointing to the smoothed loess curves are made by ggrepel. To remove them, either use `geom_label`, as suggested above, or `geom_label_repel(..., min.segment.length = 1)` in which case they are only printed when label is too far from curve due to repelling. Another option is `..., segment.colour=NA, ...` – Pierre Gramme Oct 27 '21 at 17:50
  • related https://stackoverflow.com/questions/29357612/plot-labels-at-ends-of-lines – tjebo Jul 11 '22 at 07:49

1 Answers1

4

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")

stefan
  • 90,330
  • 6
  • 25
  • 51
  • Thanks for this great answer. One more thing, do you know how I can place the labels outside the xranges? like the ones on the right, to be at x = 65 onwards and the ones on the left to be smaller than x = 45? – HNSKD Oct 30 '21 at 02:23