ANOVA and Regression
ANOVA and Regression
How much of the variation can other variables explain?
Chapter 8
Polla Fattah
By the end of today you can
- explain how ANOVA splits variation, and what F compares;
- compare three or more groups, and find which differ;
- analyse two factors and their interaction;
- fit and interpret simple and multiple linear regression;
- separate a real effect from a confounder;
- model curves and interactions;
- read diagnostic plots;
- interpret logistic regression as odds ratios and probabilities;
- report ANOVA and regression in a thesis.
One idea behind both methods
Students differ in stress, grades, and wellbeing.
Which part of those differences is connected with faculty, sleep, or support, and which part remains unexplained?
| Method | Models |
|---|---|
| ANOVA | the averages of three or more groups |
| linear regression | a numeric outcome from predictors |
| logistic regression | a yes-or-no outcome from predictors |
The data for this chapter
scores <- questionnaire |>
mutate(stress_4 = 6 - stress_4,
stress = rowMeans(pick(stress_1:stress_6), na.rm = TRUE),
support = rowMeans(pick(support_1:support_6), na.rm = TRUE)) |>
select(student_id, stress, support)
study <- students |>
left_join(scores, join_by(student_id)) |>
left_join(semesters |> filter(semester == 1), join_by(student_id))One row per student: background, first semester, and questionnaire scores.
Why not ten t-tests?
Five faculties give 10 pairs.
Each test has a 5% false alarm risk, and across 10 tests the chance of at least one is about 40%.
ANOVA asks one question first: do the faculty averages differ at all?
Variation between and within groups
Group averages (copper) vs the overall average: between. Points vs their own average: within.
The F statistic
\[ F = \frac{\text{variation between groups}}{\text{variation within groups}} \]
F near 1: averages differ no more than chance produces. Large F: they differ more.
The ratio matters, not only the gaps
tight <- group_scores |> mutate(stress = group_avg + 0.5 * offsets)
wide <- group_scores |> mutate(stress = group_avg + 4 * offsets)| Version | Group averages | F |
|---|---|---|
| tight | identical | 39.2 |
| wide | identical | 0.6 |
Half a point is convincing when students within a group hardly differ, and unremarkable when they differ by two.
Stress by faculty
Much overlap. Health Sciences sits a little higher, Humanities a little lower.
One-way ANOVA
p = 0.003: the faculties differ in stress.
The next question is how much.
Eta squared: the size of the effect
\(\eta^2\) ≈ 0.03: only about 3% of the differences in stress lie between faculties.
| \(\eta^2\) | 0.01 | 0.06 | 0.14 |
|---|---|---|---|
| label | small | medium | large |
Faculty matters, but says very little about one student’s stress.
Post-hoc tests: which groups differ?
Tukey’s test compares every pair while keeping the overall false alarm rate at 5%.
Only one pair differs: Health Sciences is 0.34 points more stressed than Humanities.
Assumptions of ANOVA
| Assumption | Check |
|---|---|
| independent observations | the design |
| roughly normal in each group | graphs, or large groups |
| similar spread | Levene’s test: car::leveneTest() |
Kruskal-Wallis compares ranks when assumptions fail. Here it agrees.
Two-way ANOVA
| Term | Asks |
|---|---|
| main effect of programme | Master’s vs PhD, on average |
| main effect of study mode | full-time vs part-time, on average |
| interaction | does the study-mode effect depend on programme? |
An interaction plot
Parallel lines: no interaction. Part-time is lower in both programmes.
Testing main effects and interaction
programme * study_mode asks for both main effects and their interaction.
Study mode matters; programme and the interaction do not.
A significant interaction is interpreted before the main effects.
Regression models the average outcome
ANOVA compares groups. Regression models how an outcome changes with predictors, numeric or categorical.
ANOVA is a special case of regression.
Average GPA at each level of sleep
sleep_bands <- study |>
filter(!is.na(sleep_hours)) |>
mutate(sleep_band = round(sleep_hours * 2) / 2) |>
summarise(mean_gpa = mean(gpa), students = n(), .by = sleep_band)The average GPA of students who sleep 5 hours, 5.5 hours, 6 hours, and so on.
The line summarises conditional averages
The spread of grey points around the line is what the model does not explain.
Simple linear regression
\[ \text{GPA} = b_0 + b_1 \times \text{sleep} + \text{error} \]
| Term | Meaning |
|---|---|
| \(b_0\), intercept | predicted GPA at zero sleep; places the line |
| \(b_1\), slope | change in predicted GPA per extra hour |
| residual | vertical distance from point to line |
Least squares makes the squared residuals as small as possible.
Fitting a line to five students
five <- tibble(sleep = c(5, 6, 6.5, 7, 8),
gpa = c(2.8, 3.0, 3.2, 3.1, 3.4))
lm(gpa ~ sleep, data = five)Each extra hour goes with a GPA about 0.19 higher.
Nobody sleeps zero hours, so the intercept rarely means anything on its own.
Three results in summary()
| Result | Value | Meaning |
|---|---|---|
| slope | 0.08 | GPA per extra hour of sleep |
| R-squared | 0.06 | share of GPA variation explained |
| residual SE | 0.32 | typical distance from the line |
Sleep matters, but it is far from the whole story.
Multiple regression
| Predictor | Change in GPA per unit |
|---|---|
| sleep (hour) | 0.11 |
| study (hour a week) | 0.007 |
| stress (point) | -0.08 |
| support (point) | 0.11 |
Each coefficient holds the other predictors constant.
How much the model explains
R-squared ≈ 25%: modest, and normal for grades, which depend on much no survey measures.
- adjusted R-squared corrects for the number of predictors;
lm()drops students with any missing value: 13 here.
Always report the number of cases each model is based on.
Categorical predictors
R compares each category with a reference category, alphabetically first: Female.
genderMale is tiny and far from significant: no evidence that gender affects GPA. That is a finding too.
relevel(factor(faculty), ref = "Humanities") chooses another reference.
Two explanations for caffeine and GPA
flowchart LR
subgraph A["Caffeine as a cause"]
C1[Caffeine] --> G1[GPA]
end
subgraph B["Sleep as a confounder"]
S2[Sleep] --> C2[Caffeine]
S2 --> G2[GPA]
C2 -. "apparent link" .- G2
end
Regression can tell them apart by comparing students who sleep the same amount.
Adjusting for the confounder
| Model | Caffeine coefficient | p |
|---|---|---|
| caffeine alone | -0.00043 | <0.001 |
| with sleep | -0.00008 | 0.51 |
Among students who sleep the same, caffeine makes no difference to grades.
Regression adjusts only for what you include
A confounder is removed only if it was measured and put in the model.
Outside a randomised experiment, regression shows associations adjusted for the variables in the model, not proof of cause.
Curved relationships
I() calculates study_hours^2 before fitting.
The squared term is negative and significant: the line bends downwards.
Diminishing returns
The first hours make a real difference. The curve peaks at about 32 hours a week.
Interactions in regression
study <- study |> mutate(support_c = support - mean(support))
lm(gpa ~ support_c * programme + sleep_hours + study_hours, data = study)Centring: zero now means average support.
| Programme | GPA per point of support |
|---|---|
| Master’s | 0.10 |
| PhD | 0.24 |
Support matters about 2.3 times as much for PhD students.
What violated assumptions look like
A shapeless band is good. A U shape means a missed curve. A funnel makes p-values unreliable.
Checking the GPA model
Residuals against fitted: a shapeless band, no curve, no funnel.
Q-Q plot of residuals: close to the line, so roughly normal. The model looks good.
Tidy results with broom
library(broom)
tidy(gpa_model, conf.int = TRUE) # coefficients with intervals
glance(gpa_model) # overall fitModel results as data frames, ready for a thesis table.
Writing it up
A multiple regression of first-semester GPA on sleep, study hours, stress, and supervisor support explained 25% of the variance, F(4, 582) = 49.14, p < .001, N = 587. Holding the other predictors constant, each additional hour of sleep was associated with a GPA 0.11 points higher (95% CI [0.08, 0.13]).
The null hypothesis of a zero sleep coefficient was rejected.
Yes-or-no outcomes need logistic regression
Linear regression could predict probabilities below 0 or above 1.
Logistic regression models the probability of “yes” in a way that stays between 0 and 1.
Odds and odds ratios
jobs <- table(students$employment, students$considering_dropout)
odds_full <- jobs["Full-time job", "Yes"] / jobs["Full-time job", "No"]
odds_none <- jobs["None", "Yes"] / jobs["None", "No"]| Group | Odds of considering dropout |
|---|---|
| full-time job | 0.35 |
| no job | 0.16 |
| odds ratio | 2.2 |
1 means no difference; above 1 higher odds; below 1 lower.
A model for dropout
study <- study |> mutate(dropout = as.integer(considering_dropout == "Yes"))
dropout_model <- glm(
dropout ~ stress + support + financial_worry + employment + study_mode,
data = study, family = binomial)
exp(cbind(odds_ratio = coef(dropout_model),
confint.default(dropout_model)))The outcome is 0 or 1. exp() turns log-odds into odds ratios.
Reading the odds ratios
| Predictor (per point) | Odds ratio | 95% CI |
|---|---|---|
| stress | 3.60 | 2.25 to 5.77 |
| financial worry | 1.57 | 1.24 to 2.00 |
| support | 0.33 | 0.22 to 0.49 |
Stress raises the odds; support cuts them by about 67%. The stress hypothesis is supported.
What happened to employment?
Alone, a full-time job about doubled the odds of considering dropout.
In the model, both employment intervals include 1.
A job likely matters because of the stress and money pressure it brings. Multiple regression makes that visible.
Predicted probabilities are easier to read
examples <- tibble(stress = c(2.5, 4.0), support = c(4.0, 2.0),
financial_worry = 3, employment = "None",
study_mode = "Full-time")
predict(dropout_model, newdata = examples, type = "response")| Student | Chance of considering dropout |
|---|---|
| low stress, good support | 1% |
| high stress, little support | 42% |
Explaining is not predicting
This model explains who considers dropping out.
Whether it can predict that for new students, and how to test that fairly, is a different question.
It is the subject of Chapters 11 and 12.
In your field: forestry and agriculture
Timber volume of 31 black cherry trees from measurements taken without felling them.
Girth and height explain 95% of volume, against 25% for GPA: physics is more predictable than people.
Practical lab: the Chapter 8 playground
Work through the playground exercises in your browser, with hints and solutions.
Every exercise also runs in RStudio, from the downloadable chapter project.
Practical exercises 1–3: ANOVA and regression
- Wellbeing by faculty: ANOVA, \(\eta^2\), and Tukey if significant.
- Wellbeing on sleep: interpret the slope and R-squared.
- Add stress and support: what happens to the sleep slope and R-squared?
Practical exercises 4–6: categories, odds, confounders
- Add study mode: name the reference and interpret its coefficient.
- Dropout on burnout alone: report and explain the odds ratio.
- Draw a confounder from your own field, and say how regression would separate it.
Try this yourself
Choose one outcome in the student wellbeing data.
- plot it against one predictor;
- fit a simple regression, then add two plausible confounders;
- watch how the first coefficient changes;
- check the residual plot;
- write one sentence reporting the adjusted effect with its interval.
Troubleshooting guide (Part 1)
| Symptom | Likely cause |
|---|---|
| ten t-tests, one “significant” | multiple testing; use ANOVA and post-hoc tests |
| significant ANOVA, unclear which group | no post-hoc test |
| a coefficient changes sign when adding a predictor | a confounder, or correlated predictors |
| fewer cases than expected | lm() dropped rows with missing values |
Troubleshooting guide (Part 2)
| Symptom | Likely cause |
|---|---|
| a U shape in the residuals | a missed curve |
| a funnel in the residuals | unequal spread; p-values unreliable |
| predicted probabilities above 1 | linear regression on a yes-or-no outcome |
| coefficients hard to explain to readers | log-odds not turned into odds ratios or probabilities |
Completion checklist
Misconceptions to leave behind (Part 1)
| Misconception | Better mental model |
|---|---|
| a significant coefficient shows a cause | an association adjusted for the model’s variables |
| a low R-squared makes a model useless | a real effect can explain little variation |
Misconceptions to leave behind (Part 2)
| Misconception | Better mental model |
|---|---|
| each coefficient describes its predictor alone | each holds the others constant |
| a significant ANOVA shows all groups differ | at least one differs; post-hoc tests say which |
The chapter in one sentence
ANOVA and regression split variation into what the predictors explain and what remains, and each coefficient holds the others constant, but only the ones you measured.
Next: Chapter 9
The next chapter looks at many variables at once:
- constructs and latent variables;
- principal component analysis;
- factor analysis of the questionnaire;
- reporting a questionnaire’s structure;
- cluster analysis for student profiles.
Questions
Which confounder could explain the main association in your own research?
Did you measure it?