rfacet-wrapgeom-point

How to connect geom_point and geom_line when using jittered position for points using facet_wrap in a graph with 2 subgroups?


I had a similar problem as in this post , and the solution almost worked for my data, but I'm having problems to connect the points to the lines when I add jitter. I addeded jitter to the geom_point (position = "jitter") component as my individual points were overlapping in the bars, but now my individual lines are not connecting to the points. Does anyone have any suggestion on how to approach the problem? I already tried to create jitter in the dataframe values, but it didn't work either.

I tried the code below expecting to have the individual points connected to the lines, but it didn't work.

I also tried adding an interaction in x, but it also didn't work.

library(ggplot2)

df <- data.frame(
  Group = c("A", "A", "A", "A", "B", "B"),
  Subgroup = c("A.1", "A.2", "A.1", "A.2", "B.1", "B.2"),
  Value = c(10, 7, 8, 9, 11, 12),
  Pair = c(1, 1, 2, 2, 3, 3)
)

ggplot(df, aes(x = Subgroup, y = Value, fill = Subgroup)) +
  geom_bar(stat = "summary", fun = "mean", width = 1) +
  geom_point(position="jitter") +
  geom_line(aes(group = Pair), color = "red", ) +
  facet_wrap(vars(Group), scales = "free_x", strip.position = "bottom") +
  labs(x = "Group") +
  theme(
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    strip.background = element_blank(),
    panel.spacing = unit(0, units = "line")
  )

Edit = worked when implementing position = position_jitter(seed = 1) in both geom_line and geom_point. But unfortunately for my data it still don't work.

My dataframe:

tdcs_cer <- as.data.frame(structure(list(Subject = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 
5, 5, 5, 5, 5, 5, 13, 13, 13, 13, 13, 13, 13, 13, 13, 17, 17, 
17, 17, 17, 17, 17, 17, 17, 21, 21, 21, 21, 21, 21, 21, 21, 21, 
25, 25, 25, 25, 25, 25, 25, 25, 25, 29, 29, 29, 29, 29, 29, 29, 
29, 29, 33, 33, 33, 33, 33, 33, 33, 33, 33, 37, 37, 37, 37, 37, 
37, 37, 37, 37, 41, 41, 41, 41, 41, 41, 41, 41, 41, 45, 45, 45, 
45, 45, 45, 45, 45, 45, 49, 49, 49, 49, 49, 49, 49, 49, 49, 53, 
53, 53, 53, 53, 53, 53, 53, 53, 57, 57, 57, 57, 57, 57, 57, 57, 
57, 61, 61, 61, 61, 61, 61, 61, 61, 61, 65, 65, 65, 65, 65, 65, 
65, 65, 65, 77, 77, 77, 77, 77, 77, 77, 77, 77, 81, 81, 81, 81, 
81, 81, 81, 81, 81, 89, 89, 89, 89, 89, 89, 89, 89, 89), Group = c("cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum", 
"cerebellum", "cerebellum", "cerebellum", "cerebellum", "cerebellum"
), Music = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 
0, 0, 0, 0, 0, 0, 0, 0), StimType = structure(c(2L, 2L, 2L, 3L, 
3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 
2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 
2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 
1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 
3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 
3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 
2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 
1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 
1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 
3L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 1L, 1L, 1L, 2L, 2L, 
2L, 3L, 3L, 3L, 1L, 1L, 1L), levels = c("Sham", "Anodal", "Cathodal"
), class = "factor"), condition = structure(c(2L, 1L, 3L, 2L, 
1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 
3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 
2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 
1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 
3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 
2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 
1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 
3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 
2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 
1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 3L, 2L, 1L, 
3L, 2L, 1L, 3L, 2L, 1L, 3L), levels = c("Strong Beat", "Weak Beat", 
"Non-Beat"), class = "factor"), TrialPassnum = c(1, 1, 1, 1, 
1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 
0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 
1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, NA, 1, NA, NA, NA, 
NA), n = c(3, 8, 3, 1, 10, 20, 1, 3, 2, 3, 10, 2, 2, 4, 1, 2, 
9, 1, 1, 10, 3, 3, 12, 2, 4, 15, 4, 9, 18, 6, 4, 11, 4, 6, 14, 
3, 2, 7, 1, 1, 3, 2, 20, 7, 20, 6, 12, 1, 5, 11, 4, 7, 13, 1, 
6, 16, 1, 5, 18, 3, 3, 14, 4, 1, 1, 20, 20, 20, 20, 2, 20, 20, 
2, 7, 3, 2, 11, 2, 5, 12, 2, 20, 6, 1, 20, 4, 20, 20, 4, 1, 20, 
2, 1, 20, 3, 1, 20, 20, 20, 1, 5, 2, 1, 9, 1, 1, 4, 1, 9, 19, 
6, 2, 18, 1, 3, 14, 6, 1, 20, 20, 1, 5, 20, 2, 10, 20, 2, 13, 
1, 2, 11, 2, 4, 15, 1, 7, 16, 5, 1, 8, 2, 6, 15, 7, 4, 14, 4, 
4, 15, 2, 3, 12, 2, 1, 7, 1, 4, 14, 1, 1, 9, 2, 6, 4, 1, 20, 
2, 20, 20, 20, 20), PropCorr = c(15, 40, 15, 5, 50, 0, 5, 15, 
10, 15, 50, 10, 10, 20, 5, 10, 45, 5, 5, 50, 15, 15, 60, 10, 
20, 75, 20, 45, 90, 30, 20, 55, 20, 30, 70, 15, 10, 35, 5, 5, 
15, 10, 0, 35, 0, 30, 60, 5, 25, 55, 20, 35, 65, 5, 30, 80, 5, 
25, 90, 15, 15, 70, 20, 5, 5, 0, 0, 0, 0, 10, 0, 0, 10, 35, 15, 
10, 55, 10, 25, 60, 10, 0, 30, 5, 0, 20, 0, 0, 20, 5, 0, 10, 
5, 0, 15, 5, 0, 0, 0, 5, 25, 10, 5, 45, 5, 5, 20, 5, 45, 95, 
30, 10, 90, 5, 15, 70, 30, 5, 0, 0, 5, 25, 0, 10, 50, 0, 10, 
65, 5, 10, 55, 10, 20, 75, 5, 35, 80, 25, 5, 40, 10, 30, 75, 
35, 20, 70, 20, 20, 75, 10, 15, 60, 10, 5, 35, 5, 20, 70, 5, 
5, 45, 10, 30, 20, 5, 0, 10, 0, 0, 0, 0)), row.names = c(NA, 
-171L), class = c("tbl_df", "tbl", "data.frame")))

My code for my figure:

My_Theme = theme(
  title = element_text(size = 15),
  axis.title.x = element_blank(),
  axis.text.x = element_blank(),
  axis.title.y = element_text(size = 16),
  axis.text.y = element_text(size = 15),
  strip.text.y = element_blank(),
  strip.text = element_text(size=15),
  strip.background = element_blank(),
  strip.placement = "outside",
  axis.ticks.x = element_blank(),
  axis.line.x = element_line(linetype="solid"),
  legend.text = element_text(size = 13))
    

cer_plot <- ggplot(tdcs_cer, aes(x=StimType, y = PropCorr, fill = StimType)) +
  geom_bar(stat = "summary", fun = "mean", width = 0.6, size=0.5, alpha=0.9) +
  stat_summary(fun.data = mean_se, geom = "errorbar", width=0.3, size = 0.5, alpha = 0.4) +
  geom_point(aes(x=StimType,y=PropCorr, color=StimType), size=2.5,position = position_jitter(seed = 1)) +
  scale_color_manual(values=c("lightgrey", "pink", 'cyan'))+
  geom_line(aes(group = Subject), color = "antiquewhite3", size=0.6, alpha=0.5, position = position_jitter(seed = 1)) +
  facet_wrap(vars(condition), scales = "free_x", strip.position = "bottom") +
  scale_fill_manual(values = c("Sham" = "darkgrey",
                               "Anodal" = "brown1",
                               "Cathodal" = "blue"))+
  ggtitle("Right Cerebellum") +
  labs(x = "", y = "Percent Correct")+
  theme_classic()+
  My_Theme

Solution

  • You have to use position_jitter in geom_line too and set the same random seed via the seed= argument:

    library(ggplot2)
    
    ggplot(df, aes(x = Subgroup, y = Value, fill = Subgroup)) +
      geom_bar(stat = "summary", fun = "mean", width = 1) +
      geom_point(position = position_jitter(seed = 1)) +
      geom_line(aes(group = Pair),
        color = "red",
        position = position_jitter(seed = 1)
      ) +
      facet_wrap(vars(Group),
        scales = "free_x",
        strip.position = "bottom"
      ) +
      labs(x = "Group") +
      theme(
        axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        strip.background = element_blank(),
        panel.spacing = unit(0, units = "line")
      )
    

    EDIT The issue with your real data and code is that the data for the geom_point and the geom_line are in a different order (Unfortunately I have no clue why). As a result the jittered points /lines will not align. The only option I have figured out to fix that is to manually jitter StimType and PropCorr outside of ggplot and to use the jittered values for both layers.

    library(ggplot2)
    
    tdcs_cer$StimType1 <- jitter(
      as.numeric(factor(tdcs_cer$StimType)),
      amount = .45
    )
    
    tdcs_cer$PropCorr1 <- jitter(
      tdcs_cer$PropCorr,
      amount = .45
    )
    
    ggplot(tdcs_cer, aes(x = StimType, y = PropCorr)) +
      geom_bar(
        aes(fill = StimType),
        stat = "summary", fun = "mean",
        width = 0.6, size = 0.5, alpha = 0.9
      ) +
      stat_summary(
        fun.data = mean_se, geom = "errorbar",
        width = 0.3, size = 0.5, alpha = 0.4
      ) +
      geom_point(
        aes(
          x = StimType1,
          y = PropCorr1,
          color = StimType
        ),
        size = 2.5
      ) +
      geom_line(
        aes(
          x = StimType1,
          y = PropCorr1,
          group = Subject
        ),
        color = "antiquewhite3",
        size = 0.6, alpha = 0.5
      ) +
      facet_wrap(vars(condition),
        scales = "free_x",
        strip.position = "bottom"
      ) +
      scale_fill_manual(values = c(
        "Sham" = "darkgrey",
        "Anodal" = "brown1",
        "Cathodal" = "blue"
      )) +
      scale_color_manual(
        values = c("lightgrey", "pink", "cyan")
      ) +
      ggtitle("Right Cerebellum") +
      labs(x = "", y = "Percent Correct") +
      theme_classic() +
      My_Theme
    

    enter image description here