Confounding variables and how adjustment works
What a confounding variable is, how adjusting for one changes an odds ratio, and which variables to leave out of the model, worked on the birthwt data.
Updated . Written by Matt Gencer.
On this page
A confounding variable is a third variable that distorts the comparison you care about: it is tied to both the exposure and the outcome, it is not a consequence of the exposure, and so a raw comparison of exposed and unexposed groups also carries its effect. Adjusting for it in a regression model asks what the exposure-outcome association would look like among people who share the same value of that variable.
This post defines the idea, shows how adjustment works on one dataset, and spends as much time on the variables you should not adjust for as on the ones you should. The worked example is smoking during pregnancy and low birth weight in the birthwt data from the MASS package, fitted three ways in R. For reading odds ratios in general, see the logistic regression guide; for the wider choice of regression models, start from the regression analysis overview.
What a confounding variable is
Three conditions are usually given for a confounder of an exposure E and an outcome Y. It is associated with E. It is associated with Y among people who are not exposed, that is, it is a cause of Y, or a proxy for one, by a route of its own. And it is not on the causal path from E to Y: it is not something E causes on the way to Y. VanderWeele and Shpitser (2013) give a more careful definition in terms of a causal model, and the point of their paper is that the checklist above is a guide, not a complete definition.
Two things follow that surprise people. First, confounding is a property of the question and the causal structure, not of the numbers. Two analysts with the same table can reasonably disagree about which variables are confounders, because they hold different views about what causes what. Second, there is no test on the data that says “this variable is a confounder”. Statistical output can show that a variable is associated with the exposure, or that adding it changes the estimate. It cannot show the direction of cause, and direction is what separates a confounder from a mediator.
Terms get tangled here, so it helps to separate four roles a third variable can play.
| Role | Where it sits | Adjust for it? |
|---|---|---|
| Confounder | A common cause of the exposure and the outcome | Yes, to remove the bias it creates |
| Mediator | Caused by the exposure, and causes the outcome | Not if you want the total effect |
| Collider | Caused by both the exposure and the outcome (or their causes) | No, it can create bias |
| Precision variable | Affects the outcome only, unrelated to the exposure | Optional; in a linear model it tightens the interval, in a logistic model it moves the odds ratio away from 1 |
Causal diagrams, drawn as arrows between variables, make these roles explicit and show which sets of variables are enough to adjust for. Greenland, Pearl and Robins (1999) is the standard introduction. You do not need to draw one for every analysis, but you do need to be able to say, for each variable you add, which of these roles you believe it plays and why.
Effect modification is a different thing again: the exposure-outcome association differs across levels of a third variable, say a larger odds ratio in one group than another. It is a finding to report, usually as estimates within each level, not a bias to remove, and whether it appears can depend on the scale of measurement, ratio or difference (VanderWeele and Knol 2014).
How adjustment works
Regression is one of several ways to control a confounder. At the design stage, randomly assigning the exposure leaves groups that do not differ systematically in anything, measured or not (Altman and Bland 1999); restriction admits only one level of the confounder, such as mothers in one age band; matching makes exposed and unexposed groups alike on it. At the analysis stage, stratification estimates the association within each level of the confounder and combines the estimates as a weighted average; regression adjustment handles several confounders at once (Vandenbroucke and colleagues 2007).
In a regression model, adding a variable to the right-hand side compares exposed and unexposed people who have the same value of that variable. In a logistic model, the exposure coefficient becomes the log odds ratio for the exposure at fixed values of the other predictors, and exponentiating it gives an adjusted odds ratio. The version without the other predictors is the crude odds ratio.
If a variable really is a confounder, the crude and adjusted estimates differ, and the adjusted one is the better answer to the causal question, provided the adjustment set is right and the model form is reasonable. That is the textbook case. The reasons it is more complicated in practice are worth knowing before you read the example.
A changed estimate is not proof of confounding. The odds ratio is non-collapsible: a variable that predicts the outcome but is unrelated to the exposure, and so is not a confounder at all, still moves the exposure odds ratio away from 1 when added to a logistic model, because the crude odds ratio is not a weighted average of the odds ratios within levels of that variable. Greenland, Robins and Pearl (1999) work through this. For the same reason the shift between a crude and an adjusted odds ratio mixes confounding with this arithmetic effect.
Selecting confounders by the change in the estimate is a heuristic. A common rule keeps a variable if adding it changes the exposure estimate by more than some threshold such as 10 per cent. Mickey and Greenland (1989) studied selection criteria of this kind and found that selecting by p-values at the conventional 0.05 cutoff can drop real confounders unless the cutoff is raised to 0.20 or more, which is the argument for looking at the change in the estimate instead. It is still a data-driven rule, and it should come after the causal reasoning, not instead of it. Hernán and colleagues (2002) show with birth-defects data that statistical criteria of this kind can select a variable whose adjustment adds bias rather than removing it.
Adjustment only removes measured confounding. A variable that was not recorded cannot be adjusted for, and observational estimates stay exposed to it however long the covariate list is. A measured confounder can still leave residual confounding when it is recorded with error or grouped into coarse categories (Vandenbroucke and colleagues 2007), and a larger sample does not help: it narrows the interval around the same biased estimate. The E-value (VanderWeele and Ding 2017) states the minimum strength of association, on the risk ratio scale, that an unmeasured confounder would need with both exposure and outcome, beyond the measured covariates, to explain an estimate away. With a common outcome, as here, the odds ratio must first be converted to an approximate risk ratio.
Worked example: smoking and low birth weight
The birthwt data hold 189 births recorded at one US hospital in 1986, one row per birth. The outcome is a binary indicator of birth weight under 2,500 grams. The exposure is whether the mother smoked during pregnancy. The question is whether smoking is associated with the odds of low birth weight, and how that association changes when we adjust for the mother’s age, her weight at her last menstrual period and her race. Race is stored as the integers 1, 2 and 3 in the original data; before the run it was recoded to the labels in the MASS documentation (white, black, other) so that it enters as a category. The reference level is black, the first label alphabetically.
Each row is one birth with no identifier that links rows, so the births are treated as independent. The outcome is binary and nothing is censored. The engine used a logistic regression for all three fits, modelling the odds of low birth weight, and no rows were dropped.
The crude odds ratio
Fitting low birth weight on smoking alone gives the crude odds ratio: 2.022, with a 95% interval of 1.082 to 3.801, z = 2.203 and p = 0.0276. On the log-odds scale the coefficient is 0.704. The interval excludes 1, but it is wide: compatible with anything from a slightly raised to nearly fourfold odds.
The adjusted odds ratio
Adding age, weight and race gives an adjusted odds ratio of 2.87, with a 95% profile-likelihood interval of 1.382 to 6.186, z = 2.775 and p = 0.00552. The coefficient on the log-odds scale is 1.054. Adjustment moved the estimate away from 1, from 2.022 to 2.87, and made the interval wider, which is usual in a logistic model: adding predictors of the outcome widens the exposure interval, unlike in linear regression.
A reader expecting adjustment to “shrink” an association will be surprised by the direction. Confounding can push an estimate either way. Here the covariates with the clearest coefficients in the adjusted model were weight at the last menstrual period, with a coefficient of -0.0125 per pound (p = 0.0498), and the white category relative to black, with a coefficient of -1.232 (p = 0.0172). Age did little: its coefficient was -0.0225 per year, p = 0.511. A confounder must also be related to the exposure, and this output does not show that relationship, which needs its own look at how smoking is distributed across race and weight in the data. So the output tells you the estimate moved and which covariates carry weight in the outcome model. It does not, alone, tell you which of them is the reason it moved, and part of the shift may be the non-collapsibility described above.
Adding a variable that may sit on the path
The data also record uterine irritability (ui, present or absent). Smoking could plausibly affect it, and it could affect birth weight, which would make it a mediator rather than a confounder. That is a causal assumption, and it is the one this example needs to make visible, so treat it as an illustration. Adding it to the adjusted model gives an odds ratio for smoking of 2.781, with an interval of 1.329 to 6.029, z = 2.667 and p = 0.00765.
The estimate barely moved here, from 2.87 to 2.781, and a reader could conclude that adjusting for it did no harm. It is the wrong conclusion. A small change in one dataset says nothing about whether the variable belongs in the model, and if it is a mediator the new estimate answers a different question: the association of smoking with low birth weight among women with the same uterine-irritability status, which leaves out whatever smoking does through that route. The fit also now estimates six coefficients besides the intercept from 59 events, which is 9.833 events per parameter, below the rule-of-thumb floor of 10 that Peduzzi and colleagues (1996) examined; the engine flags it as a warning. The adjusted model before this addition had 11.8.
The coefficient on ui was 0.7669 with p = 0.0815, so the data are compatible with no association with the outcome in this model. That is neither a reason to keep the variable nor a reason to drop it: the decision rests on the causal question, not on its p-value.
The R code
The three fits are ordinary glm calls. The reference level for race is set to black to match the engine run.
data(birthwt, package = "MASS")
birthwt$race <- factor(birthwt$race, levels = 1:3, labels = c("white", "black", "other"))
birthwt$race <- relevel(birthwt$race, ref = "black")
crude <- glm(low ~ smoke, family = binomial, data = birthwt)
adjusted <- glm(low ~ smoke + age + lwt + race, family = binomial, data = birthwt)
extended <- glm(low ~ smoke + age + lwt + race + ui, family = binomial, data = birthwt)
# Odds ratios with profile-likelihood intervals
sapply(list(crude = crude, adjusted = adjusted, extended = extended), function(m) {
c(OR = exp(coef(m)["smoke"]), exp(confint(m, "smoke")))
})
The script beside the example reproduces the adjusted model’s coefficients, standard errors, z statistics and Wald p-values from the bundled copy of the data. The profile-likelihood intervals and the engine’s checks are engine output, which the script does not recompute.
Variables you should not adjust for
The belief that more adjustment is always safer is the most common mistake in this area. Three kinds of variable do damage.
Mediators. If the exposure causes the variable and the variable causes the outcome, adjusting for it blocks part of the effect you set out to measure. It is a legitimate step when you want the direct effect, the part of the effect that does not go through the mediator, but then the question has changed and the interpretation needs more assumptions than the total effect does. Schisterman, Cole and Platt (2009) call this overadjustment and discuss its cost.
Colliders. A collider is caused by two other variables. Adjusting for it, or restricting the data to one of its values, opens a path between its causes that was closed before and can create an association that does not exist. Cole and colleagues (2010) illustrate the bias with worked examples. The practical warning sign is a variable that is influenced by both the exposure and the outcome, such as a hospital admission or a measurement taken after both have happened.
Variables recorded after the exposure that you cannot place. If you do not know whether a variable came before or after the exposure, you do not know whether it is a confounder or a mediator. Timing is the cheapest evidence you have; use the order in which things were measured and the biology of the situation.
There are also unnecessary adjustments: variables related to neither the exposure nor the outcome. They cost degrees of freedom and, in a logistic model, events per parameter, without removing any bias.
What to report
State the exposure, the outcome, and the model. Name the adjustment set, say which variables were chosen before looking at the data and why, and report the crude and the adjusted estimate side by side, each with its interval. Say what the variables you left out were and whether you believe any is a mediator or collider. If you tried more than one adjustment set, report all of them rather than the one with the neat result; the sets in this example are a sensitivity analysis, not a search. The STROBE guideline for observational studies asks for the same: unadjusted and adjusted estimates with their precision, and which confounders were adjusted for and why (Vandenbroucke and colleagues 2007).
For the interpretation, an adjusted odds ratio is an association at fixed values of the covariates. Whether it estimates a causal effect depends on assumptions about the causal structure that the data cannot confirm. This is research on groups, not an assessment of any individual pregnancy. The odds ratio vs relative risk guide covers why an odds ratio should not be read as a risk ratio when the outcome is common, and the ANCOVA guide covers the continuous-outcome counterpart of adjusting a group comparison for a covariate. Once the adjustment set is chosen, check the fitted model with the assumptions in logistic regression assumptions, real and invented, and see the logistic regression method reference for what the engine reports for this model.
Worked example
Data: Hosmer DW, Lemeshow S (1989). Applied Logistic Regression. New York: Wiley. Data collected at Baystate Medical Center, Springfield, Massachusetts, 1986; distributed as birthwt in the MASS R package (Venables and Ripley 2002). https://cran.r-project.org/package=MASS. Seed: 20261022.
Run on Celeus engine 0.31.1, seed 20261022, public data.
Output
# Results - Test: Logistic regression - n: 189 - statistic: 2.775 - p-value: 0.00552 - effect size: odds ratio = 2.87 - 95% CI: [0.3238, 1.822] - seed: 20261022 ## Assumption checks - sample size: passed=TRUE (adequate n) - events per variable: passed=TRUE (at or above the rule-of-thumb floor (a convention, not a guarantee of a stable model)) - calibration (Hosmer-Lemeshow, 10 groups): passed=TRUE (no evidence of poor calibration (observed event rates track the predicted risks) - caution: at least one risk group expects fewer than 5 events or fewer than 5 non-events, where the chi-squared approximation behind this p-value is unreliable) ## Power - Power is not calculated for this test.
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Logistic regression | engine 0.31.1 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261022)
.data_path <- "data/confounding-variables.csv"
if (!file.exists(.data_path)) stop("Run reproduce.R from the package folder (the bundled data is in data/).")
data <- readr::read_csv(.data_path, show_col_types = FALSE) # needs the readr package
clean_cells <- function (df)
{
ctrl_collapse <- "[\r\n\t\v\f]+"
zero_width_chars <- intToUtf8(c(8203L, 65279L), multiple = TRUE)
uni_space_chars <- intToUtf8(c(160L, 8239L, 8199L), multiple = TRUE)
clean_text <- function(x) {
out <- x
for (ch in zero_width_chars) out <- gsub(ch, "", out,
fixed = TRUE)
out <- gsub(ctrl_collapse, " ", out)
for (ch in uni_space_chars) out <- gsub(ch, " ", out,
fixed = TRUE)
trimws(gsub(" +", " ", out))
}
changed_cols <- character(0)
cells_cleaned <- 0L
retyped <- character(0)
for (j in seq_along(df)) {
col <- df[[j]]
if (!is.character(col))
next
cleaned <- clean_text(col)
n_changed <- sum(!is.na(col) & !is.na(cleaned) & col !=
cleaned, na.rm = TRUE)
if (n_changed > 0L) {
changed_cols <- c(changed_cols, names(df)[j])
cells_cleaned <- cells_cleaned + n_changed
}
df[[j]] <- cleaned
non_blank <- cleaned[!is.na(cleaned) & nzchar(cleaned)]
if (length(non_blank) && all(grepl("^[+-]?(0|[1-9][0-9]*)([.][0-9]+)?$",
non_blank))) {
parsed <- suppressWarnings(as.numeric(cleaned))
was_blank <- is.na(cleaned) | !nzchar(cleaned)
lost <- (is.na(parsed) | is.infinite(parsed)) & !was_blank
if (!any(lost)) {
finite <- parsed[!is.na(parsed)]
all_whole <- all(finite == floor(finite))
in_range <- all(abs(finite) <= .Machine$integer.max)
df[[j]] <- if (all_whole && in_range)
as.integer(parsed)
else parsed
retyped <- c(retyped, names(df)[j])
}
}
}
names(df) <- clean_text(names(df))
if (length(changed_cols)) {
attr(df, "ingest_clean_columns") <- changed_cols
attr(df, "ingest_clean_cells_cleaned") <- cells_cleaned
}
if (length(retyped))
attr(df, "ingest_clean_retyped") <- retyped
df
}
data <- clean_cells(data)
# Complete-case analysis: drop rows missing any analysis value.
.acols <- c("low", "smoke", "age", "lwt", "race")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- summary(glm(low ~ smoke + age + lwt + race, data = data, family = binomial()))
print(result)Common questions
What is a confounding variable?
A confounding variable is a third variable that is related to both the exposure and the outcome, is not a consequence of the exposure, and so mixes its own effect into the exposure-outcome comparison. Left out of the analysis, it makes the raw comparison differ from the comparison you meant to make.
How do you control for a confounding variable?
Decide which variables are confounders before fitting, from subject knowledge and a causal diagram. Then include them in the regression model, stratify on them, or match on them in the design. Randomising the exposure is the one method that also handles confounders nobody measured.
Is a confounder the same as a mediator?
No. A confounder sits upstream of the exposure and the outcome, so adjusting for it removes a source of bias. A mediator sits between the exposure and the outcome, so adjusting for it removes part of the effect you are trying to estimate. Statistics cannot tell them apart; the causal story does.
Does adjusting for more variables always give a better estimate?
No. Adding a mediator removes part of the effect, adding a collider can create an association that was not there, and adding many variables to a small data set leaves too few events per parameter. Adjust for the variables your causal reasoning names, not for everything recorded.
How do I know if a variable confounds my result?
No test on the data alone settles it. A variable that changes the estimate when added is a candidate, not a confirmation, because odds ratios can move for reasons other than confounding. Check that it plausibly causes the outcome and is linked to the exposure but not caused by it.
Sources
- Greenland S, Pearl J, Robins JM (1999). Causal diagrams for epidemiologic research. Epidemiology 10(1):37-48. doi:10.1097/00001648-199901000-00008
- Greenland S, Robins JM, Pearl J (1999). Confounding and collapsibility in causal inference. Statistical Science 14(1):29-46. doi:10.1214/ss/1009211805
- VanderWeele TJ, Shpitser I (2013). On the definition of a confounder. Annals of Statistics 41(1):196-220. doi:10.1214/12-AOS1058
- Schisterman EF, Cole SR, Platt RW (2009). Overadjustment bias and unnecessary adjustment in epidemiologic studies. Epidemiology 20(4):488-495. doi:10.1097/EDE.0b013e3181a819a1
- Cole SR, Platt RW, Schisterman EF, Chu H, Westreich D, Richardson D, Poole C (2010). Illustrating bias due to conditioning on a collider. International Journal of Epidemiology 39(2):417-420. doi:10.1093/ije/dyp334
- Mickey RM, Greenland S (1989). The impact of confounder selection criteria on effect estimation. American Journal of Epidemiology 129(1):125-137. doi:10.1093/oxfordjournals.aje.a115101
- VanderWeele TJ, Knol MJ (2014). A tutorial on interaction. Epidemiologic Methods 3(1):33-72. doi:10.1515/em-2013-0005
- Altman DG, Bland JM (1999). Treatment allocation in controlled trials: why randomise? BMJ 318(7192):1209. doi:10.1136/bmj.318.7192.1209
- Vandenbroucke JP, von Elm E, Altman DG, Gotzsche PC, Mulrow CD, Pocock SJ, Poole C, Schlesselman JJ, Egger M (2007). Strengthening the Reporting of Observational Studies in Epidemiology (STROBE): explanation and elaboration. PLoS Medicine 4(10):e297. doi:10.1371/journal.pmed.0040297
- Hernan MA, Hernandez-Diaz S, Werler MM, Mitchell AA (2002). Causal knowledge as a prerequisite for confounding evaluation: an application to birth defects epidemiology. American Journal of Epidemiology 155(2):176-184. doi:10.1093/aje/155.2.176
- VanderWeele TJ, Ding P (2017). Sensitivity analysis in observational research: introducing the E-value. Annals of Internal Medicine 167(4):268-274. doi:10.7326/M16-2607
- Peduzzi P, Concato J, Kemper E, Holford TR, Feinstein AR (1996). A simulation study of the number of events per variable in logistic regression analysis. Journal of Clinical Epidemiology 49(12):1373-1379. doi:10.1016/S0895-4356(96)00236-3
Methods discussed
Try it on your own data
Upload a dataset and Celeus suggests a method, checks its assumptions, runs it in R, and gives you the script to rerun it.
Try it in Celeus