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())

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
