Search code examples
rggplot2mappinggroupaesthetics

Incorrect mapping of group aesthetic in geom_line() to a variable


I am trying to plot the results of a study with within-subjects experimental manipulations in ggplot2. I would like geom_line() to connect the points of the same subject between different trial types but not between conditions (see figure below). When I try to map subject to group in my data, it produces lines connecting the same subject across the whole plot, which is not what I want.

This is how it looks:

What I get

Now what I would like to obtain - lines are orange for better visibility here. It looks crap because I generated only a few data points, but this is the idea:

lines connecting individuals only within condition

A small, workable example (apologies in advance for my inelegant code):

# produce fake data
signal <- c(0.74660393, 1.69004752, 0.38833258, 1.00708169, 0.72820926, 0.63489489,
            1.46378383, 0.67635374, 0.15536748, 0.19220099, 0.32673839, 1.64773836,
            0.99743467, 0.47589479, 0.83547231, 1.98466375, 0.13243662, 1.26484889,
            1.67973564, 1.52482770, 1.87735472, 1.00273947, 1.71527739, 0.23374901) 
subject <- rep(c("Sub1", "Sub2", "Sub3", "Sub4"), 6)
condition <- c(rep("Congruent", 8), rep("Incongruent", 8), rep("Neutral", 8))
trial <- c("alt", "rep", "alt", "rep", "alt", "rep",
           "alt", "rep", "alt", "rep", "alt", "rep",
           "alt", "rep", "alt", "rep", "alt", "rep",
           "alt", "rep", "alt", "rep", "alt", "rep")
hemisphere <- c(rep("right", 4), rep("left", 4),
                rep("right", 4), rep("left", 4),
                rep("right", 4), rep("left", 4))

df <- data.frame(subject, signal, condition, trial, hemisphere)

Now the plot.

library(ggplot2)

p <- ggplot(data = df, aes(y = signal, x = condition, fill = trial)) 
p + geom_point(aes(fill = trial, color = trial), 
               position = position_dodge(width = 0.6), 
                alpha = 0.3) +
    geom_line(aes(group = subject), alpha = 0.1) +
  geom_boxplot(position = position_dodge(width = 0.6), alpha = 0.05) +
  facet_wrap(~hemisphere) +
  stat_summary(fun = "mean",
              geom = "crossbar", 
              aes(colour = trial),
               position = position_dodge(width = 0.6),
               width = 0.6) +
 theme_classic() 

I already tried:

  • putting the group aesthetic in ggplot() instead that in geom_line(), it screws up the whole thing
  • producing a paired variable for geom_line() as paste(df$subject, df$condition, df$hemisphere), but it doesn't produce what I want - there are no lines anymore

Any other solutions?

Thanks in advance!


Solution

  • IMHO this can't be achieved by the group aes, because on the one hand we have to group by subject to get a line per subject and on the other hand we have to group by condition and trial to dodge the start and end positions. And you can't both at the same time. Instead I would suggest to manually dodge the lines, i.e. compute the start and end positions manually. To this end convert condition and trial to numerics, rescale trial_num to the interval c(-1, 1) and finally account for (half of) the width of the dodge and the number of categories in trial, i.e. divide by 4.

    Note: Your example data contained only one obs. per subject, condition and hemisphere so I adjusted your data slightly so that we have two obs.

    library(dplyr, warn = FALSE)
    library(ggplot2)
    
    width <- .75
    pd <- position_dodge(width = width)
    
    df <- df %>%
      mutate(
        condition_num = as.numeric(factor(condition)),
        trial_num = as.numeric(factor(trial)),
        trial_num = scales::rescale(trial_num, to = c(-1, 1)),
        x_line = condition_num + width / 4 * trial_num
      )
    
    p <- ggplot(data = df, aes(y = signal, x = condition, fill = trial))
    p + geom_point(aes(fill = trial, color = trial),
      position = pd,
      alpha = 0.3
    ) +
      geom_line(aes(x = x_line, group = interaction(subject, condition)),
        color = "orange"
      ) +
      geom_boxplot(position = pd, alpha = 0.05, width = .6) +
      facet_wrap(~hemisphere) +
      stat_summary(
        fun = "mean",
        geom = "crossbar",
        aes(colour = trial),
        position = pd,
        width = 0.6
      ) +
      theme_classic()
    

    enter image description here

    DATA

    signal <- c(
      0.74660393, 1.69004752, 0.38833258, 1.00708169, 0.72820926, 0.63489489,
      1.46378383, 0.67635374, 0.15536748, 0.19220099, 0.32673839, 1.64773836,
      0.99743467, 0.47589479, 0.83547231, 1.98466375, 0.13243662, 1.26484889,
      1.67973564, 1.52482770, 1.87735472, 1.00273947, 1.71527739, 0.23374901
    )
    subject <- rep(c("Sub1", "Sub2", "Sub3", "Sub4"), 12)
    condition <- rep(c("Congruent", "Incongruent", "Neutral"), each = 8)
    trial <- rep(c("alt", "rep"), each = 4)
    hemisphere <- rep(c("right", "left"), 24)