How to show directlabels after geom_smooth and not after geom_line?
Asked Answered
C

4

8

I'm using directlabels to annotate my plot. As you can see in this picture the labels are after geom_line but I want them after geom_smooth. Is this supported by directlabels? Or any other ideas how to achieve this? Thanks in advance!

enter image description here

This is my code:

library(ggplot2)
library(directlabels)

set.seed(124234345)

# Generate data
df.2 <- data.frame("n_gram" = c("word1"),
                   "year" = rep(100:199),
                   "match_count" = runif(100 ,min = 1000 , max = 2000))

df.2 <- rbind(df.2, data.frame("n_gram" = c("word2"),
                      "year" = rep(100:199),
                      "match_count" = runif(100 ,min = 1000 , max = 2000)) )

# plot
ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show_guide=F) +
  stat_smooth(size=2, span=0.3, se=F, show_guide=F) +
  geom_dl(aes(label=n_gram), method = "last.bumpup", show_guide=F) +
  xlim(c(100,220))
Carpel answered 8/4, 2012 at 18:19 Comment(2)
NB - missing library(directlabels)Galvanism
ggrepel does a nice job of labelling the end of lines (eg here - I wonder how hard it would be to modify for a fitted line?Galvanism
C
-1

I'm gonna answer my own question here, since I figured it out thanks to a response from Tyler Rinker.

This is how I solved it using loess() to get label positions.

 # Function to get last Y-value from loess
funcDlMove <- function (n_gram) {

  model <- loess(match_count ~ year, df.2[df.2$n_gram==n_gram,], span=0.3)
  Y <- model$fitted[length(model$fitted)]
  Y <- dl.move(n_gram, y=Y,x=200)
  return(Y)
}

index <- unique(df.2$n_gram)
mymethod <- list(
  "top.points", 
  lapply(index, funcDlMove)
  )

# Plot

PLOT <- ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show_guide=F) +
  stat_smooth(size=2, span=0.3, se=F, show_guide=F)

direct.label(PLOT, mymethod)

Which will generate this plot: https://i.stack.imgur.com/FGK1w.png

Carpel answered 9/4, 2012 at 6:46 Comment(0)
G
6

This answer takes the basic concept of @celt-Ail's answer, and rather than function, base R, and direct label, attempts a tidyverse approach, stealing some code from here for the multiple loess models.

Happy to hear suggested improvements.

set.seed(124234345)

# Generate data
df.2 <- data.frame("n_gram" = c("word1"),
                   "year" = rep(100:199),
                   "match_count" = runif(100 ,min = 1000 , max = 2000))

df.2 <- rbind(df.2, data.frame("n_gram" = c("word2"),
                               "year" = rep(100:199),
                               "match_count" = runif(100 ,min = 1000 , max = 2000)) )

#example of loess for multiple models
#https://mcmap.net/q/103275/-loess-regression-on-each-group-with-dplyr-group_by
library(dplyr)
library(tidyr)
library(purrr)
library(ggplot2)

models <- df.2 %>%
  tidyr::nest(-n_gram) %>%
  dplyr::mutate(
    # Perform loess calculation on each CpG group
    m = purrr::map(data, loess,
                   formula = match_count ~ year, span = .3),
    # Retrieve the fitted values from each model
    fitted = purrr::map(m, `[[`, "fitted")
  )

# Apply fitted y's as a new column
results <- models %>%
  dplyr::select(-m) %>%
  tidyr::unnest()

#find final x values for each group
my_last_points <- results %>% group_by(n_gram) %>% summarise(year = max(year, na.rm=TRUE))

#Join dataframe of predictions to group labels
my_last_points$pred_y <- left_join(my_last_points, results)

# Plot with loess line for each group
ggplot(results, aes(x = year, y = match_count, group = n_gram, colour = n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show.legend=F) +
  #stat_smooth(size=2, span=0.3, se=F, show_guide=F)
  geom_point() +
  geom_line(aes(y = fitted))+  
  geom_text(data = my_last_points, aes(x=year+5, y=pred_y$fitted, label = n_gram))

direct_label

Galvanism answered 22/4, 2020 at 10:14 Comment(3)
By adding library(ggrepel) and replacing geom_text() with geom_text_repel(), this solution works well when your plot gets busier.Options you may want to include in the call to geom_text_repel() include hjust=0, direction = "y", force=2.Galvanism
And if you want to use linear or polynomial fit options (instead of loess), you may find this enlighteningGalvanism
And to neatly increase the x axis to accomodate longer labels, something like this is helpful scale_x_continuous(expand = expansion(mult = c(0, .25))) where it is making the x axis 25% longer to the right.Galvanism
W
3
# use stat smooth with geom_dl to get matching direct labels.
span <- 0.3
ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey") +
  stat_smooth(size=2, span=span, se=F) +
  geom_dl(aes(label=n_gram), method = "last.qp", stat="smooth", span=span) +
  xlim(c(100,220))+
  guides(colour="none")
Wadmal answered 22/10, 2013 at 5:20 Comment(1)
I get an error message trying to run this: Warning message: Computation failed in 'stat_smooth()': object 'last.qp' of mode 'function' was not found Galvanism
I
0

This is not what you asked for as I don't know how to do that, but this might be more useful to you as you will lose less plotting area to labels:

PLOT <- ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show_guide=F) +
  stat_smooth(size=2, span=0.3, se=F, show_guide=F) 

mymethod <- list(
    "top.points", 
    dl.move("word1", hjust=-6.65, vjust=13),
    dl.move("word2", hjust =-7.9, vjust=20.25)
)

direct.label(PLOT, mymethod)

which yields:

enter image description here

You could also try:

mymethod <- list(
    "top.points", 
    dl.move("word1", hjust=-6, vjust=14),
    dl.move("word2", hjust =-7.1, vjust=19.5)
)

ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show_guide=F) +
  xlim(c(100,220))+
  stat_smooth(size=2, span=0.3, se=F, show_guide=F) +
  geom_dl(aes(label=n_gram), method = mymethod, show_guide=F)

which yields:

enter image description here

NOTE: to print to other graphics devices (this was the windows rgui) you'll need to tweak the vjust and hjust to suit. But if there's a more direct way that would be nicer.

Intussusception answered 8/4, 2012 at 19:1 Comment(2)
Thanks! It's a bit tedious to manually adjust each label if I use this on data sets with more groups. But I used your idea to create a function to do this automatically. I'm gonna post that as an answer to my own question, I hope that's proper etiquette.Carpel
Yes please do. The point of this forum (at least to me) is to get the best solution to a problem in a searchable format. sometimes we build off of the responses of others and improve them.Intussusception
C
-1

I'm gonna answer my own question here, since I figured it out thanks to a response from Tyler Rinker.

This is how I solved it using loess() to get label positions.

 # Function to get last Y-value from loess
funcDlMove <- function (n_gram) {

  model <- loess(match_count ~ year, df.2[df.2$n_gram==n_gram,], span=0.3)
  Y <- model$fitted[length(model$fitted)]
  Y <- dl.move(n_gram, y=Y,x=200)
  return(Y)
}

index <- unique(df.2$n_gram)
mymethod <- list(
  "top.points", 
  lapply(index, funcDlMove)
  )

# Plot

PLOT <- ggplot(df.2, aes(year, match_count, group=n_gram, color=n_gram)) +
  geom_line(alpha = I(7/10), color="grey", show_guide=F) +
  stat_smooth(size=2, span=0.3, se=F, show_guide=F)

direct.label(PLOT, mymethod)

Which will generate this plot: https://i.stack.imgur.com/FGK1w.png

Carpel answered 9/4, 2012 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.