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:
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:
Any other solutions?
Thanks in advance!
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()
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)