5

I am trying to annotate a line plot with an arrow pointing to the highest point in line plot and displaying an arrow and maximum value on the plot. I am using the mtcars dataset as my reference. Below is my code.

e <- df$mpg
ggplot(df, aes(x=e, y=df$hp)) + 
  geom_line() + 
  annotate("segment", color="blue", x=max(e), xend = max(e), y=max(df$hp), 
            yend=max(df$hp), arrow=arrow())

Thanks in advance,

Machavity
  • 30,841
  • 27
  • 92
  • 100
Data_is_Power
  • 765
  • 3
  • 12
  • 30

2 Answers2

8

Are you looking for something like this:

labels <- data.frame(mpg = mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"]+7, hp = mtcars[which(mtcars$hp == max(mtcars$hp)), "hp"],text = paste0("Max value at mpg = ", mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"], " and hp = ", max(mtcars$hp)))


ggplot(mtcars, aes(mpg, hp))+
    geom_line()+
    geom_text(data = labels, aes(label = text))+
    annotate("segment", 
        x=mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"]+2,
        xend=mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"]+.2, 
        y= mtcars[which(mtcars$hp == max(mtcars$hp)), "hp"],
        yend= mtcars[which(mtcars$hp == max(mtcars$hp)), "hp"], 
        arrow=arrow(), color = "blue")

enter image description here

Explanation: In order to annotate with the max, we need to find the position of mpg that is the maximum for hp. To do this we use mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"]. The which() statement gives us the row possition of that maximum so that we can get the correct value of mpg. Next we annotate with this position adding a little bit of space (i.e., the +2 and +.2) so that it looks nicer. Lastly, we can construct a dataframe with the same positions (but different offset) and use geom_text() to add the data label.

AndS.
  • 7,748
  • 2
  • 12
  • 17
  • Yes, I would like to display maximum value. If possible can you also provide explanation ?. Thanks ! – Data_is_Power Aug 05 '18 at 20:26
  • Yes, let me update with displaying the maximum value. and then I'll add the explanation with it. – AndS. Aug 05 '18 at 20:29
  • Great.Thanks in advance ! – Data_is_Power Aug 05 '18 at 20:31
  • Quick question. Why was 7 added to mpg = mtcars[which(mtcars$hp == max(mtcars$hp)), "mpg"]+7 ? – Data_is_Power Aug 05 '18 at 20:50
  • 1
    Purely aesthetic. I added an arbitrary 7 units to offset the text from the arrow. That value will depend on how long your text is and the size of the plot you want to make. I just changed that number until I found one that looked nice to me. There may be a way to do that more programatically. – AndS. Aug 05 '18 at 20:52
5

Here is an alternative approach using package 'ggpmisc'. The code below uses a statistic instead of annotate() making it more generally useful. As stat_peaks() finds the peak and builds the labels on-the-fly this code can be used together with facets or grouped data nearly unchanged. By changing the value passed to span it is possible to highlight multiple peaks per group of data.

The main limitation of using annotate() is that this function works independently of the ggplot data, grouping or facets. With facets the same annotation is replicated in each panel. By design annotate() is aimed at adding annotations such as labels that are rather independent of the plotted data.

Highlighting a feature of the data like an observation at the peak, is not a true plot annotation but what is technicaly called a data label. The idea of using geom_text_s() (the "s" in the name is for segment) is to avoid the need of manual positioning and linking a text label to an observation. To achieve the linking we displace the label with position_nudge_keep() which is a variation of ggplot2::position_nudge() that keeps a copy of the original location making it possible to draw the label plus a segment or arrow with ggpp::geom_text_s() or ggrepel::geom_text_repel().

There is a companion statistic stat_valleys() that can be used to highlight minima. In 'ggpmisc' (>= 0.4.5) these stats support flipping x and y by means of parameter orientation.

Note: I added geom_point() to highlight the observations.

library(ggplot2)
library(ggpmisc)

ggplot(mtcars, aes(mpg, hp)) +
  geom_line() +
  geom_point() +
  stat_peaks(span = NULL,
             geom = "text_s",
             mapping = aes(label = paste(after_stat(y.label), after_stat(x.label))),
             x.label.fmt = "at %.0f mpg",
             y.label.fmt = " Max hp = %.0f",
             segment.colour = "blue",
             arrow = grid::arrow(length = unit(0.1, "inches")),
             position = position_nudge_keep(x = 1, y = 0),
             hjust = 0)

enter image description here

Mapping variable vs to the color aesthetic creates two groups. For this example we need to tweak the nudging to avoid label overlaps.

ggplot(mtcars, aes(mpg, hp, color = factor(vs))) +
  geom_line() +
  geom_point() +
  stat_peaks(span = NULL,
             geom = "text_s",
             mapping = aes(label = paste(after_stat(y.label), after_stat(x.label))),
             x.label.fmt = "at %.0f mpg ",
             y.label.fmt = " Max hp = %.0f",
             arrow = grid::arrow(length = unit(0.1, "inches")),
             position = position_nudge_keep(x = c(1, -1),  y = 10),
             hjust = c(0, 1))

enter image description here

Or we can use facet_wrap() to create panels.

ggplot(mtcars, aes(mpg, hp)) +
  geom_line() +
  geom_point() +
  stat_peaks(span = NULL,
             geom = "text_s",
             mapping = aes(label = paste(after_stat(y.label), after_stat(x.label))),
             x.label.fmt = "at %.0f mpg",
             y.label.fmt = " Max hp = %.0f",
             segment.colour = "blue",
             arrow = grid::arrow(length = unit(0.1, "inches")),
             position = position_nudge_keep(x = 2, y = 10),
             hjust = 0) +
  facet_wrap(~factor(vs))

enter image description here

Bug fixes in 'ggpmisc' (>= 0.4.6) and 'ggpp' (>= 0.4.4) make it possible to highlight multiple peaks and valleys in the same plot. The parameter span describes the width of the window (number of consequtive observations) within which a peak or valley is searched for.

I changed the format of mpg values in the labels to include one decimal place.

ggplot(mtcars, aes(mpg, hp)) +
  geom_line() +
  geom_point() +
  stat_valleys(span = 5,
               strict = TRUE,
               geom = "text_s",
               mapping = aes(label = paste(after_stat(y.label), after_stat(x.label))),
               x.label.fmt = "at %.0f mpg ",
               y.label.fmt = "hp = %.0f",
               segment.colour = "blue",
               arrow = grid::arrow(length = unit(0.1, "inches")),
               position = position_nudge_keep(x = -1, y = -20),
               hjust = 1) +
  stat_peaks(span = 5,
             strict = TRUE,
             geom = "text_s",
             mapping = aes(label = paste(after_stat(y.label), after_stat(x.label))),
             x.label.fmt = "at %.0f mpg",
             y.label.fmt = "hp = %.0f\n",
             segment.colour = "red",
             arrow = grid::arrow(length = unit(0.1, "inches")),
             position = position_nudge_keep(x = 1, y = 20),
             hjust = 0) +
 expand_limits(y = 0)

enter image description here

We can flip the plot in the first example in this answer with the help of parameter orientation. Because the y has become the independent variable we need to edit the text of the label format strings.

ggplot(mtcars, aes(hp, mpg)) +
  geom_line(orientation = "y") +
  geom_point() +
  stat_peaks(span = NULL,
             strict = TRUE,
             geom = "text_s",
             mapping = aes(label = paste(after_stat(x.label), after_stat(y.label))),
             x.label.fmt = "Max hp = %.0f",
             y.label.fmt = "at %.0f mpg ",
             segment.colour = "red",
             arrow = grid::arrow(length = unit(0.1, "inches")),
             position = position_nudge_keep(x = 0, y = 1),
             hjust = 1,
             angle = -90,
             orientation = "y")

enter image description here

Pedro J. Aphalo
  • 5,796
  • 1
  • 22
  • 23
  • The code example in my original answer had stopped working with a recent update to package 'ggpmisc' (== 0.4.5). I updated the original example code and added new examples and a more detailed explanation. – Pedro J. Aphalo Apr 09 '22 at 22:01