Variable selection for prediction models
Picking predictors for a prediction model has nothing to do with causal structure — the only question is whether a variable makes the prediction better. Three common strategies are run on one breast cancer cohort; this dataset cannot tell their external performance apart (the three point estimates differ in the third decimal place, and no uncertainty is estimated here). The difference that does matter lies elsewhere - the same algorithm, on resamples of the same patients, hands back dozens of different "final models". A closing section covers how to read a machine-learning prediction paper - what variable importance and SHAP do and do not say, and why not one item on the acceptance list changes.
Change the question and the whole rulebook changes
Every regression in the B2 and B6 families is answering an aetiological question: does this exposure have an effect on this outcome. In that kind of model, “should this variable go in” is a question about causal structure — a confounder must go in, a mediator must stay out, and a collider creates bias if you include it. The criteria live in the cohort chapter and on the DAG page.
A prediction model asks something else entirely: what is this person’s risk? The same decision now runs on completely different criteria:
| Aetiological model | Prediction model | |
|---|---|---|
| Should this variable go in | Depends on where it sits in the causal diagram | Depends on whether it makes the prediction more accurate |
| Mediators | Including one blocks part of the effect you are estimating | Allowed, as long as it is available at deployment |
| Collinearity | The target coefficient becomes unstable, so it is a problem | Not a problem, as long as the predictions are stable |
| How to read the coefficients | Effect sizes, expected to carry a causal reading | Do not read them; they are only weights |
| What counts as success | Unbiased estimation, assumptions satisfied | How it performs on people it has never seen |
Three strategies, one dataset
survival::rotterdam is the development cohort (2982 patients, 1713 events) and survival::gbsg is the external validation set (686 patients). There are 8 candidate variables: age, menopausal status, tumour size, grade, node count, PgR, ER and hormonal treatment.
The three approaches you will meet most often in published papers:
- Full model approach — specify all 8 in advance and keep every one, whatever its p-value.
- Stepwise regression — start from the full model and delete backwards using AIC.
- Univariable screening — fit each variable on its own first, and only let the ones with p < 0.05 into the multivariable model.
| Strategy | Variables kept | Development C-index | External C-index | External calibration slope |
|---|---|---|---|---|
| Full model (prespecified) | 8 | 0.670 | 0.662 | 0.723 |
| Backward stepwise (AIC) | 6 | 0.669 | 0.661 | 0.721 |
| Univariable screening (p < 0.05) | 7 | 0.669 | 0.662 | 0.722 |
Stepwise regression’s problem is not that it selects badly, it is that it selects unstably
The model diagnostics page already demonstrated one half of this with 300 simulations: throw 10 pure noise variables at stepAIC() and it keeps 1.81 of them on average, with at least one “significant” random variable surviving in 48.0% of runs. That demonstration is not repeated here.
This page asks a different question: same algorithm, same patients, one new resample — will it give you the same model?
Resample the development cohort 200 times, run the identical backward AIC on each resample, and record how often each variable survives:
figures/scripts/B5-01-variable-selection.R| Variable | Univariable p | In the final model on the full data? | Selected in resamples (all patients) | Selected in resamples (200 patients) |
|---|---|---|---|---|
| age | < 0.001 | yes | 62% | 32% |
| menopausal | < 0.001 | no | 40% | 22% |
| tumour size | < 0.001 | yes | 100% | 79% |
| grade 3 | < 0.001 | yes | 100% | 56% |
| nodes | < 0.001 | yes | 100% | 99% |
| log PgR | < 0.001 | yes | 76% | 31% |
| log ER | 0.773 | no | 27% | 32% |
| hormonal tx | < 0.001 | yes | 63% | 26% |
Across those 200 resamples, backward AIC produced 25 different models, and the most frequent one accounted for only 22% of the runs. Cut each resample to two hundred patients — much closer to the size of most single-centre studies — and it becomes 69 different models, with the most common one accounting for 12%.
Univariable screening has a fault stepwise regression does not
Univariable screening — fit each variable alone, admit only those with p < 0.05 — is even more common than stepwise regression, because it looks more conservative. It is not.
Look at menopausal status in the table above: its univariable p-value is far below 0.001, so it passes the screen and enters the multivariable model — but backward AIC deletes it. The reason is that it almost duplicates age: once you adjust for age, there is nothing left of it.
The reverse case is the dangerous one: a variable can carry no univariable signal at all and still matter after adjustment. Univariable screening throws it away before it ever gets adjusted, and no number in the final model records that this happened.
So how should you choose?
The current advice (TRIPOD+AI, the methodological literature from Riley and colleagues) comes down to one sentence: fix the candidate variables and the model form before you see any results, then put all of them in.
| Approach | When it makes sense | Watch out for |
|---|---|---|
| Prespecify from domain knowledge and keep everything (full model approach) | The default. Candidates come from existing models, guidelines, and what is actually obtainable in clinic | The number of candidates has to clear the sample size requirement first |
| Let penalised regression decide the weights | Many candidates, sample size not generous | The variables lasso drops are just as unstable — see the shrinkage page |
| Cut on availability | Always applicable, and it should be done first | This is not a statistical decision, so it introduces no optimism |
| Stepwise regression | Almost never | If you really use it, the whole selection procedure has to go inside the resampling loop — see the internal validation page |
Run it yourself
library(survival); library(MASS)
data(cancer, package = "survival")
rot <- rotterdam
rot$rfs_time <- pmin(rot$rtime, rot$dtime)
rot$rfs_event <- as.integer(rot$recur == 1 | rot$death == 1)
rot$size_mm <- c("<=20" = 15, "20-50" = 35, ">50" = 60)[as.character(rot$size)]
rot$grade3 <- as.integer(rot$grade >= 3)
rot$log_pgr <- log1p(rot$pgr); rot$log_er <- log1p(rot$er)
cand <- c("age", "meno", "size_mm", "grade3", "nodes", "log_pgr", "log_er", "hormon")
f <- as.formula(paste("Surv(rfs_time, rfs_event) ~", paste(cand, collapse = " + ")))
full <- coxph(f, data = rot) # full model approach: this one line
step <- stepAIC(full, direction = "backward", trace = 0)
# Univariable screening: note what it is asking -- association without adjustment
sapply(cand, function(v)
summary(coxph(as.formula(paste("Surv(rfs_time, rfs_event) ~", v)),
data = rot))$coefficients[1, "Pr(>|z|)"])
# Stability: resample the same data and watch what it picks each time
tally <- setNames(numeric(length(cand)), cand)
for (b in 1:200) {
d <- rot[sample(nrow(rot), replace = TRUE), ]
ff <- f; environment(ff) <- environment() # without this line the whole loop fails silently
st <- stepAIC(coxph(ff, data = d), direction = "backward", trace = 0)
tally[names(coef(st))] <- tally[names(coef(st))] + 1
}
round(100 * tally / 200)Verified with R 4.6.0 + survival 3.8.6 + MASS 7.3.65. ⚠️ stepAIC() re-evaluates the model call in the frame that called it, so the data object inside a loop has to be visible to the formula; the line environment(ff) <- environment() in the figure script is what handles this, and without it every resample fails silently.
import numpy as np
import pandas as pd
from lifelines import CoxPHFitter
RD = "https://vincentarelbundock.github.io/Rdatasets/csv/"
d = pd.read_csv(RD + "survival/rotterdam.csv")
# The same derived columns as the R code above. rotterdam ships none of
# them, and without this every line below is a KeyError.
d["rfs_time"] = d[["rtime", "dtime"]].min(axis=1)
d["rfs_event"] = ((d["recur"] == 1) | (d["death"] == 1)).astype(int)
d["size_mm"] = d["size"].map({"<=20": 15, "20-50": 35, ">50": 60})
d["grade3"] = (d["grade"] >= 3).astype(int)
d["log_pgr"] = np.log1p(d["pgr"]); d["log_er"] = np.log1p(d["er"])
cand = ["age", "meno", "size_mm", "grade3", "nodes", "log_pgr", "log_er", "hormon"]
cols = cand + ["rfs_time", "rfs_event"]
# Full model approach
full = CoxPHFitter().fit(d[cols], "rfs_time", "rfs_event")
print(full.concordance_index_)
# The penalised version (the subject of B5-02) -- not a substitute for variable
# selection but an alternative to it
pen = CoxPHFitter(penalizer=0.1, l1_ratio=1.0).fit(d[cols], "rfs_time", "rfs_event")
print(pen.params_[pen.params_ != 0].index.tolist())No stepwise implementation is offered on the Python side, deliberately: statsmodels has no built-in stepwise selection, and that is a good thing. lifelines' CoxPHFitter fits Cox models, and its penalizer argument gives you the penalised version.
When the paper uses a machine-learning model
“XGBoost predicts AKI”, “random forest predicts sepsis” — papers like these are now common in clinical informatics and critical care, and a medical student usually meets one for the first time at a journal club. This section teaches how to read them, not how to fit them. Swapping the model for a forest or a boosted ensemble is a separate skill tree, but not one item on the acceptance list changes.
In order: variable importance, impurity bias, SHAP, and the one thing that has not changed.
One, variable importance is not a regression coefficient. It has no direction, no confidence interval, and it is not an effect size. A variable ranked first only means it came first under whatever importance definition the package used — permutation importance means that shuffling it degrades prediction the most, while the default in tree packages is often the impurity-based version of point two, and the two can rank the same variables differently. Either way it does not tell you whether risk goes up or down, or by how much, and it cannot be cited as “this factor raises risk by so much”. When a Discussion turns an importance ranking into a list of risk factors, it is treating two entirely different quantities as one.
Two, impurity-based importance is systematically biased towards high-cardinality variables. A tree picks a split at every node, and a variable with more distinct values offers more places to split, so it more easily finds a cut that looks useful on noise alone. Continuous variables and many-levelled categorical ones (admitting specialty, drug codes) therefore rise systematically, and binary variables sink systematically — neither of which has anything to do with whether they are actually useful. Permutation importance, or recomputing on held-out data, reduces this but does not remove it.
Three, SHAP is local attribution, not a causal effect. It answers “for this patient, how far did this variable push the model’s prediction away from the baseline” — a decomposition that takes the fitted model as given. If the association the model learned came from confounding, selection or reverse causation, SHAP will faithfully attribute that association, and will draw it very persuasively. A beautiful SHAP plot is not causal evidence; it is the model describing itself.
Four, the acceptance criteria are unchanged. Discrimination, calibration, the decision curve, and external validation — every one of them, none optional. And the one machine-learning papers most often skip is calibration: tree ensembles shrink their predictions towards the middle by construction, a calibration slope above 1 is a common finding, and a paper that reports only the AUC will never show you that.
Four questions to ask when reading a paper
- Were the candidate variables prespecified, or chosen from the data? If the Methods do not say “prespecified”, assume they came from the data.
- Was anything revalidated after selection? Selecting variables spends degrees of freedom; the p-values, confidence intervals and C-index computed afterwards are all optimistic.
- Will every variable in the model actually be available at deployment? This is the one place a reader can catch data leakage.
- Are the discarded variables reported? A paper that lists only the final model leaves the reader no way to judge how marginal the deletions were.
Common misuses
| Misuse | Why it is wrong |
|---|---|
| Selecting with stepwise regression, then reporting p-values and CIs as usual | Selection is itself multiple comparison; one new resample gives a different variable set |
| Saying “the variables in the final model are independent risk factors” | They are predictive weights, not causal effects — and they change with the subsample |
| Dropping a variable because its univariable p-value is not significant | Some variables only contribute after adjustment, and the loss leaves no trace in any number |
| Treating prediction model selection like aetiological selection | The criteria are entirely different: causal position versus predictive contribution |
| Deleting variables out of concern about collinearity | A prediction model only needs stable predictions; collinearity is not a problem |
| Including a variable that will not be available at deployment | Data leakage; every statistical check passes and only deployment reveals it |
| Screening candidates without checking them against the sample size | Backwards order — clear the sample size requirement first |
| Justifying deletions on the grounds that “the model is more parsimonious” | Parsimony is not the acceptance criterion; predictive performance is |
Reproducing every number on this page
/opt/homebrew/bin/Rscript figures/scripts/B5-01-variable-selection.RRead the figure
The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.
The three variable-selection strategies give calibration slopes of 0.723, 0.721 and 0.722 in the same external cohort. How should that set of numbers be read?
Show the answer and why
Correct answer: All three sit well below 1 and differ only in the third decimal, so 0.723 says the same thing the other two do: the model stretches the external cohort's risk differences too far apart
A calibration slope below 1 means the predicted risk differences are too extreme - high-risk people pushed too high, low-risk people pressed too low. That is not the same as overestimating overall: an overall shift lives in the intercept, and moving every prediction down changes the mean without touching the slope. As for which of 0.721, 0.722 and 0.723 is larger, each is a single point estimate computed once in the same external cohort, and this page does not estimate their uncertainty, so a ranking in the third decimal cannot choose a strategy. What the three agree on is a calibration problem the three strategies share, not evidence that one beats another.
Age has a univariable p value far below one in a thousand, yet backward AIC does not keep it in every resample. What do those two facts together tell you?
Show the answer and why
Correct answer: Age is kept in 62.0 percent of the resamples; an unadjusted p value and survival through AIC ask different questions, and the two can disagree
The contrast between 62.0 and 100.0 is the point: tumour size survives every single resample, age barely six times in ten. Age has a strong univariable association, but it overlaps with menopausal status and others, and what is left after adjustment is not enough to make AIC pay for the parameter every time. Reading the gap as too few resamples does not hold either - tumour size reaches 100.0 in the very same resamples, and noise does not single out age. Menopausal status at 40.0 is indeed less stable still, but that does not make age stable; all a reader sees in the paper is which variables entered the final model, never how narrowly each of them did.
The development cohort is resampled 200 times and the identical backward AIC run on each resample, recording the final model every time. What is the important output of that experiment?
Show the answer and why
Correct answer: The same people and the same code, differing only in the resample, produced 25 distinct final models - that sentence describes one draw, not a finding
The full-sample version produced 25 distinct models, not one, so the claim that it converges on a single model is refuted by the same output; the two-hundred-per-draw version is worse still at 69. Nor does resampling confirm the model fitted to the complete data: it asks what the same algorithm picks when it meets another batch of people from the same source, and the answer is usually something else. What inherits that instability is every later sentence that presupposes membership of the final model - this is an independent risk factor, this retains predictive value after adjustment.
When each resample is cut from the whole cohort down to two hundred people, most variables are selected less often while nodes barely moves. What does that mean?
Show the answer and why
Correct answer: Nodes still reaches 99.0 percent in the small samples, a signal strong enough to survive the shrinkage; the decline elsewhere says sample size also decides which variables reach the final model
99.0 and 100.0 are all but identical, while grade drops from 100.0 to 55.5 - same algorithm, subsets of the same people, differing only in how many are seen each time. Selection frequency measures whether a variable's signal is strong enough to clear the AIC threshold reliably, which is decided jointly by signal strength and sample size, not by how much the variable matters clinically. So reading 55.5 as grade mattering less in a smaller population restates a power problem as biology; and pointing at the two near-ceiling figures for nodes to conclude that sample size is irrelevant picks the one variable that is unaffected to speak for the whole table.
Menopausal status has a univariable p value far below one in a thousand, so it passes univariable screening; backward AIC then drops it from the model. What is the sensible reading?
Show the answer and why
Correct answer: Menopausal status is kept in only 40.0 percent of the resamples; it overlaps almost entirely with age, so once age is adjusted for little independent contribution is left
40.0 does not contradict the univariable p value: univariable analysis looks at menopausal status against the outcome on its own, and age and menopausal status measure very nearly the same thing, so once both are in the model there is little extra for menopausal status to explain. Age at 62.0 is higher, but that is not a verdict on which one is the genuine risk factor - selection frequency is statistical stability, not causal standing, and this page's model was never fitted to estimate effects. log ER at 26.5 is indeed lower, but it is dropped for a different reason entirely: it has no univariable association at all. And AIC does not delete variables in order of selection frequency; at every step it asks whether the parameter buys enough improvement in the likelihood to be worth its cost.
The three strategies retain different numbers of variables in the complete development data, yet their external C-index values differ in the third decimal place. What does that comparison support?
Show the answer and why
Correct answer: Backward AIC keeps 6 variables without external performance dropping, which says that when events are plentiful relative to parameters the deleted variables were contributing little
This development cohort gives every parameter a couple of hundred events, and under those conditions stepwise deletion usually removes variables that were contributing little, so external performance shows no difference - move to data where events are tight and the same deletions start removing things that matter. The second option reads a failure to detect a difference this once as prespecification being unnecessary, but prespecification is not guarding this C-index; it is guarding against the problem in the section above, where the same algorithm returns a different set of variables on the next draw. The third is more direct: a variable count in the middle is not an acceptance criterion of any kind. Predictive performance is, and predictive performance cannot separate these three here.
Chapters that use this method
Watch next
What Are Clinical Prediction Rules?
AI 臨床研究實戰 EP7|預測 vs 分類、Data Leakage、Propensity Score
Key Steps and Common Pitfalls in Clinical Prediction Model ResearchSources and licences
This page is original writing