Problem: My dataset has a shared baseline (timepoint 1) with 2 repeated measures. The followup points are repeated with 2 different conditions (ie a cross-over). When plotting, the results, the error bars and data points overlap.
library(tidyverse)
set.seed(11)
df_test <-
tibble(group = factor(rep(c("A", "B"), each = 3)),
timepoint = factor(rep(1:3, 2)),
y = c(0, rnorm(2, mean = 0, sd = .2), 0, rnorm(2, mean = .7, sd = 0.35))) %>%
mutate(ymax = y + .2,
ymin = y - .2)
# plot_nododge <-
df_test %>%
ggplot(aes(timepoint, y,
group = group,
shape = group,
fill = group)) +
geom_linerange(linetype = 1,
aes(ymin = ymin,
ymax = ymax)) +
geom_point() +
geom_line()

Solution with position_dodge(): This solution fixes the overlap, but the "Baseline" point is actually a single measure. I would like for this to be a single point to avoid confusion, but still dodge the followup points.
Is there a simple solution to this that I'm missing?
I would like to use a custom dodge for each point (eg scale_position_dodge_identity), but position is not accepted as an aesthetic.
# plot1 <-
df_test %>%
ggplot(aes(timepoint, y,
group = group,
shape = group,
fill = group)) +
geom_linerange(linetype = 1,
position = position_dodge(.2),
aes(ymin = ymin,
ymax = ymax)) +
geom_point(position = position_dodge(.2)) +
geom_line(position = position_dodge(.2))

Solution and expansion* Maybe something better will be developed in the future but manually adjusting the position seems best for now. Since the point shape and color should also reflect the different "Baseline" measure and the x-axis should really have nice labels, I've edited to show some more finishing touches.
df_test %>%
mutate(shape_var = case_when(timepoint == 1 ~ 22,
group == "A" ~ 21,
group == "B" ~ 24),
fill_var = case_when(timepoint == 1 ~ "black",
group == "A" ~ "red",
group == "B" ~ "blue"),
timepoint2 = as.numeric(as.character(timepoint)),
timepoint2 = timepoint2 + 0.05*(timepoint2 > 1 & group == "B"),
timepoint2 = timepoint2 - 0.05*(timepoint2 > 1 & group == "A")) %>%
ggplot(aes(timepoint2, y,
group = group,
shape = shape_var,
fill = fill_var)) +
geom_linerange(aes(ymin = ymin,
ymax = ymax)) +
geom_line() +
geom_point(size = 2) +
scale_x_continuous(breaks = 1:3, # limit to selected time points
labels = c("Baseline", "time 1", "time 2"), # label like a discrete scale
limits = c(.75, 3.25)) + # give it some room
scale_shape_identity(guide = "legend",
name = "Treatment",
breaks = c(21, 24), # Defines legend order too; only label the treatment groups
labels = c("A", "B" )) + # this part is tricky - could easily reverse them
scale_fill_identity(guide = "legend",
name = "Treatment",
breaks = c("red", "blue"),# Defines legend order too; only label the treatment groups
labels = c("A", "B" )) + # this part is tricky - could easily reverse them
labs(x=NULL)

Created on 2021-09-19 by the reprex package (v2.0.1)


