AdvancedIndependently reviewed, not yet spot-checked by a human

Regression model diagnostics

What each of the four residual plots is looking for, why passing all four still leaves problems undetected, where the importance of VIF is overstated, how influence is measured, and why stepwise regression is one of the most common statistical errors in the medical literature — demonstrated by simulation.

What diagnostics are checking

Getting a set of coefficients out does not mean the model is right. Every p-value and every confidence interval in linear regression rests on four assumptions:

  1. Correct specification — the shape of the relationship between the variables you put in and the outcome is right (linearity, interactions)
  2. Constant variance (homoscedasticity) — the spread of the residuals does not change with the fitted value
  3. Approximately normal residuals — this mainly affects intervals in small samples; in large samples the central limit theorem rescues you
  4. Independent observations — this one is invisible in the data and has to come from the design (repeated measures, cluster sampling, and patients from the same hospital all violate it). For what to do once it is violated, see repeated measures and clustered data

Diagnostics work through the first three, plus two problems the output never shows: collinearity and influential points. This page reuses the linear model from B2-01 and the logistic model from B2-02, whose output you have already read.

What to look for in the residual plots

Four diagnostic plots: residuals against fitted values, a normal Q-Q plot, a scale-location plot, and leverage against standardised residuals with Cook's distance contours. None of the four shows an obvious systematic pattern.
The four standard diagnostic plots for the B2-01 model. The red line is a lowess smooth; the dashed red curves in panel D are the Cook's distance contours at 0.5 and 1, and the circled point is this model's largest Cook's D.Plotting script figures/scripts/B2-04-model-diagnostics.R
PlotWhat it looks forWhat broken looks likeThis model
A. Residuals vs fittedWhether the specification is right (missing curvature, interactions)The smooth bends into a U or an inverted UNo overall curvature detected (Tukey test p 0.632, not statistically significant)
B. Normal Q-QThe tails of the residual distributionBoth ends leave the diagonal (heavy tails, skew)No departure from normality detected (Shapiro-Wilk p 0.765, not statistically significant)
C. Scale-locationWhether the variance is constantThe smooth climbs to the right (a fan shape)No change in variance with fitted value detected (Breusch-Pagan p 0.765, not statistically significant)
D. Leverage vs residualWhether a single point is driving the modelA point falls outside the Cook’s D contour at 0.5 or 1Largest Cook’s D 0.118

The residual standard deviation over the lower half of the fitted values is 628 and over the upper half 643 — almost identical, so panel C being flat has numbers behind it.

Two residual plots: on the left, residuals against maternal age, where the lowess smooth turns upward at both ends; on the right, residuals against maternal weight, where the smooth is roughly flat.
The same residuals, plotted against one variable at a time. The smooth in the age panel turns up at both ends — exactly what the overall test in the previous section missed. The maternal weight panel is the control, and looks the way it should.Plotting script figures/scripts/B2-04-model-diagnostics.R

Collinearity and VIF

Collinearity means one variable can be very nearly predicted from a linear combination of the others. The variance inflation factor (VIF) measures precisely that:

VIFj=11Rj2\mathrm{VIF}_j = \frac{1}{1 - R_j^2}

where Rj2R_j^2 is the R² from regressing the jj-th variable on all the others. VIF=4\mathrm{VIF} = 4 means that coefficient’s standard error has been inflated two-fold.

Every VIF in the B2-01 model is low (the largest is 1.34). To demonstrate the problem, a column holding the same maternal weight converted to kilograms is added deliberately — something that happens often once real datasets are merged. Note that the conversion carries a little measurement / rounding error (lwt * 0.4536 + rnorm(n, 0, 0.5)), which is what merged real data actually looks like; had the conversion been exactly proportional, R would declare the column aliased with lwt, return NA for the coefficient, report an infinite VIF, and the table below could not be produced at all:

VariableVIF, original modelVIF after adding the duplicate column
age1.101.11
lwt1.22771.05
lwt_kg771.66
smoke_fYes1.161.16
race_fBlack1.191.19
race_fOther1.341.35
ht1.081.08
ui1.041.04

Influential points

Outlier, leverage and influence are three different things:

  • Outlier: yy is far from its fitted value → look at the standardised residual
  • Leverage: xx sits at the edge of the covariate space → look at the hat value; the average is p/np/n = 0.042
  • Influence: would the coefficients change if the point were removed → Cook’s distance, which combines the other two multiplicatively: it is large only when the residual and the leverage are both large
Di=j(y^jy^j(i))2pσ^2D_i = \frac{\sum_j (\hat{y}_j - \hat{y}_{j(i)})^2}{p \cdot \hat{\sigma}^2}

Only the third one really matters. A point with an extreme xx that sits on the regression line has high leverage and low influence; it is in fact helping to hold the slope steady.

The five largest Cook’s D values in this model:

RowBirth weight (g)Maternal ageMaternal weight (lb)LeverageStandardised residualCook’s D
1304990451230.1062.820.118
1331135341870.153-1.710.066
1321021291300.057-2.900.063
1063790252410.1431.660.058
1023756191840.1081.720.045

These diagnostics also close a loose end from B2-01. The quadratic age term on that page was statistically supported (p 0.018), but refitting without the 3 mothers older than 35 makes the evidence disappear (p 0.373). 3 people can decide a conclusion in a model of 189 — which is why “significant” and “robust” are two different things, and why splines beat polynomials.

Diagnostics differ for binary and count outcomes

Those four plots cannot be carried over to logistic regression. The reason is simple: the outcome takes only the values 0 and 1, so the raw residuals can only fall on two curves, and the plot is always two bands with nothing to read in it (in this model the raw residuals range from -0.74 to 0.91, but the shape is determined entirely by the outcome value).

Three other things take their place:

1. Calibration — whether the predicted probabilities match the observed rates. Split the patients into ten groups by predicted probability:

DecilenMean predicted probabilityExpected eventsObserved events
1190.0571.10
2190.1021.92
3190.1542.95
4190.2073.93
5190.2434.64
6180.2835.17
7190.3416.56
8190.4508.68
9190.55110.511
10190.73113.913

The Hosmer-Lemeshow statistic is 4.68 on df = 8, p 0.792 — no systematic miscalibration was detected.

2. Discrimination — see ROC and AUC (the B4 family). Calibration and discrimination are independent: a model can rank patients well while its probabilities sit systematically too high, and it can produce well-calibrated probabilities while ranking poorly.

3. Influence still matters — a glm object has cooks.distance() and hatvalues() as well, and they are read the same way.

As for Poisson regression, the crucial diagnostic is not a residual plot but the dispersion: whether the Pearson chi-square divided by the degrees of freedom is close to 1. On that page the example comes out at 11.05, meaning the standard errors need to be inflated by roughly 3.32-fold.

Stepwise regression: one of the most common statistical errors in the medical literature

Stepwise regression means letting an algorithm add and drop variables automatically by p-value or AIC until a set of “significant” variables remains. R’s step() / stepAIC() and the forward / backward options in SPSS all do this.

It is extremely common in the literature and almost always wrong. A simulation makes it visible: take the B2-01 model, add 10 pure standard normal random variables (unrelated to birth weight in any way), run stepAIC(), and repeat 300 times.

Two bar charts. The left shows how many of the pure noise variables were retained on each run, mostly one to three. The right shows how many of them reached p below 0.05 in the final model, with close to half of the runs having at least one.
300 simulation runs. Left: stepAIC retains 1.81 pure noise variables on average. Right: of those, 0.63 on average reach p < 0.05 in the final model, and 48% of runs produce at least one "significant" noise variable.Plotting script figures/scripts/B2-04-model-diagnostics.R

A harsher version: replace the outcome with random numbers too, and feed in 15 random predictors, so there is nothing in the data to find at all. Across 300 runs:

  • 2.59 variables retained on average
  • 55% of final models contain at least one variable with p < 0.05
  • the mean R² is 0.047
  • 64% of final models also return a “significant” overall F test

That last figure needs care with its denominator: in 24 of those 300 runs stepAIC deleted every variable and left only the intercept, and such a model has no overall F test. The 64% above counts those 24 runs as “not significant” (denominator 300); count only the 276 runs that actually have an F test and the figure is 70%. Both numbers deserve to be seen — reporting only the second quietly discards the runs closest to the null, the ones where stepwise selection picked nothing, and pushes the figure a few points further in the “stepwise regression manufactures findings” direction. Incidentally, even on pure noise, 92% of runs left stepAIC holding at least one variable.

So how should the variables be chosen

If your question isDo this
Estimating the effect of an exposure (most clinical research)Let a causal diagram (DAG) decide which variables to adjust for, and write it into the protocol in advance. Do not let p-values decide what stays.
Building a prediction modelWork out the required sample size first, then use penalised methods (LASSO, ridge, elastic net) with internal validation (bootstrap or cross-validation) and external validation. Report following TRIPOD.
Exploratory work with no prior hypothesisLegitimate, but label the whole analysis exploratory, do not report p-values as conclusions, and expect to need a second dataset to confirm anything.

All three share one feature: whether a variable stays is not decided by a p-value from the same dataset.

Run it yourself

library(MASS)
data(birthwt, package = "MASS")
bw <- birthwt
bw$race_f  <- factor(bw$race,  levels = 1:3, labels = c("White", "Black", "Other"))
bw$smoke_f <- factor(bw$smoke, levels = 0:1, labels = c("No", "Yes"))

fit <- lm(bwt ~ age + lwt + smoke_f + race_f + ht + ui, data = bw)

par(mfrow = c(2, 2)); plot(fit)          # the four standard diagnostic plots

# ⚠️ Those four plot against the fitted value; curvature in one variable gets diluted
plot(bw$age, residuals(fit)); lines(lowess(bw$age, residuals(fit)))

# VIF, computed from the definition (no need for car)
X <- model.matrix(fit)[, -1]
sapply(seq_len(ncol(X)), function(j) 1 / (1 - summary(lm(X[, j] ~ X[, -j]))$r.squared))

# Collinearity demo: add the same weight again, converted to kilograms
# ⚠️ The conversion needs a little error, or the two columns are exactly proportional,
#    R calls them aliased, the coefficient is NA and no VIF can be computed
bw$lwt_kg <- bw$lwt * 0.4536 + rnorm(nrow(bw), 0, 0.5)
fit_col <- lm(bwt ~ age + lwt + lwt_kg + smoke_f + race_f + ht + ui, data = bw)
summary(fit_col)$coefficients[c("lwt", "lwt_kg"), ]

# Influential points: find them, then actually refit and see whether the conclusion moves
cd <- cooks.distance(fit)
worst <- which.max(cd)
coef(fit)["smoke_fYes"]
coef(lm(formula(fit), data = bw[-worst, ]))["smoke_fYes"]

# What stepwise selection picks up: throw in 10 random variables and try it once
d <- bw[, c("bwt", "age", "lwt", "smoke_f", "race_f", "ht", "ui")]
for (j in 1:10) d[[paste0("z", j)]] <- rnorm(nrow(d))
summary(stepAIC(lm(bwt ~ ., data = d), direction = "both", trace = 0))

Verified with R 4.6.0 and MASS 7.3.65. The car package is not installed, so the VIFs on this page are computed from the definition; car::vif() returns GVIF for factors, which is a different number.

Common misuses

MisuseWhy it is wrong
Fitting a regression and looking at no diagnostics at allThe p-values and confidence intervals are only as good as the assumptions
Declaring the specification correct on the strength of the four standard plotsCurvature in one variable is diluted inside the fitted value; plot residuals variable by variable
Using a normality test to decide whether linear regression is allowedThe assumption is about the residuals, not the outcome, and it matters least in large samples
Dropping variables because the VIF is highCollinearity only harms the variables caught up in it; if yours is not among them, leave it alone
Treating VIF 5 or 10 as a hard thresholdBoth are conventions; the question is whether the standard error still answers your question
Comparing a car::vif() GVIF with a column-by-column VIFThey are defined differently and the values are not comparable
Deleting a high-leverage point as an outlierHigh leverage is not influence; look at Cook’s distance
Cleaning data automatically with a Cook’s D thresholdCheck whether it is a data error instead, and run a sensitivity analysis
Deleting influential points without declaring itData manipulation
Carrying the linear model’s residual plots over to logistic regressionRaw residuals from a 0/1 outcome only ever line up in two bands
Claiming good calibration because Hosmer-Lemeshow was not significantThat means “no evidence it is broken”; both the grouping and the sample size drive the result
Selecting variables by stepwise regression and reporting the p-values as usualThe selection is a multiple comparison; the p-values, CIs and R² are all over-optimistic
Calling the output of stepwise regression “independent risk factors”That set of variables changes with the subsample
Screening by univariable p-value before building the multivariable modelThe same problem in another form, and it invalidates the inference just as thoroughly

Reproducing every number on this page

/opt/homebrew/bin/Rscript figures/scripts/B2-04-model-diagnostics.R

Read the figure

The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.

Maternal weight is entered twice in the same model, once in pounds and once in kilograms. What happens to the lwt term?

Show the answer and why

Correct answer: Its VIF becomes 771.1 - two near-identical variables explain each other

lwt's VIF goes from 1.2 to 771.1. "Same information, different units" is true of the data and false of the estimates: two near-identical variables explain each other, the model cannot decide which owns the effect, and the individual standard errors blow up. 771.7 is the model's largest VIF, sitting on the kilogram version rather than on this row. Note that R-squared and the residual standard deviation barely move - collinearity does not make the model predict worse, it makes individual coefficients unreadable.

The duplicated weight variable wrecks lwt's VIF. What happens to the smoking coefficient?

Show the answer and why

Correct answer: It becomes -358.5 - a shift of well under a percent, because it is uncorrelated with the pair

Smoking moves from -360.7 to -358.5, a shift of well under one percent. Collinearity touches only the terms that are highly correlated with each other; coefficients unrelated to them are unaffected - so a huge VIF is not a reason to rebuild the whole model, the question is whether the term you care about got caught up in it. 647.4 is the residual standard deviation rather than any coefficient, and it barely moves either, which is the same point about prediction.

This model's overall residual plot, normality test and homoscedasticity test all look clean. So what is regressing the residuals on each covariate's square meant to reveal?

Show the answer and why

Correct answer: Whether age is linear: p = 0.017, evidence against linearity

Passing the overall diagnostics only means the residuals carry no structure visible at a glance; splitting by variable is what exposes the significant quadratic term on age (0.017), meaning age may not enter linearly. 0.749 belongs to maternal weight, which really is fine, and 0.632 is Tukey's non-additivity test, which asks about the model as a whole and misses age just as the other overall checks did. "All the diagnostics passed" often means only that nothing was split finely enough.

Watch next

生物統計學一 93.【迴歸分析 (1)】Simple Linear Regression Model
繁中臺大開放式課程 NTU OCW· 22 minIn Mandarin; lists the assumptions of linear regression one by one. Those are exactly what this page checks, so start by knowing what there is to check.
Lec04 統計學(二) Ch11.1-11.5 簡單廻歸分析與相關分析
繁中NYCU OCW· 143 minNo verified video on this site is devoted to regression diagnostics; the residual and model-testing segments of this long lecture are the closest supplement.

Sources and licences

This page is original writing

Report a content problem

The statistics on this site are written by AI and reviewed by AI; a human only spot-checks. What you can see may be what we cannot.

The more specific, the more fixable — e.g. which sentence disagrees with which textbook or paper.

Needed only if you want a reply; reports without it are still read.

Sent along with your report

These are attached automatically. You can drop any of them.