
31 Slope graphs
theme_savedbythebell()
Graphs in this chapter use fmggp2::theme_savedbythebell(), built from the Saved by the Bell logo: a black background with a royal blue frame, a yellow title, a white classroom panel, sky blue, pink, yellow, orange, royal blue, and navy marks, and a SignPainter title.
31.1 Description
A slope graph shows how a number changes from one time to the next, most often across two times.1 The number goes up the y axis, and the times go across the x axis. A line joins the two numbers for each group,2 so how far a line goes up or down shows how much that group changed (which is where the name comes from).3
We can make slope graphs in ggplot2 with geom_line() and geom_point().
31.2 Set up
PACKAGES:
Bring in the code we’ll use.4
show/hide
library(ggplot2)
library(dplyr) # for data manipulation
library(tidyr) # for expand_grid()DATA:
Bayside High is a school from a show, run by a man named Belding. His school gave us six students who feel very different ways about school work they take home:5 Zack finds ways to get out of it, Screech and Jessie do well on everything, and Slater and Kelly are in the middle. A quiz is a short test, and a score is the number of points a student gets on a quiz. A semester is half of a school year. We’ll make up6 ten biology quizzes and ten history quizzes for each student in the fall of 1990 and the spring of 1991. Each student gets an average score for each semester (avg_score). Zack and Slater do better in the spring, Lisa does a little worse, and Kelly and Jessie stay just the same. rnorm() picks each quiz score close to those averages,7 and pmin() keeps every score at 100 or under.8 Starting with set.seed()9 means you’ll get the same 240 quizzes every time you run the code below.
show/hide
set.seed(1989) # same quiz scores every time the code runs
bayside_quizzes <- expand_grid(
student = c("Zack", "Kelly", "Slater", "Jessie", "Lisa", "Screech"), # six Bayside students
semester = c("Fall 1990", "Spring 1991"), # two semesters
subject = c("Biology", "History"), # two subjects
quiz = 1:10 # ten quizzes in each subject
) |>
mutate(
student = factor(student, levels = unique(student)), # keep the order above
avg_score = case_when( # each student's average score
student == "Zack" ~ if_else(semester == "Fall 1990", 70, 80), # improves
student == "Kelly" ~ 82, # holds steady
student == "Slater" ~ if_else(semester == "Fall 1990", 72, 78), # improves
student == "Jessie" ~ 91, # holds steady
student == "Lisa" ~ if_else(semester == "Fall 1990", 84, 81), # slips a little
student == "Screech" ~ if_else(semester == "Fall 1990", 93, 95) # improves a little
),
score = round(pmin(100, rnorm(n(), mean = avg_score, sd = 8))) # no score above 100
)
glimpse(bayside_quizzes)
#> Rows: 240
#> Columns: 6
#> $ student <fct> Zack, Zack, Zack, Zack, Zack, …
#> $ semester <chr> "Fall 1990", "Fall 1990", "Fal…
#> $ subject <chr> "Biology", "Biology", "Biology…
#> $ quiz <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,…
#> $ avg_score <dbl> 70, 70, 70, 70, 70, 70, 70, 70…
#> $ score <dbl> 79, 79, 55, 68, 65, 67, 72, 74…A slope graph needs one number for each student in each semester, so we’ll find the average of the 20 quizzes in each semester and keep the results in bayside_slope.
show/hide
bayside_slope <- bayside_quizzes |>
group_by(semester, student) |> # one group per student per semester
summarise(avg_score = mean(score)) |> # average across both subjects
ungroup()
glimpse(bayside_slope)
#> Rows: 12
#> Columns: 3
#> $ semester <chr> "Fall 1990", "Fall 1990", "Fal…
#> $ student <fct> Zack, Kelly, Slater, Jessie, L…
#> $ avg_score <dbl> 70.00, 83.70, 72.55, 88.30, 83…31.3 Distribution
How far each line goes up or down10 is the difference between two averages. Averages move around much less than single quiz scores, but they still move a little, so a slope graph can show a small change up or down even when nothing changed. Below we’ll move from the quizzes, to a graph, to the rule behind it,11 and work out how big a change needs to be before we take it seriously.
COUNT IT:
Start with Zack’s first five biology quizzes from the fall.
show/hide
bayside_quizzes |>
filter(student == "Zack", semester == "Fall 1990") |>
select(subject, quiz, score) |>
head(5)A single quiz score usually lands about 6 points above or below the student’s average, and a few land more than 14 points away. Below we compare the change in each student’s average from fall to spring (change) with the change we built into the data we made up (true_change).
show/hide
slope_changes <- bayside_quizzes |>
group_by(student, semester) |>
summarise(avg = mean(score), truth = first(avg_score),
.groups = "drop") |>
pivot_wider(names_from = semester, values_from = c(avg, truth)) |>
mutate(change = `avg_Spring 1991` - `avg_Fall 1990`,
true_change = `truth_Spring 1991` - `truth_Fall 1990`) |>
select(student, change, true_change)
slope_changesZack’s and Slater’s averages went up, just as they should. But Kelly’s average went down by about 4.5 points, even though the average we gave her didn’t change at all.12
DRAW IT:
The graph below shows every quiz for Zack and Kelly as a point we can see through,13 with the slope graph lines going through their averages for each semester. Zack’s group of points moves up in the spring. Kelly’s two groups of points are in about the same place, and her line goes down only because her fall quizzes happened to land a little high (about 84) and her spring quizzes a little low (about 79), even though we gave her 82 in both.
show/hide
zack_kelly <- bayside_quizzes |>
filter(student %in% c("Zack", "Kelly"))
ggplot(zack_kelly, aes(x = semester, y = score, color = student)) +
geom_jitter(width = 0.08, height = 0, alpha = 1 / 3, size = 2) +
stat_summary(aes(group = student), fun = mean, geom = "line",
linewidth = 2) +
stat_summary(fun = mean, geom = "point", size = 4) +
labs(x = NULL, y = "Quiz score", color = "Student")
NAME IT:
A hill is a line that is high in the middle and low on both sides. Each quiz score is picked from a hill14 with its middle at the student’s average for that semester (and kept at 100 or under):
\[ \text{score} \sim \text{Normal}(\mu_{\text{student, semester}}, \; \sigma = 8) \]
An average of \(n = 20\) quizzes usually falls about \(\sigma / \sqrt{n} = 8 / \sqrt{20} \approx 1.8\) points from the true average.15 A slope is the difference between two of these averages, so it usually falls about \(\sqrt{1.8^2 + 1.8^2} \approx 2.5\) points from the true change.16 About 95% of slopes land within two times that (about 5 points) of the true change. Kelly’s drop is inside that range, while Zack and Slater went up by enough to stand out. Try changing quiz = 1:10 to quiz = 1:40, and every slope will move around half as much.17
31.4 Grammar
CODE:
Make the words around the graph18 with
labs()Start the graph with
ggplot()and give itdataPut
semesteron thexaxis andavg_scoreon theyaxis, and joinstudenttogroupso each student gets one line19Add
geom_line()on top,20 joinstudenttocolor,21 and setlinewidthto2Add
geom_point(), joinstudenttocolor, and setsizeto4We’ll move the
xaxis to the"top"of the graph withscale_x_discrete()
show/hide
labs_slope <- labs(
title = "Bayside High quizzes",
caption = "Simulated data",
subtitle = "Fall 1990 & Spring 1991",
x = NULL, y = "Average quiz score",
color = "Student")
ggp2_slope <- ggplot(data = bayside_slope,
mapping = aes(x = semester,
y = avg_score,
group = student)) +
geom_line(aes(color = student),
linewidth = 2, show.legend = FALSE) +
geom_point(aes(color = student),
size = 4) +
scale_x_discrete(position = "top")
ggp2_slope +
labs_slopeGRAPH:

31.5 More info
We can also cut a slope graph into small graphs22 to show a third column of groups.23
DATA:
We’ll use the bayside_quizzes data again, but this time we put it into groups by semester, student, and subject.
show/hide
bayside_grp_slope <- bayside_quizzes |>
group_by(semester, student, subject) |> # one group per student, semester, and subject
summarise(avg_score = mean(score)) |> # average across the ten quizzes
ungroup()
glimpse(bayside_grp_slope)
#> Rows: 24
#> Columns: 4
#> $ semester <chr> "Fall 1990", "Fall 1990", "Fal…
#> $ student <fct> Zack, Zack, Kelly, Kelly, Slat…
#> $ subject <chr> "Biology", "History", "Biology…
#> $ avg_score <dbl> 69.4, 70.6, 86.6, 80.8, 70.9, …GRAPH:
Make the words around the graph with
labs()Start the graph with
ggplot()and give itdataPut
semesteron thexaxis andavg_scoreon theyaxis, and joinstudenttogroupso each student gets one lineAdd
geom_line()on top, joinstudenttocolor, and setlinewidthto2Add
geom_point(), joinstudenttocolor, and setsizeto4We’ll move the
xaxis to the"top"of the graph withscale_x_discrete()Last, we make one small graph for each
subject,24 set thesizeof the words, and move the key25 to the"bottom"of the graph.
show/hide
labs_grp_slope <- labs(
title = "Bayside High quizzes",
caption = "Simulated data",
subtitle = "Fall 1990 & Spring 1991, by subject",
x = NULL, y = "Average quiz score",
color = "Student")
ggp2_grp_slope <- ggplot(data = bayside_grp_slope,
mapping = aes(x = semester,
y = avg_score,
group = student)) +
geom_line(aes(color = student),
linewidth = 2, show.legend = FALSE) +
geom_point(aes(color = student),
size = 4) +
scale_x_discrete(position = "top") +
facet_wrap(. ~ subject,
ncol = 2) +
theme(
legend.position = "bottom",
axis.text.x = element_text(size = 9),
axis.text.y = element_text(size = 9),
strip.text = element_text(size = 10))
ggp2_grp_slope +
labs_grp_slope
Each average for a subject comes from only ten quizzes, so these slopes move around more than the ones above (they usually fall about \(\sqrt{2} \times 8 / \sqrt{10} \approx 3.6\) points from the true change).26 That’s why Jessie’s history line goes up while her biology line goes down, even though the average we gave her is the same in both semesters.
Slope graphs show changes in a numeric value, typically over two points in time.↩︎
Each group or unit of measurement.↩︎
Differences between the two time points are represented by the slope of the lines (hence the name).↩︎
Install and load the packages.↩︎
Very different relationships to homework.↩︎
Simulate.↩︎
rnorm()draws each quiz score from a normal distribution around those averages, withsd = 8.↩︎pmin()caps the scores at 100.↩︎Setting the random seed.↩︎
The slope of each line.↩︎
The formula.↩︎
Her true average.↩︎
A faded (partially transparent) point, set with
alpha.↩︎A normal distribution centered on the student’s average.↩︎
Its standard deviation, the standard error.↩︎
The standard deviation of a slope.↩︎
The noise in every slope is cut in half.↩︎
Labels.↩︎
Map
semestertox,avg_scoretoy, andstudenttogroup.↩︎A layer.↩︎
Map
studentto thecoloraesthetic.↩︎Facets.↩︎
A third categorical variable.↩︎
Facet by
subjectwithfacet_wrap().↩︎The legend.↩︎
Their standard deviation.↩︎