AdvancedIndependently reviewed, not yet spot-checked by a human

Marginal estimates and G-computation

A logistic model hands you a conditional OR, but the quantities you can interpret at the population level — the risk difference, the NNT — have to be computed separately. This page shows how standardisation (G-computation) works, why the conditional and marginal ORs are not supposed to agree (non-collapsibility), and why the confidence interval has to come from a bootstrap.

Two promises this page is here to keep

The logistic regression page, when it got to non-collapsibility, said that “if you want an effect measure that can be interpreted at the population level (to compute an NNT, say), report a marginal estimate”. Its table of common mistakes says the same thing again: taking the model’s OR and computing an NNT from it is wrong, and what you need is “standardisation / G-computation”.

Neither sentence says how. This page does.

Same MASS::birthwt, same binary outcome (birth weight under 2500 g), and deliberately the same covariates as that page, so the numbers on the two pages can be compared directly.

Why a conditional OR cannot be turned into an NNT

Exponentiating a logistic coefficient gives an OR, and specifically a conditional OR. Read out in full, it says: “holding age, mother’s weight, race, hypertension history and uterine irritability fixed, smoking multiplies the odds of low birth weight by this much.”

The trouble is the phrase “holding fixed”. The clinical question usually looks like this instead:

If nobody in this population smoked, how much less low birth weight would there be?

Nothing is held fixed in that sentence. The people in the population differ in age, in weight, in race, and the question asks what happens to the average risk across all of them. A conditional OR does not answer it; it answers what happens within one particular combination of covariate values.

Two further things put the NNT out of reach of a conditional OR:

  • An NNT is by definition the reciprocal of a risk difference, and an OR is not a risk difference and cannot be converted into one on its own — the same OR corresponds to wildly different risk differences depending on the baseline risk, which is the point of the OR-versus-RR figure on the logistic regression page.
  • The OR is non-collapsible, so even if you convert it into some “average” quantity, what comes out is still not the population-level quantity you wanted. The section on non-collapsibility deals with this on its own.

G-computation in three steps

Standardisation applied to a regression model is called G-computation, and it has only three steps:

  1. Fit a model, as usual. Here it is an ordinary logistic regression, with nothing special about it.
  2. Make two copies of the entire dataset. In the first, set everyone’s exposure to “yes”; in the second, set everyone’s exposure to “no”. Change nothing else. Every mother therefore appears once in each copy, once as a smoker and once as a non-smoker.
  3. Predict in each copy and average. The two averages are the two marginal risks; subtract them for the risk difference, divide them for the risk ratio.

Step 2 is the whole method. Leaving every other column alone means the study’s own covariate distribution is being used as the standard population, so what comes out is “this population, fully exposed versus fully unexposed” rather than some hypothetical population.

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"))

FORM <- low ~ smoke_f + age + lwt + race_f + ht + ui
fit <- glm(FORM, data = bw, family = binomial)

# Steps 2 and 3: two copies, predict in each, average
gcomp <- function(model, data) {
  d1 <- d0 <- data
  d1$smoke_f <- factor("Yes", levels = levels(data$smoke_f))
  d0$smoke_f <- factor("No",  levels = levels(data$smoke_f))
  r1 <- mean(predict(model, d1, type = "response"))
  r0 <- mean(predict(model, d0, type = "response"))
  c(risk1 = r1, risk0 = r0, RD = r1 - r0, RR = r1 / r0, NNT = 1 / (r1 - r0))
}
gcomp(fit, bw)

# The marginal OR comes from the two average risks, not from a coefficient
r <- gcomp(fit, bw)
(r["risk1"] / (1 - r["risk1"])) / (r["risk0"] / (1 - r["risk0"]))
exp(coef(fit))["smoke_fYes"]        # conditional OR -- a different number

# Confidence interval: resample individuals, rerun the whole procedure
set.seed(20260823)
boot <- replicate(2000, {
  ix <- sample(nrow(bw), replace = TRUE)
  d  <- bw[ix, ]
  gcomp(glm(FORM, data = d, family = binomial), d)
})
apply(boot, 1, quantile, c(0.025, 0.975))

Verified with R 4.6.0 and MASS 7.3.65. In practice the marginaleffects or stdReg packages do this in one line; it is written out by hand here so that every step is visible.

What comes out

QuantityEstimate95% CI (bootstrap)How to read it
Marginal risk, everyone smoking43.1%31.2–54.7%Low birth weight in this population if every mother smoked
Marginal risk, nobody smoking23.8%16.6–31.9%The same mothers if none of them smoked
Risk difference (RD)19.2 percentage points4.2–33.8The two marginal risks subtracted
Risk ratio (RR)1.8081.15–2.86The two marginal risks divided
NNT5.22.9–21.7The reciprocal of the risk difference
Marginal OR2.4191.22–4.78The two marginal risks converted to odds, then divided
Conditional OR (model coefficient)2.7941.29–6.05The odds ratio with the other variables held fixed

The NNT reads as “for every this many mothers in this population who stop smoking, one fewer low-birth-weight baby”. There is no “with the other variables held fixed” in that sentence, because G-computation never held anything fixed.

Non-collapsibility: two ORs that disagree, and neither is wrong

One model, one set of people, one contrast — and two odds ratios:

  • Conditional OR 2.794
  • Marginal OR 2.419

The gap is 0.375, a ratio of 1.155. The conditional OR sits further from 1 — and it always sits on that side.

Two panels. The left panel is a forest plot of three ratio measures on a logarithmic axis with a dashed reference line at 1: at the top the conditional OR 2.79 (1.29–6.05), in the middle the marginal OR 2.42 (1.22–4.78), and at the bottom the marginal risk ratio 1.81 (1.15–2.86). Reading down the panel, each estimate lies closer to 1 than the one above it, and all three intervals lie entirely to the right of the reference line. The right panel shows the risk difference against a reference line at 0: point estimate 19.2 percentage points with an interval from 4.2 to 33.8, lying entirely to the right of 0, with the NNT 5.2 (2.9–21.7) annotated below it.
One model and one set of people, but four different quantities. The conditional OR is furthest from 1, the marginal OR next, the marginal risk ratio closest. The two panels use different horizontal scales, so their bar lengths are not comparable.Plotting script figures/scripts/B2-08-marginal.R

Why does this happen? Because the odds ratio is a non-collapsible measure: the average of the subgroup odds ratios is not the odds ratio of the average. Non-linearity alone is not the reason — a log link is non-linear too, and its rate ratio collapses perfectly well.

Working out the odds for a group of people at different risks and then averaging those odds is simply not the same operation as averaging their risks first and converting afterwards. The predicted risks this model assigns are spread widely — from 6.9% to 90.7% under the smoking scenario, and from 2.6% to 77.7% under the non-smoking one — and the wider that spread, the further apart the two ORs sit.

Why the confidence interval has to come from a bootstrap

The marginal risk difference is a non-linear function of every coefficient and every covariate, not one coefficient. The model output contains no standard error for it, and the standard-error column in summary() is not it either.

The standard approach is a non-parametric bootstrap: resample individuals, refit the model, and run the whole G-computation again, 2000 times, then take percentiles. “Run the whole thing again” is the load-bearing part — resampling without refitting amounts to pretending the coefficients are known.

So what happens if you cut corners? Here are three ways of getting an interval for the risk difference from the same data:

Method95% CI for the risk difference (points)WidthVersus bootstrap
Non-parametric bootstrap (resample individuals, rerun everything)4.2–33.829.5
Delta method (covariate distribution treated as fixed)5.2–33.328.10.95×
Substituting only the exposure coefficient’s Wald limits4.3–35.531.21.06×

The NNT interval is lopsided — never write it as a plus-or-minus

An NNT is the reciprocal of a risk difference, and taking a reciprocal turns “close to zero” into “close to infinity”. The lower limit of the risk difference here is 4.2 percentage points, not far from zero, which pushes the upper limit of the NNT out to 21.7:

  • Point estimate 5.2
  • 95% CI 2.9 to 21.7

The lower limit sits only 2.3 below the point estimate; the upper limit sits 16.5 above it.

The predicted-probability figure is a standardised curve, not a partial plot

Two panels. The left panel is a scatter plot with mother's weight in pounds on the horizontal axis and predicted risk of low birth weight on the vertical axis: red points are the predictions with everyone set to smoking, blue points with everyone set to non-smoking. Both clouds fall as weight increases, and the red cloud sits above the blue one throughout. Two horizontal dashed lines mark the two averages, 0.431 and 0.238. The right panel shows two falling standardised risk curves with bootstrap confidence bands, the red (smoking) curve above the blue (non-smoking) one throughout: at about 90 pounds the curves are near 0.567 and 0.346, falling to about 0.171 and 0.075 at about 218 pounds. The two confidence bands overlap across the whole weight range. Short tick marks along the bottom show the observed distribution of mothers' weights.
Left: the model gives every mother her own risk, and the marginal estimate is the average of those. Right: the two risk curves once mother's weight is standardised as well, with bootstrap confidence bands shaded.Plotting script figures/scripts/B2-08-marginal.R

The right panel is easy to mistake for an ordinary partial dependence plot — “hold the other variables at their means and let weight vary”. It is not. Each point on those curves is computed by setting everyone’s weight to that one value, leaving every other column as observed, predicting, and averaging. It is G-computation applied a second time, now to mother’s weight.

The difference is not pedantry:

  • Holding covariates at their means invents an “average patient” who may not exist: average age, average weight, and a race that is some decimal fraction. The more covariates there are, and the more of them are categorical, the more absurd that fictional person becomes.
  • Standardisation invents nobody. It keeps the whole population’s heterogeneity and replaces only the one variable being varied.

The vertical distance between the two curves is the marginal risk difference at that level of weight. That distance shrinks in absolute terms as weight increases — from about 22.0 percentage points at the left-hand end to about 9.6 at the right.

Which one to report

The question you are answeringWhat to report
Two patients alike in every other respect, one exposed and one notConditional estimate (model coefficient)
This whole population fully exposed versus fully unexposedMarginal estimate (G-computation)
Computing an NNT, estimating policy impact, talking to a decision makerMarginal risk difference
Comparing your effect size with other papersBoth, each labelled for what it is

Common mistakes

MistakeWhy it is wrong
Computing an NNT straight from a conditional ORAn NNT is the reciprocal of a risk difference; an OR is neither one nor convertible into one
Reading the conditional-versus-marginal OR gap as residual confoundingIt is the algebra of non-collapsibility, and happens with no confounding at all
Saying “the two ORs disagree, so one must be wrong”Both are right; they answer different questions
Using a coefficient’s standard error for the marginal risk differenceThe marginal estimate is a non-linear function of all the coefficients and covariates
Bootstrapping by resampling without refitting the modelThat is a broken bootstrap, not either shortcut in the table above; it treats the coefficients as known and understates uncertainty
Writing an NNT as “estimate ± something”Its sampling distribution is badly asymmetric
Reporting an NNT interval when the risk difference interval crosses zeroThat interval is discontinuous, and printing it as one range misleads
Describing a standardised curve as “other variables held at their means”That is a partial plot, and it invents an average patient who does not exist
Judging a difference by whether two confidence bands overlapLook at the interval for the difference; band overlap is far more conservative
Assuming G-computation makes the conclusion causalThe arithmetic does nothing about unmeasured confounding; a causal reading needs separate assumptions

Further reading

Reproducing every number on this page

/opt/homebrew/bin/Rscript figures/scripts/B2-08-marginal.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.

Same model, same patients: the conditional odds ratio printed by the model is 2.79 and the G-computation marginal odds ratio is something else. Why do they differ?

Show the answer and why

Correct answer: The marginal one is 2.42 - odds ratios are not collapsible, so the two were never going to agree

The marginal OR is 2.42 against a conditional 2.79, while 2.02 is the crude unadjusted figure - marginal is not the same as crude, it is adjusted and then averaged over the population. The gap is neither an arithmetic slip nor residual confounding: odds ratios are not collapsible, so averaging one over a covariate changes it even when that covariate is unrelated to the exposure. This is peculiar to the odds ratio; risk ratios and risk differences do not behave this way.

What does the marginal risk difference from G-computation describe?

Show the answer and why

Correct answer: 0.192 - the gap between average risk if everyone smoked and if nobody did

0.192 comes from duplicating every row, forcing smoking on in one copy and off in the other, predicting each person's risk under both, averaging each, and subtracting - so it describes the whole population rather than one particular covariate profile. 0.153 is the unadjusted crude risk difference; 0.238 is the average risk under one of the two scenarios rather than the difference. The distinction matters when writing conclusions: a marginal estimate answers a policy question about this population, not a question about an individual patient.

One risk difference, three ways of building a confidence interval, three widths. Which admits the most uncertainty?

Show the answer and why

Correct answer: The bootstrap percentile interval, width 0.295 - resampling lets the covariate distribution and every coefficient move together

The bootstrap's 0.295 re-runs the whole estimation, letting the covariate distribution and all the coefficients vary, which is the fullest account of the uncertainty. The delta method's 0.281 treats the covariate distribution as fixed and so leaves a piece out. 0.312 is indeed the widest, but wide is not the same as complete: it moves the smoking coefficient alone between its Wald limits while pinning every other coefficient - over-conservative along one axis and blind along the others. All three are offered as 95% intervals for the same estimate, and in output they look equally respectable.

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.