Dunois
Dunois

Reputation: 1843

ggplot2: How to get geom_text() to play nice with facet_grid()?

So I'm trying to plot a couple of curves using ggplot(), and I would like to have each curve sitting in its own plot in a facet_grid. All of this works fine.

The problem is that I'd also like to annotate the curve with the x value corresponding to the peak y value. I tried using geom_text(), and I tried implementing it as shown below, but it doesn't seem to quite work. It's clearly printing something onto the plot, but not the way I hoped it would; i.e., each plot has its corresponding x value printed on it at the location (x, max(y)).

I suspect I've not implemented the ifelse() correctly, but I'm not experienced enough with R to figure out what exactly the problem is.

Any suggestions on where I'm going wrong?

Output: Faceted ggplot output

Data + code:

library('ggplot2')

x <- seq(5, 15, length=1000)
y <- dnorm(x, mean=10, sd=1)
z <- rep_len("z", length.out = 1000)
x1 <- seq(5, 15, length=1000)
y1 <- dnorm(x1, mean=10, sd=2)
z1 <- rep_len("z1", length.out = 1000)
x <- c(x, x1)
y <- c(y, y1)
z <- c(z, z1)
df <- data.frame(x, y, z)

ggplot(data = df, aes(x, y)) + geom_line() + facet_grid(.~z) + geom_text(data = df, aes(x, y, label = ifelse(y == max(y), as.numeric(x), '')), inherit.aes = FALSE, hjust = 0, vjust = 0)

Edit: the output I'm expecting is something like this: Expected output

Upvotes: 3

Views: 748

Answers (3)

rg255
rg255

Reputation: 4169

Get the means and maxes for each z:

Ys <- df %>% group_by(z) %>% summarise(maxY = max(y))
Xs <- df %>% group_by(z) %>% summarise(meanX = mean(x))

Plot with the geom_text

ggplot(data = df, aes(x, y)) +
  geom_line() + 
  geom_text(data = left_join(Xs,Ys), aes(meanX, maxY, label = meanX)) +
  facet_grid(.~z)

Or more succinctly

ggplot(data = df, aes(x, y)) +
  geom_line() + 
  geom_text(data = 
    df %>% 
      group_by(z) %>% 
      summarise(maxY = max(y), meanX = mean(x)),
    aes(meanX, maxY, label = meanX)) +
  facet_grid(.~z)

Upvotes: 1

TimTeaFan
TimTeaFan

Reputation: 18541

You need to fix two things. (1) calculate max per z (2) avoid duplicate y_values

The following code should fix both:

library(dplyr)
   df2 <- df %>% 
   distinct(y, .keep_all = TRUE) %>%
   group_by(z) %>%
   mutate(y_label = ifelse(y == max(y), as.numeric(x), '')) 

as.data.frame(df2)

ggplot(data = df2, aes(x, y)) + geom_line() + facet_grid(.~z) + geom_text(aes(label = y_label), hjust = 0, vjust = 0)

Upvotes: 1

Roman Luštrik
Roman Luštrik

Reputation: 70603

You need to provide geom_text a data.frame with data for z and z1.

          x         y  z
z  9.994995 0.3989373  z
z1 9.994995 0.1994705 z1

How to get that? Well, here's one way.

df.split <- split(df, f = df$z)
df.max <- sapply(df.split, FUN = function(x) which.max(x$y))
df.max <- mapply(function(x1, x2) x1[x2, ], x1 = df.split, x2 = df.max, SIMPLIFY = FALSE)
df.max <- do.call(rbind, df.max)

which you can then plot

ggplot(data = df, aes(x, y)) + 
  geom_line() +
  geom_text(data = df.max, aes(x = x, y = y, label = round(y, 2))) +
  facet_grid(. ~ z)

enter image description here

Upvotes: 1

Related Questions