multi-plot tutorial

Author

jordan wylie

This is a very short tutorial that will show you how to create a plot that has individual data points, densities, and summary data all in the same plot. It uses ggplot2(), and is inspired by a plot I saw on Twitter (linked in the notes at the end).

I thought this was a really effective way to communicate a lot of information about your data in a single plot! Here is the final figure that we’ll create:

                                 Now we will dive into how to do it!

Libraries

library(ggplot2)
library(tidyverse)
library(gridExtra)

Also creating my color pallete here:

my_pal=c("#005f73", "#ee9b00")

Data Creation

First, we will create some simulated data for the figure. This simulated data has two trials per participant. You can create something similar for between subjects conditions (but you would likely want to omit the lines connected the points in that case and opt to add violin plots)

# Set the seed for reproducibility
set.seed(42)

# Create a function to simulate data
generate_fake_data <- function(n_subjects, mean, sd, category) {
  data.frame(
    subj = 1:n_subjects,
    char_value = rnorm(n_subjects, mean, sd),
    char_var = category
  )
}

n_subjects <- 30
fast_data <- generate_fake_data(n_subjects, mean = 5, sd = 1, category = "fast")
slow_data <- generate_fake_data(n_subjects, mean = 3, sd = 1, category = "slow")

# Combine dfs
df <- bind_rows(fast_data, slow_data)

# Make sure data are within 1 and 7 boundaries
df$char_value <- pmin(pmax(df$char_value, 1), 7)

# Reorder the factors so the plot is more intuitive
df$char_var <- factor(df$char_var, levels = c("slow", "fast"))

Main Figure

The way I’ve created this is by creating the individual pieces and then putting them altogether via gridExtra.

First, we will create the main focus of the figure.

Plot

#for reproducibility
set.seed(123)

# Create the main plot with points and lines connecting participants
main_plot <- ggplot(df, aes(x = char_var, y = char_value, group = subj, color = char_var)) +
  geom_line(aes(y = char_value), alpha = 0.5, size = 0.4, 
            position =position_dodge(0.1),color = "lightgray") + 
  geom_jitter(alpha = 0.8, stroke = 0, position = position_dodge(0.1)) + 
  geom_hline(yintercept = 4, lty = "dashed", color = "gray55")+
  theme_bw()+ theme(legend.position = "none")+
  scale_color_manual(values = my_pal)+
  labs(y = "Target Evaluation", x = "Condition")

Density Plot

Now we will add in the density plot:

Plot

# Create density plot
density_plot <- ggplot(df, aes(x = char_value, fill = char_var)) +
  geom_density(alpha = 0.5) +
 # coord_flip() +
  theme_minimal()+   theme(legend.position = "none",
        panel.grid = element_blank(),
        axis.title = element_blank(),
        axis.text = element_blank(),
        axis.ticks = element_blank(),
        panel.background = element_blank()) +
  scale_fill_manual(values = my_pal)

Summary Plot (boxplot)

Now we will add in the box plot. The boxplot has the mean value indicated with a red triangle. If a boxplot wasn’t the desired visual, you could change this to a bar graph or crossbars.

# Create box plot
box_plot <- ggplot(df, aes(x = "", y = char_value, fill = char_var)) +
  geom_boxplot(aes(x = char_var)) +
  #coord_flip() +
  stat_summary( aes(x = char_var), fun = mean,     
    geom = "point",
    col = "black",
    size = 3,
    shape = 24,
    fill = "red") +
  theme_minimal()+   theme(legend.position = "none",
        panel.grid = element_blank(),
        axis.title = element_blank(),
        axis.text = element_blank(),
        axis.ticks = element_blank(),
        panel.background = element_blank())+
  scale_fill_manual(values = my_pal)

Placeholder Plot

I also add in this blank plot to keep the spacing how I want it to look:

Plot

blankPlot <- ggplot()+geom_blank(aes(1,1))+
  theme(plot.background = element_blank(), 
   panel.grid.major = element_blank(),
   panel.grid.minor = element_blank(), 
   panel.border = element_blank(),
   panel.background = element_blank(),
   axis.title.x = element_blank(),
   axis.title.y = element_blank(),
   axis.text.x = element_blank(), 
   axis.text.y = element_blank(),
   axis.ticks = element_blank()
     )

Complete Figure

And finally, put them all together:

grid.arrange(density_plot, blankPlot, main_plot, box_plot, 
        ncol=2, nrow=2, widths=c(4, 1.4), heights=c(1.4, 4))

The End

And there it is! Now you have a complete figure with a ton of information about your data available for readers! As I noted in the boxplot section, there are reasons to avoid boxplots, but this set up should be plug and play-able so that whatever summary plot you’d like to include can replace the boxplot.

Some quick notes:

  • Your y-axis of the main plot sets the y-axis for the summary plot. If for some reason you wanted to re-add axis information for that summary plot (or maybe adjust one axis and not the other, though I would suggest avoiding that), you can edit the theme section of the summary plot code. You could also do this for the density plot if you wanted to make axis information available.

  • Here is the link to the twitter post that inspired this plot. It’s by Gregory Samanez-Larkin, and he was using a similar style of plot to show student progress in one of his classes over the course of a semester.

  • There is another resource to produce similar plots via daestr if you are interested in a different approach (thanks to Fred Duong for making me aware of this!).

  • If you have feedback, suggestions, things you would like to see added or changed, or questions or just want to connect, you can find my contact info here: jordancwylie.com.

Thank you for reading! – Jordan Wylie