AdvancedIndependently reviewed, not yet spot-checked by a human

Adjusted risk ratios: log-binomial and modified Poisson

When the outcome is common an odds ratio overstates the effect, but log-binomial regression — the obvious way to estimate an adjusted risk ratio — very often refuses to run. This page walks the sequence in the order it actually happens: it converges, it fails, it converges with starting values, and then modified Poisson does the job; plus what the robust standard error is really fixing here.

Why this page exists

The logistic regression page concluded that cohort studies and trials have denominators, so a risk ratio can be computed directly, and reporting an OR instead means taking on an extra assumption. It also mentioned, in passing, that an adjusted RR can be had from log-binomial regression or from Poisson regression with robust standard errors.

That sounds like changing one family argument. It is not — log-binomial very often refuses to run, and the way it fails is easy to mistake for your own error the first time you meet it.

So this page does not follow the textbook order (principle, then method, then a footnote saying it sometimes fails to converge). It follows the order you will meet on your own machine: something works, then something breaks, then it is fixed, then a different route is taken. The failure in the middle is one of the points of the page, not an aside.

The example on this page

The same MASS::birthwt: 189 mothers, 59 low-birth-weight babies, a prevalence of 31.2%. Among smokers 30 of 74; among non-smokers 29 of 115 — so the risk in the unexposed group is 25.2%, a number the whole page keeps coming back to.

The adjustment set is age, mother’s weight and race, overlapping with the logistic regression page so the estimates can be compared side by side.

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"))
ADJ <- low ~ smoke_f + age + lwt + race_f

# Step 1: univariable log-binomial, converges straight away
fit_uni <- glm(low ~ smoke_f, data = bw, family = binomial(link = "log"))
exp(coef(fit_uni))["smoke_fYes"]

# Step 2: add covariates and the default starting values fail
# try() is only here so the steps below can run; called directly this
# aborts, which is the point of this step.
try(glm(ADJ, data = bw, family = binomial(link = "log")))

# Step 3: supply starting values. log(0.3) says "baseline risk near 30%",
# every slope starts at 0
fit_lb <- glm(ADJ, data = bw, family = binomial(link = "log"),
              start = c(log(0.3), rep(0, 5)))
exp(cbind(RR = coef(fit_lb), confint.default(fit_lb)))
max(fitted(fit_lb))                 # after convergence, is anything pinned near 1?

# Step 4: modified Poisson. The sandwich estimator, written out (HC0)
fit_p <- glm(ADJ, data = bw, family = poisson)
robust_vcov <- function(model) {
  X <- model.matrix(model)
  r <- model$y - fitted(model)
  bread <- solve(crossprod(X, X * model$weights))   # (X'WX)^-1
  bread %*% crossprod(X, X * r^2) %*% bread         # the meat, in between
}
se <- sqrt(diag(robust_vcov(fit_p)))
cbind(RR = exp(coef(fit_p)),
      lcl = exp(coef(fit_p) - 1.96 * se),
      ucl = exp(coef(fit_p) + 1.96 * se))
summary(fit_p)$coefficients[, 2]     # for contrast: the naive Poisson SEs

# Step 5: the logistic OR, for comparison
exp(coef(glm(ADJ, data = bw, family = binomial)))["smoke_fYes"]

Verified with R 4.6.0 and MASS 7.3.65. The sandwich package gives robust variances in one line; this site installs no extra packages, so the sandwich estimator is written out below.

Step 1: the univariable log-binomial converges

Only one thing changes in the model — the link function goes from logit to log:

logp=β0+β1x1++βkxk\log p = \beta_0 + \beta_1 x_1 + \cdots + \beta_k x_k

so exp(β)\exp(\beta) is now the risk ratio directly, with no conversion needed. With smoking as the only predictor, R does not complain at all:

  • RR 1.608 (95% CI 1.06–2.44, p 0.026), converged in 6 iterations.

Step 2: adding covariates fails on the default

Add age, mother’s weight and race, write it exactly the same way, and R stops with an error:

LocaleMessage
Englishno valid set of coefficients has been found: please supply starting values
The machine that produced these figures (zh_TW)找不到有效的係數:請提供初始值

The second row is not a different error. R translates its own messages according to the system locale, so the identical failure prints differently on a colleague’s machine. Two practical consequences: search the web with the English wording, because that is what the answers are indexed under; and when you paste an error into an issue or a message to someone, include the English version.

What the message means is that no valid set of starting coefficients could be found. The reason is that a log link does not constrain the fitted values to stay below 1: the inverse logit always lands between 0 and 1, whereas exp(β0+β1x1+)\exp(\beta_0 + \beta_1 x_1 + \cdots) can exceed 1, and that is not a probability. The rule R uses to guess starting values produces, on this data, a set of coefficients that puts some fitted probabilities above 1, and the iteration has nowhere valid to begin.

Step 3: starting values make it converge

Starting values only have to be legal; they do not have to be close to the answer. A generally useful set is: the intercept at the log of a rough baseline risk, and every slope at 0 — which says “assume no association to begin with, and a baseline risk of about this much”.

With that, it converges in 7 iterations:

  • Adjusted RR 1.786 (95% CI 1.18–2.71, p 0.006)

Step 4: modified Poisson, with nothing to tune

The other route is less work: use the wrong likelihood on purpose. Fit the 0/1 outcome with Poisson regression and a log link, and the point estimate is still a consistent estimate of the risk ratio; then repair the variance with a robust (sandwich) standard error. That combination is what the literature calls modified Poisson (Zou 2004).

It runs with nothing tuned at all:

  • Adjusted RR 1.915 (95% CI 1.26–2.92, p 0.002), 5 iterations, no starting values supplied.

What the robust standard error is actually fixing

The sandwich estimator is two slices of bread around a filling:

V=(XWX)1[Xdiag((yiμ^i)2)X](XWX)1V = (X^\top W X)^{-1} \left[ X^\top \mathrm{diag}\big((y_i - \hat\mu_i)^2\big) X \right] (X^\top W X)^{-1}

The bread is the information matrix under the model’s assumption; the filling is the squared residuals actually observed. So what it does is replace the assumption “the variance is whatever the model says” with “the variance is whatever the data actually showed”.

Step 5: the logistic OR, for contrast

Same data, same covariates, logistic instead:

  • Adjusted OR 2.870 (95% CI 1.36–6.05, p 0.006)
A forest plot of four estimates with 95% confidence intervals on a logarithmic axis, with a dashed reference line at 1. From the top: the log-binomial RR with smoking only, 1.61 (1.06–2.44); the adjusted log-binomial RR 1.79 (1.18–2.71); the adjusted modified Poisson RR 1.91 (1.26–2.92), all three drawn in blue; and at the bottom, in red to mark it as a different quantity, the adjusted logistic OR 2.87 (1.36–6.05), whose point estimate and whole interval are shifted to the right of the other three and whose interval is longer, though it still overlaps them. All four intervals lie entirely to the right of the reference line.
Three risk ratios and one odds ratio side by side. The bottom row is a different quantity, not a bigger number on the same ruler — they are drawn together only to make the consequence of misreading it visible.Plotting script figures/scripts/B2-09-adjusted-rr.R
ModelQuantityEstimate95% CIp
log-binomial, smoking onlyRR1.6081.06–2.440.026
log-binomial, adjustedRR1.7861.18–2.710.006
modified Poisson, adjustedRR1.9151.26–2.920.002
logistic, adjustedOR2.8701.36–6.050.006

At the 31.2% prevalence of this dataset, the OR is about 61% larger than the log-binomial RR (and about 50% larger than the modified Poisson RR).

Where the rare-disease assumption stops working

Two panels, both with the risk in the unexposed group (0 to 50 percent) on the horizontal axis. Left panel: the adjusted OR 2.87 is drawn as a horizontal dashed line, and a red curve shows the risk ratio that OR corresponds to, falling from about 2.84 at a baseline risk of 0.5% to about 1.48 at 50%; a vertical dotted line at 10% is labelled as the rule of thumb, and a filled point marks this dataset's unexposed risk of 25.2%, where the corresponding risk ratio is about 1.95. Right panel: the same relationship expressed as how many percent larger the OR is than that risk ratio, a red straight line through the origin rising from about 1% to about 94%; dotted lines cross at a baseline risk of 10% and a gap of about 19%, and a filled point marks this dataset's 25.2% baseline risk with a gap of about 47%.
Left: one OR corresponds to entirely different risk ratios at different baseline risks. Right: the gap grows strictly linearly with baseline risk (it is a straight line, not merely one that looks straight), and the customary 10% threshold is not where the gap disappears — only where it is still small.Plotting script figures/scripts/B2-09-adjusted-rr.R

The line in the right-hand panel is exactly straight, not coincidentally straight-looking. Rearranging the conversion makes that obvious: given an OR and an unexposed risk p0p_0,

RR=OR(1p0)+ORp0ORRR=1+p0(OR1)\mathrm{RR} = \frac{\mathrm{OR}}{(1 - p_0) + \mathrm{OR} \cdot p_0} \quad\Longrightarrow\quad \frac{\mathrm{OR}}{\mathrm{RR}} = 1 + p_0 (\mathrm{OR} - 1)

In other words, how much bigger the OR is than the RR is proportional to the unexposed risk, with OR1\mathrm{OR} - 1 as the constant of proportionality. There is therefore no baseline risk that is a “safe threshold”: the gap grows linearly from zero, it is just that it grows slowly at the low end. As p00p_0 \to 0 the gap goes to zero, and that limit is all the rare-disease assumption ever says.

Holding the adjusted OR fixed and varying the baseline risk:

Risk in the unexposed groupRR this OR corresponds toOR larger by
1%2.8182%
5%2.6259%
10%2.41819%
20%2.08937%
This dataset, 25.2%1.95047%

Which one to use

SituationWhat to do
Cohort study or trial, outcome not rare, an RR is wantedTry log-binomial first; use modified Poisson if it will not converge
log-binomial converges but fitted probabilities sit near 1Boundary solution — switch to modified Poisson or report a risk difference
Case-control studyOnly an OR is available; the denominators were chosen by the investigator. See case-control studies
A population-level quantity is wanted, or an NNTReport a marginal risk difference — see marginal estimates and G-computation
The outcome really is rare (unexposed risk far below 10%)Reading the logistic OR as an RR introduces little error, but still label it an OR

Common mistakes

MistakeWhy it is wrong
Falling back to an OR when log-binomial errors, and saying the RR could not be estimatedStarting values or modified Poisson will both get you one
Treating convergence as the end of the checkLook at the largest fitted probability; sitting near 1 is the sign of a boundary solution
Reporting a binary-outcome RR with naive Poisson standard errorsA binary outcome has less variance than Poisson assumes, so the interval comes out too wide
Applying “without robust standard errors the interval is too narrow” to this pageThat statement is about clustered data; here the direction is reversed
Treating an OR as a bigger RRThey are different quantities, not different readings on one ruler
Judging the OR-as-RR approximation by the overall prevalenceThe criterion is the risk in the unexposed group
Saying “below 10% they are equal”It is an approximation, not an identity, and the gap there is already appreciable
Reporting a point estimate as “risk reduced by N%” when the interval crosses 1Write that no difference was detected, and give the interval
Comparing an RR from one paper with an OR from anotherDifferent quantities, and different adjustment sets as well
Reading a modified Poisson estimate as an odds ratioWith a log link it estimates a risk ratio

Further reading

Reproducing every number on this page

/opt/homebrew/bin/Rscript figures/scripts/B2-09-adjusted-rr.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 patients, same covariates: logistic regression gives an adjusted odds ratio of 2.87. Can that be read as the adjusted risk ratio?

Show the answer and why

Correct answer: No - the log-binomial adjusted risk ratio is 1.79, and the odds ratio sits much further from one

The log-binomial adjusted risk ratio is 1.79 against an odds ratio of 2.87 - reading 2.87 as "risk multiplied by 2.87" overstates the effect by roughly half. Low birth weight runs at close to thirty percent in these data, which is nowhere near rare, and OR and RR converge only when the outcome is rare. 1.91 is the modified Poisson adjusted risk ratio: it estimates the same quantity as log-binomial, and the small gap between them comes from model form rather than definition, so it is not a second odds ratio.

If modified Poisson skips the sandwich estimator and takes standard errors straight from the Poisson formula on the log scale, which way do they err?

Show the answer and why

Correct answer: Too large - the naive value is 0.285, because Poisson assumes variance equals the mean and so overstates it for a binary outcome

The naive 0.285 is clearly larger than the HC0 robust standard error of 0.215. Poisson assumes the variance equals the mean, while a binary outcome has variance p(1-p), which is always smaller - so the naive standard error is bound to run large and the interval wide. That direction is conservative, and conservative is not the same as correct: an over-wide interval turns a real effect into a non-significant one just as readily, and nothing in the output flags it. 0.218 is HC1, the small-sample-corrected version, barely different from HC0.

The log-binomial model fails from its default starting values and runs only when they are supplied by hand. Which number best explains why it converges at all?

Show the answer and why

Correct answer: The largest fitted probability is 0.795, still under one

What matters is that the largest fitted probability, 0.795, has not crossed one. A log link places no ceiling on fitted probabilities, and once any row is pushed past one the likelihood is undefined and IRLS has no legal starting point - which is exactly why the default start fails here. 0.405 is the crude risk among smokers and 0.312 the overall prevalence: those describe the observed data, whereas convergence turns on how high the model pushes at the edges of covariate space, which can sit far above any crude risk.

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.