Can the boxplot and the dotplot come from two different data in ggplot?

Viewed 277

I would like my box plot to contain individual data points, but then I would like to pull the points from a separate dataset.

For example, if my dataframe ("df") looks like this:

ID <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Happiness <- c(2, 3, 10, 7, 6, 8, 3, 9, 5, 1)
Smoke <- c("yes", "yes", "no", "yes", "no", "no", "no", "no", "yes", "no")
Exercise <- c("no", "yes", "no", "yes", "yes", "yes", "yes", "no", "no", "yes")

where ID = subject ID, Happiness = happiness as a continuous variable on a 1-10 scale, Smoke & Exercise = categorical variable of whether they smoke/exercise,

I'd like my box plot to be based on 'Smoke', but the dot plot to be based on 'Exercise'.

So, on the x-axis, I would have two groups, 'smoker', 'non-smoker' (based on "Smoke"), and on the Y-axis, I would have Happiness. However, the dots on the box plot would indicate whether the person is an 'exerciser' or a 'non-exerciser' (based on "Exercise"), depicted by shape or color.

I hope this makes sense.

This is my attempt, but the output doesn't quite look like what I had imagined.

ggplot(df, aes(x=Smoke, y=Happiness, fill = Smoke)) + 
 geom_boxplot(position = position_dodge()) +
 geom_dotplot(aes(shape=Exercise), binaxis='y', stackdir='center', dotsize=.5, position = position_dodge()) +
 scale_shape_manual(values=c(3, 16))+
 theme_classic() +
 labs(title = "Happiness by Smoking/Exercise", y = "Happiness")
2 Answers

Usually when I see dots overlaying a boxplot I assume they represent the same thing (i.e. the boxplot shows the distribution, the dots show each individual value). If you're interested in the interaction between Smoking and Exercise it might make more sense to plot that instead, e.g.

library(tidyverse)
ID <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Happiness <- c(2, 3, 10, 7, 6, 8, 3, 9, 5, 1)
Smoke <- c("yes", "yes", "no", "yes", "no", "no", "no", "no", "yes", "no")
Exercise <- c("no", "yes", "no", "yes", "yes", "yes", "yes", "no", "no", "yes")
df <- tibble("ID" = ID, "Happiness" = Happiness,
                 "Smoke" = Smoke, "Exercise" = Exercise)
df %>% 
  mutate(Smoke = ifelse(Smoke == "yes",
                        "Smoker",
                        "Non-Smoker"),
         Exercise = ifelse(Exercise == "yes",
                           "Exercises",
                           "Doesn't Exercise"),
         Interaction = factor(str_replace(interaction(Smoke, Exercise),
                                          '\\.', '\n'),
                              ordered=TRUE)) %>% 
  ggplot(aes(x= Interaction, y = Happiness)) + 
  geom_boxplot(aes(fill = Smoke)) +
  geom_point(aes(shape = Exercise), size = 4) +
  labs(title = "Happiness by Smoking/Exercise",
       y = "Happiness") +
  theme_classic(base_size = 16) +
  theme(axis.title.x = element_blank())

example_1.png

EDIT

In answer to the comment below, this is one way of making a raincloud plot using similar data (need more data points than the MRE above, otherwise the plot looks weird):

# Load libraries
library(tidyverse)

# Get data
ID <- seq(1:50)
Happiness <- sample(1:100, 50, replace = TRUE)
Smoke <- sample(c("yes", "no"), 50, replace = TRUE)
Exercise <- sample(c("yes", "no"), 50, replace = TRUE)
df <- tibble("ID" = ID, "Happiness" = Happiness,
             "Smoke" = Smoke, "Exercise" = Exercise)

# Source Ben Marwick's code for Violin Plots
source("https://gist.githubusercontent.com/benmarwick/2a1bb0133ff568cbe28d/raw/fb53bd97121f7f9ce947837ef1a4c65a73bffb3f/geom_flat_violin.R")

# Raincloud plot theme
raincloud_theme = theme(
  text = element_text(size = 14),
  axis.title.x = element_text(size = 14),
  axis.title.y = element_blank(),
  axis.text = element_text(size = 14),
  axis.text.y = element_text(vjust = 0.3),
  legend.title=element_text(size=14),
  legend.text=element_text(size=14),
  legend.position = "right",
  plot.title = element_text(lineheight=.8,
                            face="bold", size = 16),
  panel.border = element_blank(),
  panel.grid.minor = element_blank(),
  panel.grid.major = element_blank(),
  axis.line.x = element_line(colour = 'black',
                             size=0.5, linetype='solid'),
  axis.line.y = element_line(colour = 'black',
                             size=0.5, linetype='solid'))

# Plot the thing
df %>% 
  mutate(Smoke = ifelse(Smoke == "yes",
                        "Smoker",
                        "Non-Smoker"),
         Exercise = ifelse(Exercise == "yes",
                           "Exercises",
                           "Doesn't Exercise"),
         Interaction = factor(str_replace(interaction(Smoke, Exercise),
                                          '\\.', '\n'),
                              ordered=TRUE)) %>% 
  ggplot(aes(x = Interaction, y = Happiness, fill = Smoke)) + 
  geom_flat_violin(position = position_nudge(x = .2, y = 0),
                   alpha = .8) +
  geom_point(aes(shape = Exercise),
             position = position_jitter(width = .05),
             size = 2, alpha = 0.8) +
  geom_boxplot(width = .1, outlier.shape = NA, alpha = 0.5) +
  coord_flip(xlim=c(1.25,4.25)) +
  labs(title = "Happiness by Smoking/Exercise",
       y = "Happiness") +
  scale_fill_discrete(guide = guide_legend(override.aes = list(shape = c(".", ".")))) +
  scale_shape_discrete(guide = guide_legend(override.aes = list(size = 3))) +
  theme_classic(base_size = 16) +
  theme(axis.title.x = element_blank()) +
  raincloud_theme

example_2.png

You can use geom_point() instead of geom_dotplot().

df <- data.frame("ID" = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
                 "Happiness" = c(2, 3, 10, 7, 6, 8, 3, 9, 5, 1),
                 "Smoke" = c("yes", "yes", "no", "yes", "no", "no", "no", "no", "yes", "no"), 
                 "Exercise" = c("no", "yes", "no", "yes", "yes", "yes", "yes", "no", "no", "yes"))


ggplot(df, aes(x=Smoke, y=Happiness, fill = Smoke)) + 
  geom_boxplot(position = position_dodge()) +
  theme_classic() +
  labs(title = "Happiness by Smoking/Exercise", y = "Happiness") +
  geom_point(aes(shape = Exercise, colour = Exercise), position = position_dodge(width = 0.5)) +
  scale_shape_manual(values=c(17, 16)) +
  scale_color_manual(values = c("black", "blue"))

You can adjust whether the Exercise points are lined up or separated by changing the number in position_dodge(width = ); 0 will make them line up. This plot is confusing, because it contains two different information (Exercise) in one box plot. I agree with the other post that it's better to have a separate boxplot for each combination.

enter image description here

Related