
36 Grouped scatter plots
theme_consolewars()
Graphs in this chapter use fmggp2::theme_consolewars(), built from the Sega vs. Nintendo console wars: a charcoal background, a Sega blue frame, a Nintendo red title, Sega blue, Nintendo red, Super Nintendo purple, gray, and gold marks, and a Krungthep title.
36.1 Description
Grouped scatter-plots display the relationships between two continuous variables across a third categorical variable.
The x and y position displays the relationship between the two continuous variables, and color is used to distinguish between the categorical levels.
36.2 Set up
PACKAGES:
Install packages.
show/hide
install.packages("faux")
library(faux)
library(ggplot2)
library(dplyr) # for data manipulationDATA:
In the early 90s, every kid picked a side: Sega or Nintendo. We’ll simulate 120 Sega Genesis kids and 120 Super Nintendo kids, and record how many games each one owned (games_owned) and how many hours a week they played (hours_per_week). The two numbers go together, so we’ll draw them in pairs with faux::rnorm_multi(), which takes a mean (mu) and standard deviation (sd) for each variable and a correlation (r) between them. Genesis kids own a few more games, Super Nintendo kids play a few more hours, and the link between games and hours is stronger for the Genesis kids (a correlation of 0.6) than the Super Nintendo kids (a correlation of 0.3). Setting the seed with set.seed() means you’ll get the same 240 kids every time you run the code below.
show/hide
set.seed(1991) # same 240 kids every time the code runs
genesis <- faux::rnorm_multi(n = 120, # 120 Sega Genesis owners
mu = c(games = 12, hours = 9), # average games owned and hours played per week
sd = c(4, 3), # spread of each column
r = 0.6 # more games goes with more hours (strong)
) |>
mutate(console = "Sega Genesis")
snes <- faux::rnorm_multi(n = 120, # 120 Super Nintendo owners
mu = c(games = 10, hours = 11), # fewer games, more hours
sd = c(3.5, 3), # spread of each column
r = 0.3 # more games goes with more hours (weak)
) |>
mutate(console = "Super Nintendo")
console_kids <- bind_rows(genesis, snes) |> # stack both consoles
mutate(
kid_id = row_number(), # one row per kid
games_owned = pmax(1, round(games)), # whole games (everyone owns at least one)
hours_per_week = round(pmax(0, hours), 1) # no negative hours
) |>
select(kid_id, console, games_owned, hours_per_week)
glimpse(console_kids)
#> Rows: 240
#> Columns: 4
#> $ kid_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9…
#> $ console <chr> "Sega Genesis", "Sega Gen…
#> $ games_owned <dbl> 17, 22, 10, 8, 13, 11, 24…
#> $ hours_per_week <dbl> 10.0, 12.0, 5.5, 7.9, 10.…36.3 Distribution
A grouped scatter plot puts two clouds of points on the same axes. To compare the groups, we compare three things about each cloud: its center, its spread in each direction, and its tilt. Below we’ll move from the kids, to a picture, to the formula.
COUNT IT:
Start with the first three kids on each side.
show/hide
console_kids |>
group_by(console) |>
slice_head(n = 3)Below we calculate the center (mean_games and mean_hours), the spread (sd_games and sd_hours), and the tilt (the correlation, r) for each console.
show/hide
console_stats <- console_kids |>
group_by(console) |>
summarise(mean_games = mean(games_owned),
mean_hours = mean(hours_per_week),
sd_games = sd(games_owned),
sd_hours = sd(hours_per_week),
r = cor(games_owned, hours_per_week))
console_statsThe Genesis kids own about 11.7 games on average and play about 8.8 hours a week, while the Super Nintendo kids own about 9.8 games and play about 11.4 hours. The correlation for the Genesis kids (0.65) is about twice the correlation for the Super Nintendo kids (0.34).
DRAW IT:
The graph below adds an ellipse around each cloud with stat_ellipse(). Each ellipse is drawn to hold about 95% of its group’s points. The Genesis ellipse is longer and more tilted (a stronger correlation), and the Super Nintendo ellipse is rounder and sits higher on the y axis.
show/hide
ggplot(console_kids, aes(x = games_owned, y = hours_per_week,
color = console)) +
geom_point(alpha = 1 / 2) +
stat_ellipse(level = 0.95, linewidth = 1) +
geom_point(data = console_stats,
aes(x = mean_games, y = mean_hours),
shape = 4, size = 5, stroke = 2) +
labs(x = "Games owned", y = "Hours played per week",
color = "Console")
The x in the middle of each ellipse marks the group means from COUNT IT.
NAME IT:
Each console’s pairs come from a bivariate normal distribution, which is described by a vector of means and a covariance matrix:
\[ \begin{pmatrix} \text{games} \\ \text{hours} \end{pmatrix} \sim \text{Normal}\left( \begin{pmatrix} \mu_g \\ \mu_h \end{pmatrix}, \; \begin{pmatrix} \sigma_g^2 & r \sigma_g \sigma_h \\ r \sigma_g \sigma_h & \sigma_h^2 \end{pmatrix} \right) \]
The means set the center of each cloud, the standard deviations (\(\sigma_g\) and \(\sigma_h\)) set how wide and tall it is, and the correlation (\(r\)) sets the tilt. When \(r = 0\) the ellipse lines up with the axes, and as \(r\) gets closer to 1 it stretches into a thin, tilted line. The ellipses in DRAW IT are just the outlines of these distributions. Try changing the Super Nintendo r to 0.9 in rnorm_multi() and its ellipse collapses toward a line.
36.4 Grammar
CODE:
Create labels with
labs()Initialize the graph with
ggplot()and providedataMap
games_ownedto thexaxisMap
hours_per_weekto theyaxisMap
consoletocolorinside thegeom_point()
show/hide
labs_grp_scatter <- labs(
title = "The console wars",
subtitle = "Games owned vs. hours played per week",
caption = "Simulated data",
x = "Games owned",
y = "Hours played per week",
color = "Console")
ggp2_grp_scatter <- console_kids |>
ggplot(
aes(x = games_owned,
y = hours_per_week)) +
geom_point(aes(color = console))
ggp2_grp_scatter +
labs_grp_scatterGRAPH:
Adjust over-plotting with transparency (alpha).
