Post #34. Connecting paired points in ggplots

2024

Check out the handy tips for connecting paired points in ggplots!

Gen-Chang Hsu
2024-06-11

Introduction

Feel so glad (and relieved) to blog again after three months of rest! This post was actually inspired by a recent project I am working on: I wanted to make a ggplot comparing the feeding response of control vs. treatment for each larval parent family on two plant types, and I would like to draw a line connecting the control-treatment pair for each family for each plant type. I thought it was a walk in the park, but when I started making the plot, it turned out to be more complicated than I imagined. Thankfully, after a bit of researching and experimenting, I figured it out and I felt it would be helpful to write something about it. So in this post, I’ll show you the tips for connecting paired points in ggplots. Let’s do it!

Case 1. A single group

We will use the built-in CO2 dataset, which contains data from an experiment looking at how chilling affected the CO2 uptake rates of the grass species Echinochloa crus-galli.

Let’s start with the simplest case where we compare the CO2 uptake rates of the nonchilled vs. chilled plant individual #1 at different ambient CO2 concentrations. We’ll draw a line between each nonchilled-chilled pair to show the treatment effect.

library(tidyverse)

### Select plant individual #1
CO2_plant_1 <- filter(CO2, Plant %in% c("Qn1", "Qc1")) 
  
### Plot  
ggplot(CO2_plant_1) + 
  geom_point(aes(x = Treatment, y = uptake, shape = Treatment, color = factor(conc))) + 
  geom_line(aes(x = Treatment, y = uptake, color = factor(conc), group = factor(conc))) + 
  scale_color_brewer(palette = "Set1", name = "Ambient [CO2]") + 
  theme_classic(base_size = 13)

Looks like chilling reduces CO2 uptake rates of plant individual #1 under all ambient CO2 concentrations.

Case 2. Two or more groups

Now, what if we’re interested in other plant individuals? Would the patterns be similar? Let’s make another plot and see.

### Select plant individual #1 to #3
CO2_plant_1_to_3 <- filter(CO2, Plant %in% c("Qn1", "Qc1", "Qn2", "Qc2", "Qn3", "Qc3")) %>% 
  mutate(Plant_id = str_remove(Plant, "(n|c)")) %>% 
  mutate(Plant_conc_id = str_c(Plant_id, conc, sep = "_"))  # create a unique id for the paired points
  
### Plot  
ggplot(CO2_plant_1_to_3) + 
  geom_point(aes(x = Plant_id, y = uptake, group = Treatment, shape = Treatment, color = factor(conc)),
             position = position_dodge(width = 0.8)) + 
  geom_line(aes(x = Plant_id, y = uptake, group = Plant_conc_id, color = factor(conc)),
            position = position_dodge(width = 0.8)) + 
  scale_color_brewer(palette = "Set1", name = "Ambient [CO2]") + 
  theme_classic(base_size = 13)

Oops it doesn’t work. How can we solve this problem? Read below!

Method 1. Use facets

The first method is to create separate panels for each plant individual using facets. This is a easy quick fix.

### Plot  
ggplot(CO2_plant_1_to_3) + 
  geom_point(aes(x = Treatment, y = uptake, color = factor(conc))) + 
  geom_line(aes(x = Treatment, y = uptake, group = factor(conc), color = factor(conc))) + 
  facet_wrap( ~ Plant_id) +  # faceting by plant id
  scale_color_brewer(palette = "Set1", name = "Ambient [CO2]") + 
  theme_classic(base_size = 13)

As we can see, all three plant individuals showed similar responses to chilling.

Method 2. Manually dodge the endpoints of the connecting lines

The second method is to manually dodge the endpoints of the connecting lines. This requires slightly more work, but is a non-facet alternative. So basically, we need to dodge the nonchilled and chilled points to the two sides of each plant individual (the x-positions of the three plants are indeed 1, 2, and 3), map the new dodged positions to the x aesthetic in geomline(), and specify each plant individual–CO2 concentration combination as the grouping variable. Sounds a bit abstract?! Let’s take a look at the code below to get a better idea of how it works.

### Dodge the nonchilled and chilled points
CO2_plant_1_to_3_recoded <- CO2_plant_1_to_3 %>% 
  mutate(Plant_id_x = case_when(Plant_id == "Q1" ~ 1,  # these are the x-positions of the three plant individuals
                                Plant_id == "Q2" ~ 2,
                                Plant_id == "Q3" ~ 3),
         Plant_id_x_dodged = case_when(Treatment == "nonchilled" ~ Plant_id_x - 0.25,  # dodge the "nonchilled" points to the left of each plant individual
                                       Treatment == "chilled" ~ Plant_id_x + 0.25))   # dodge the "chilled" points to the right of each plant individual

### Plot
ggplot(CO2_plant_1_to_3_recoded) + 
  geom_point(aes(x = Plant_id, y = uptake, shape = Treatment, group = Treatment, color = factor(conc)),
             position = position_dodge(width = 1)) + 
  geom_line(aes(x = Plant_id_x_dodged, y = uptake, group = Plant_conc_id, color = factor(conc))) + 
  scale_color_brewer(palette = "Set1", name = "Ambient [CO2]") + 
  theme_classic(base_size = 13)

We made it!

Summary

To recap, we looked at how to connect paired points with lines for a single group as well as for multiple groups. When there are multiple groups, we can use facets or manually dodge the endpoints of the lines. This type of paired-points plot is especially useful for visualizing the treatment effects, and I’m pretty sure you’ll be creating it in the future.

Hope you learn something useful from this post and don’t forget to leave your comments and suggestions below if you have any!

Corrections

If you see mistakes or want to suggest changes, please create an issue on the source repository.