Published on

Why Care About Regression Assumptions?

Authors

Every regression ends with a number going in front of someone. A deck, a report, a decision.

The problem is that regression hands you the same clean output whether that number is trustworthy or not. Same coefficient, same p-value, same tidy interval. No warning, no error, no hint that anything is off.

Checking the assumptions is how you find out. It takes about two minutes, and it decides which of these three you are entitled to say:

  • "Each extra bedroom adds $12,000."
  • "And it's statistically significant."
  • "We expect somewhere between 40 and 60."

Three different claims, resting on different footings. One broken assumption kills the first outright. A different one leaves the first standing and takes out the other two instead.

So when a diagnostic plot looks off, the useful question is not "is my model broken?" It is "which of those three sentences did I just lose?"

Part 2 of a six-part series on regression. Previously: what "we ran a regression" actually means.

The four regression assumptions, in plain terms

Every regression makes four promises, and all four are about the same thing: the gaps between what actually happened and what the model predicted.

Real car got 30 mpg. Model said 27. Gap of 3.

Those gaps have a name, residuals, and they are where every problem shows up. The fitted line always looks reasonable. The gaps are what give it away.

Here are the four promises:

#The assumptionThe promise, in EnglishWhat a broken one looks like
1LinearityThe real relationship is a straight line, not a curveThe gaps curve
2Constant varianceThe gaps are about the same size everywhereThe gaps fan out
3IndependenceOne row's gap tells you nothing about the next row'sGaps of the same sign clump together
4NormalityThe gaps pile up in a bell shapeThe ends of the QQ plot bend away

Those visual tells are how you go from "this plot looks wrong" to "assumption 2 broke," which is the move the rest of this post depends on.

Two of those have intimidating names you'll meet elsewhere. Homoskedasticity just means number 2 holds: the spread stays put. Heteroskedasticity means it doesn't, usually fanning out as the predictions get bigger.

Why it matters which one broke

Each promise protects a different one of those three sentences:

AssumptionWhich sentence it protectsWhat you lose if it breaks
Linearity"Each extra bedroom adds $12,000"The coefficient itself. It measures the wrong thing.
Constant variance"It's significant", "between 40 and 60"Your p-values and intervals. The coefficient survives.
IndependenceThe same twoSame, usually worse. Error bars come out too small.
NormalityThe same two, on small samplesVery little, once you have a few hundred rows.

Read the right-hand column again. Only the first one threatens your coefficients. The other three threaten the uncertainty around them.

Broken independence deserves a note: it usually shrinks your error bars rather than widening them, so p-values come out too small and things look more significant than they are. There is also one case where it does break the coefficients, and it is worth knowing if you work with time series: a model that predicts today from yesterday's value of the same thing, with correlated gaps, is biased rather than merely inefficient.

So a broken constant-variance promise means "my effect sizes stand, my significance claims don't." A broken linearity promise means "start over." That is a five-second decision once you can name which one failed.

Normality is the one to worry about least. It matters when you have twenty rows. At sixteen hundred, a bit of skew in the gaps will not move your conclusions.

How to check regression assumptions in R: three plots

model  <- lm(quality ~ ., data = beer)
resids <- residuals(model)

plot(fitted(model), resids)      # shape and spread
qqnorm(resids); qqline(resids)   # bell-ness
hist(resids, 10)                 # sanity check

Here are the first two on a real model:

Two panels. Left: residuals against fitted values, a dense horizontal band of points centered on zero with roughly even vertical spread across the full range and no curvature. Right: a normal Q-Q plot where the points track the reference line closely through the middle and bend away from it at both ends.

The left plot checks promises 1 and 2 at once. Does the band bend? That's linearity. Does it fan out or pinch in as you move right? That's constant variance. You want a shapeless horizontal smear, which is roughly what this is: no funnel, no bend, centred on zero throughout.

One trap before you judge that plot. The cloud looks narrower at the far left and far right, which reads like the spread shrinking. It isn't. There are simply fewer rows out at the extremes, so you see less of the tail. That's density, not variance, and it fools people constantly.

If you'd rather check than squint, split the fitted values into fifths and measure each one:

b <- cut(fitted(model), quantile(fitted(model), seq(0, 1, 0.2)), include.lowest = TRUE)
round(tapply(residuals(model), b, sd), 1)     # spread within each fifth
round(tapply(residuals(model), b, mean), 1)   # average gap within each fifth

On this model:

Fifth of fitted valuesSpreadAverage gap
395 to 52173.3+5.8
521 to 54772.7−6.0
547 to 57379.8−1.6
573 to 60686.2−3.4
606 to 70187.5+5.2

Widest spread over narrowest is 1.2, so constant variance holds. The average gaps stay within ±6 on residuals that reach past ±300, so linearity holds too.

The right plot checks promise 4. A QQ plot sorts your gaps from smallest to largest and asks what a perfect bell curve would have produced in each position. Match, and the dots sit on the line.

Look at those bent ends. They mean the extremes are slightly fatter than a bell curve predicts, and at 1,599 rows that changes nothing. Bent tails are worth a glance, not a rewrite.

The histogram catches one thing the QQ plot hides: two humps. If your gaps have two separate peaks, you almost certainly left out a categorical variable that splits the data into two groups with different averages. That's a missing column, not a distribution problem.

Promise 3 needs a fourth plot, but only if your rows have an order. If the data is a time series, or arrived in a sequence that could matter, plot the gaps against that order:

plot(residuals(model), type = "b")   # look for runs of the same sign
lmtest::dwtest(model)                # Durbin-Watson: 2 is clean, under 1 is trouble

Runs of same-signed gaps mean consecutive rows are related. The Durbin-Watson test puts a number on it, running from 0 to 4, where 2 means no correlation and anything below 1 or above 3 is a problem.

If your rows have no natural order, there is nothing to plot, and independence becomes a fact about how the data was collected rather than something you can see. Repeated measures on the same person, students in the same classroom, customers in the same store: all break it by design, and only knowing the study will tell you.

Fixing heteroscedasticity (the fan shape)

This is the cheap one. When the gaps grow as the predictions grow, change the scale of the thing you're predicting. Which change depends on how fast the spread is growing:

How the spread behavesWhat to do to your response
Stays flatNothing. You're fine.
Grows like the square rootTake the square root
Grows in step with the predictionTake the log
Grows faster stillTake one divided by it

The middle case is the common one. Spread growing in step with level is the default behaviour of anything measured in money, counts, or population: prices, revenue, claim sizes, web traffic. That's why logs are everywhere.

Two things to keep straight. The transformation goes on the response, not the predictor, because that's where the gaps live. And it changes what your coefficient means: after logging the response, a coefficient is a percentage effect rather than an additive one. Don't carry the old sentence over.

The exact conversion is 100 * (exp(coefficient) - 1) percent. People often shortcut that to "multiply the coefficient by 100," which is close enough only while the coefficient stays under about 0.2. At 0.5 the shortcut says 50% and the truth is 65%.

One warning, from a mistake worth watching me make. I log-transformed a predictor that didn't need it and refit:

original:          R-squared = 0.1360
log(predictor):    R-squared = 0.1331

Slightly worse, which is exactly what should have happened. The relationship was already straight, so bending it could only hurt.

Fixing non-linearity (the curved residual plot)

This is the expensive one, because linearity is the promise whose failure breaks your coefficients. Options, roughly in order of how much readability they cost:

  1. Change the predictor's scale. Log, square root, one-over. Cheap, and the model stays easy to read.
  2. Add a squared term. Captures one bend. Past that you're usually fitting noise in the tails.
  3. Add an interaction. If the slope genuinely differs by group, this fixes it with no curve at all.
  4. Split the model. Sometimes two populations honestly need two models.

Five things worth remembering

  1. All four assumptions are about the gaps between what happened and what the model predicted.
  2. Linearity is the emergency. Break it and your coefficients are wrong. Break any other promise and only your error bars are.
  3. Broken constant variance means your p-values are wrong and your effect sizes are fine. That is a sentence you can say out loud in a meeting.
  4. Broken normality is the least urgent of the four. Bent QQ tails on a big sample are fine.
  5. Match the fix to the diagnosis. Shopping for transformations that raise R-squared is not diagnosis.

Previous: What "we ran a regression" actually means · Next: How to read a regression coefficient

See also: 15 ways to misread your own regression model.

Get the next one

Posts on data, analytics and the judgment calls that decide whether a model gets trusted.

ShareLinkedInXReddit