ExpertIndependently reviewed, not yet spot-checked by a human

Marginal structural models and time-varying IPTW

Patients start, stop and switch treatment mid-follow-up, and the marker that decides whether they switch is itself a consequence of the earlier treatment. Put that variable in the regression and you get over-adjustment plus collider bias; leave it out and the confounding stays — both are wrong. Weighting is the third route: this page uses a simulation with a known truth to put all three estimates side by side, and shows how stabilised weights are fitted, why the variance cannot be read off the model, and how IPCW multiplies with IPTW.

Why this method gets its own page

There are about three ways you land here from a paper. The Methods say patients who switched treatment. They say time-updated covariates. Or they say per-protocol analysis, and mean something more than dropping the non-adherent. There is only one way you land here from your own data: patients start, stop and switch treatment during follow-up, instead of being sorted into two arms on day one and staying there.

Data of that shape has a feature no standard regression handles. The marker that decides whether a patient gets treated the second time — CD4 count, blood pressure, renal function, disease activity — is itself a consequence of the first treatment. It is a confounder of the later treatment and a consequence of the earlier one at the same time. A standard regression offers exactly two options for such a variable: put it in, or leave it out. This page is about why both are wrong, and what the third route looks like.

Three places on this site send readers here: time-varying covariates notes that a time-varying mediator-cum-confounder cannot be handled by a standard Cox model, IPTW notes that only time-varying weighting works once exposure changes over time, and target trial emulation notes that a per-protocol contrast has to deal with treatment that changes. This page is where those three lines end, so it does not hand you back to them — the weight formula, runnable code and a simulation with a known truth are all below.

Treatment and confounder feeding each other

Shrink the time axis to its minimum — two clinic visits — and the whole problem is already there:

  • Visit 0: the patient is treated or not, written A0A_0. In this simulation it is randomised.
  • Visit 1: a clinical marker L1L_1 is measured, and the second treatment A1A_1 is decided from it.
  • Outcome YY: an event within, say, one year.

L1L_1 sits in an awkward place. It is a consequence of A0A_0 (treatment moved the marker) and a confounder of A1A_1 (the marker drove the second treatment decision, and it also predicts the outcome). That is treatment-confounder feedback: treatment changes the covariate, and the covariate turns around and decides the next treatment.

A directed acyclic graph spanning two clinic visits. Along the bottom, four circular nodes read left to right: A0, L1, A1, Y, with a blue solid arrow between each neighbouring pair — A0 into L1, L1 into A1, A1 into Y. Two further blue arrows curve underneath from A0, one landing on A1 and a deeper one landing directly on Y, so that neither passes through the nodes in between. A fifth node, U, sits above and slightly left of centre; its circle is grey with a red border and is labelled unmeasured. Two red dashed arrows leave U, one down-left into L1 and one down-right into Y. Three small captions below the row mark A0 as visit 0, the L1 and A1 pair as visit 1, and Y as the outcome. Two lines of text across the top state that L1 is both a consequence of A0 and a confounder of A1, and that conditioning on L1 blocks a real path while opening the path from A0 through L1 and U to Y, whereas weighting does neither.
Two visits are enough to produce feedback: A0 causes L1, and L1 decides A1. The dashed red node U is unmeasured, and it is the crux of this page — without it, adjusting for L1 directly would not go wrong.Plotting script figures/scripts/B6-08-msm.R

The graph also carries an unobserved UU that causes both L1L_1 and YY. Clinically this is everywhere: unrecorded disease severity, performance status, social circumstances, family support. They move the marker, they move the outcome, and the database has no column for them.

The generating process writes one line for each arrow above:

VariableHow it is generatedWhat it means
Urnorm(n)Unmeasured common cause; invisible to every analysis below
A0rbinom(n, 1, 0.5)Randomised, so visit 0 carries no confounding
L1logit = -0.50 + 1.50·A0 + 1.20·UPushed by the earlier treatment, and by U
A1logit = -0.50 + 1.20·L1 + 0.30·A0Decided by looking at the marker — confounding by indication
Ylogit = -1.50 + -0.70·A0 + -0.70·A1 + 1.00·UEach treatment protective; U harmful

The cohort that comes out: 20,000 people, event rate 13.4%. Among those untreated at visit 0, 40.4% are L1L_1 positive, against 68.5% among the treated — that is the A0L1A_0 \to L_1 arrow showing up in the data. And 40.3% of the L1L_1 negative get the second treatment against 70.6% of the L1L_1 positive — that is L1A1L_1 \to A_1. Both hold at once, so feedback holds.

Putting it in and leaving it out are both wrong

Leaving it out is no better: L1L_1 is a genuine confounder of A1A_1 (it predicts A1A_1, and it predicts YY through UU), so ignoring it leaves confounding by indication untouched.

Here are the three routes, as the joint log odds ratio for treatment at both visits against treatment at neither:

AnalysisA0A_0A1A_1JointDistance from truth
Adjust for L1 (naive)-0.835-0.592-1.427 (-1.544, -1.309)0.180
No adjustment-0.622-0.353-0.975 (-1.073, -0.872)0.272
MSM, stabilised IPTW-0.593-0.580-1.173 (-1.275, -1.064)0.074
Truth (g-computation)-0.624-0.624-1.247

The directions are predictable in advance, and the data obliges: adjusting for L1L_1 overshoots (-1.427, further from the null than the truth), no adjustment undershoots (-0.975, closer to the null), and weighting lands between them and nearest the truth (-1.173, off by 0.074). Three routes, three directions — that is what this simulation exists to show.

Three stacked forest-plot panels sharing one horizontal axis of log odds ratio. Panel A shows the first-period effect A0, panel B the second-period effect A1, panel C the joint contrast of treatment at both visits against neither. Each panel has four rows, labelled from the top: adjust for L1 (the naive route), no adjustment, MSM with stabilised IPTW, and the truth from g-computation. The first three rows each show a square point estimate with a horizontal 95% bootstrap confidence interval, red for the first two and blue for the MSM row; the fourth row is a green diamond, and a green dashed vertical line runs through the whole panel at the truth. A grey dotted line marks zero. Each row is annotated on the right with its point estimate and interval limits. In panel C the adjust-for-L1 row sits to the left of the green dashed line, further from the null, the no-adjustment row sits to its right, closer to the null, and the MSM row sits between them and closest to the line. A note across the top records that this is a simulated cohort with n = 20,000, seed 20260823 and 500 bootstrap replicates.
One dataset, three analyses, three directions. Panel C is the one to read: naive adjustment overshoots, no adjustment undershoots, and weighting lands closest to the green dashed line.Plotting script figures/scripts/B6-08-msm.R

Weighting, the third route: what an MSM estimates

Start from the estimand. In potential-outcome notation, Ya0,a1Y^{a_0, a_1} is whether this person would have the event if they were assigned a0a_0 at visit 0 and a1a_1 at visit 1. Four static regimes, four whole-cohort risks:

Visit 0Visit 1Whole-cohort risk
no treatno treat22.2%
treatno treat13.4%
no treattreat13.4%
treattreat7.6%

A marginal structural model puts a model directly on those four risks, rather than on the observed outcome conditional on covariates:

logitPr(Ya0,a1=1)=β0+β1a0+β2a1\operatorname{logit} \Pr\left(Y^{a_0, a_1} = 1\right) = \beta_0 + \beta_1 a_0 + \beta_2 a_1

Marginal means the left-hand side is not conditional on any covariate — it is the risk in the whole cohort, not the risk in some kind of patient. Structural means the left-hand side is a potential outcome, not the observed one. And β1+β2\beta_1 + \beta_2 is the log odds ratio for both-treated against neither-treated in the table above, which is the number this page has been chasing.

How do you estimate that from observational data? Weight each person by the inverse of the probability of the treatment they actually received, which builds a pseudo-population in which treatment at each visit is unrelated to the covariates at that visit — as if someone had flipped a coin at every clinic appointment.

What stabilised weights look like

Unstabilised weights are the running product of inverse treatment probabilities. They explode: as soon as somebody receives, at some visit, the treatment almost nobody in their condition receives, the denominator approaches zero and that one person dominates the analysis.

Stabilised weights put back, in the numerator, a probability that uses past treatment only, not covariates:

SWi=k=0KPr(Ak=aikAˉk1=aˉi,k1)Pr(Ak=aikAˉk1=aˉi,k1, Lˉk=lˉik)SW_i = \prod_{k=0}^{K} \frac{\Pr\left(A_k = a_{ik} \mid \bar{A}_{k-1} = \bar{a}_{i,k-1}\right)}{\Pr\left(A_k = a_{ik} \mid \bar{A}_{k-1} = \bar{a}_{i,k-1},\ \bar{L}_k = \bar{l}_{ik}\right)}

The bars mean history up to that visit. The denominator removes the confounding carried by the covariates; the numerator puts back the part of the treatment process that depends only on past treatment, which is what keeps the weights near 1 instead of spread out. Covariates must never enter the numerator — that would add back exactly what the denominator removed.

In this simulation the two models are: numerator A1 ~ A0, denominator A1 ~ A0 + L1.

The weights themselves: mean 1.000, standard deviation 0.284, range 0.653 to 1.517, effective sample size 18,505, which is 92.5% of the nominal n. Stabilisation is why they are this well behaved. The unstabilised version of the same weights — numerator 1 instead of the marginal treatment probability — runs from 2.76 to 7.43 around a mean of 4.00, a maximum 4.90 times the stabilised one and a standard deviation 5.20 times as large. A factor of 2.00 of that is bookkeeping — the visit-0 numerator is a constant here because A0A_0 was randomised — but the effective sample size still falls to 17,597, or 88.0% of the nominal n against 92.5% for the stabilised weights.

A bar chart of eight bars. The horizontal axis is the treatment and covariate pattern, each bar labelled with the three binary values A0 slash L1 slash A1, ordered left to right by increasing weight. The vertical axis is the number of people. The stabilised weight for that pattern is printed above each bar. The four bars on the left carry weights below 1 and are the patterns in which the second treatment agrees with the marker, the largest of them being the pattern with A0 one, L1 one and A1 one; the four bars on the right carry weights above 1 and are the patterns in which the second treatment disagrees with the marker, and they hold fewer people. Two lines of text above the plot report the mean, range and effective sample size of the weights, and note that the visit-0 weight is exactly 1 because A0 was randomised.
All three variables are binary, so the weight takes only 8 distinct values — this is a bar chart of eight patterns, not a histogram. The up-weighted people are the four bars on the right: their second treatment did not follow their marker.Plotting script figures/scripts/B6-08-msm.R
A0 / L1 / A1PeopleStabilised weightDid the second treatment follow the marker?
1 / 0 / 01,7310.653Followed it (down-weighted)
0 / 1 / 12,7070.743Followed it (down-weighted)
0 / 0 / 03,7170.812Followed it (down-weighted)
1 / 1 / 14,9640.879Followed it (down-weighted)
0 / 0 / 12,2831.307Did not follow it (up-weighted)
1 / 1 / 01,8361.324Did not follow it (up-weighted)
1 / 0 / 11,4021.424Did not follow it (up-weighted)
0 / 1 / 01,3601.517Did not follow it (up-weighted)

This table repays a second look, because it turns what weighting does into something visible. Every one of the four patterns with a weight above 1 is a person whose L1L_1 and A1A_1 disagree — marker positive but untreated, or marker negative but treated — 6,881 people in total. They are a minority in the real world, and in the pseudo-population they are inflated to match everyone else, which is what decouples treatment from the marker. The four patterns with a weight below 1 are the mirror image: the majority who did follow the marker, shrunk.

Put differently: weighting prises the confounding open using the patients who did not follow clinical logic. That is also why positivity is this method’s weak point. If not a single marker-positive patient goes untreated, that cell is empty, the weight tends to infinity or cannot be estimated at all, and the mean weight will not show you — you have to look at the ESS, at the maximum, and at the count in every pattern, which is the table above.

Running it: long format, two stages

The implementation has two stages with a set of weights in between. This is where an MSM departs furthest from ordinary regression: you fit two models, and you never once look at the coefficients of the first — it exists only to produce the weights.

# Build the wide table d. U is the unmeasured confounder -- without it,
# conditioning on L1 merely over-adjusts, the naive model looks correct,
# and the page is refuted by its own numbers.
set.seed(20260823)
n  <- 20000
U  <- rnorm(n)                                   # the unmeasured confounder
A0 <- rbinom(n, 1, 0.5)
L1 <- rbinom(n, 1, plogis(-0.5 + 1.5 * A0 + 1.2 * U))
A1 <- rbinom(n, 1, plogis(-0.5 + 1.2 * L1 + 0.3 * A0))
Y  <- rbinom(n, 1, plogis(-1.5 - 0.7 * A0 - 0.7 * A1 + 1.0 * U))
d  <- data.frame(A0, L1, A1, Y)

# ── Stage 1: the weight models, on a long table (one row per person-visit) ──
# d is the wide table: one row per person, columns A0 / L1 / A1 / Y. Reshape
# first, because a weight is computed at each visit and multiplied through —
# a long table lets the loop be written once.
n <- nrow(d)
long <- rbind(
  data.frame(id = 1:n, visit = 0, A = d$A0, L = 0,     lagA = 0),
  data.frame(id = 1:n, visit = 1, A = d$A1, L = d$L1,  lagA = d$A0)
)

K  <- 1                    # index of the last visit
sw <- rep(1, n)            # one running weight per person, starting at 1

for (k in 0:K) {
  dk <- long[long$visit == k, ]
  dk <- dk[order(dk$id), ]

  # Numerator: past treatment only. Denominator: past treatment plus the
  # covariate measured now. Never put covariates in the numerator — that adds
  # back exactly the confounding the denominator removed.
  num <- glm(A ~ lagA,     data = dk, family = binomial())
  den <- glm(A ~ lagA + L, data = dk, family = binomial())

  pn <- predict(num, type = "response")   # P(A = 1 | past treatment)
  pd <- predict(den, type = "response")   # P(A = 1 | past treatment + covariate)

  # Each person contributes the probability of the treatment they actually
  # received, which is not P(A = 1) for the untreated.
  sw <- sw * ifelse(dk$A == 1, pn, 1 - pn) / ifelse(dk$A == 1, pd, 1 - pd)
}

# ⚠️ In this simulation lagA and L are constant at visit 0, so both models
# collapse to an intercept and that period's weight is exactly 1 (R reports the
# constant terms as NA coefficients, which is expected). Real data has baseline
# covariates L0 at visit 0, the denominator is A ~ L0, and the weight is not 1.

summary(sw)                       # the mean should sit near 1; far from it means a misfit
sum(sw)^2 / sum(sw^2)             # effective sample size

# ── Stage 2: the weighted outcome model — this is the MSM ──────────────────
# Treatment on the right-hand side and nothing else. No L1. The covariate
# appears only inside the stage-1 denominator.
# ⚠️ suppressWarnings is deliberate and is not hiding a misfit: once weighted,
# w_i * y_i is no longer a whole number of events, so glm prints
# non-integer #successes in a binomial glm!. The point estimates are the
# correct weighted score solutions; only glm's binomial bookkeeping objects.
msm <- suppressWarnings(
  glm(Y ~ A0 + A1, data = d, family = binomial(), weights = sw)
)
coef(msm)["A0"] + coef(msm)["A1"]   # both visits treated vs neither

# ⚠️ Do not read the standard errors from summary(msm). See the next section.

Verified on R 4.6.0 + jsonlite 2.0.0. Base R throughout, no extra packages; ipw and WeightIt wrap the same computation.

The truth is not the structural coefficient: non-collapsibility

The line that generates YY gives A0A_0 and A1A_1 a coefficient of -0.70 each, summing to -1.40. But the truth this page has been comparing against is -1.247, not -1.40. The two differ, and neither is a mistake.

They are different parameters:

  • -0.70 is a conditional parameter — how much treatment moves the log odds holding UU fixed.
  • -1.247 is a marginal parameter — how much the log odds of the whole-cohort risk differ between treating everybody and treating nobody.

The odds ratio is non-collapsible: even with no confounding at all, dropping a variable that predicts the outcome (here UU) shrinks the coefficient towards the null. That is a property of the odds ratio scale, not a bias — risk differences and risk ratios do not behave this way.

An MSM is not magic; it is also a model

In the table above, the MSM is still 0.074 from the truth. Part of that residual gap is not noise. It is misspecification — of the working model this page itself wrote down.

That model assumes the effect of A0A_0 does not depend on the value of A1A_1: the right-hand side has two main effects and no interaction term. The truth disagrees. With A1A_1 held at untreated, the A0A_0 contrast is -0.612; with A1A_1 held at treated it is -0.635, a difference of 0.022. The working model estimates a single parameter for two quantities that are not equal, so what it reports is a compromise between them.

This is worth stating because it is where MSMs are most often misunderstood. Weighting handles the confounding; it does not specify your outcome model for you. You still choose whether to include an interaction, whether to model time flexibly, whether dose enters as a continuous variable. A misfit weight model distorts the weights; a misfit outcome model moves the estimand itself. They are two independent routes to a wrong answer.

Why the variance cannot be read off the model

Two honest routes:

  • Bootstrap, used here. Resample people, and refit the weight models inside every replicate — refitting only the outcome model and carrying the weights through as fixed constants reproduces error 2 above inside the bootstrap. This page uses 500 replicates, and every interval in the tables comes from them.
  • Robust (sandwich) standard errors. Cheap, one option in most software, but they treat the weights as known and are usually conservative for stabilised weights. Computed here as well: the MSM A0A_0 sandwich SE is 0.045 against a bootstrap SE of 0.045, and for A1A_1 it is 0.044 against 0.046. They are close on this dataset, which is a fact about this dataset rather than a general result.

IPCW: a second weight, and the two multiply

Everything above deals with who got treated. Real follow-up data has a second selection problem: who is still under observation. Patients are lost, withdraw, move hospitals — and those who leave are rarely like those who stay. If the probability of leaving depends on covariates and treatment, then still being followed is itself a conditioned-on variable, with the same structure as confounding.

The remedy has the same shape: inverse probability of censoring weighting (IPCW). The formula runs parallel to IPTW, with the probability of receiving treatment replaced by the probability of remaining uncensored:

SWiC=k=0KPr(Ck=0Aˉk1,Cˉk1=0)Pr(Ck=0Aˉk1,Lˉk,Cˉk1=0)SW_i^{C} = \prod_{k=0}^{K} \frac{\Pr\left(C_k = 0 \mid \bar{A}_{k-1}, \bar{C}_{k-1} = 0\right)}{\Pr\left(C_k = 0 \mid \bar{A}_{k-1}, \bar{L}_k, \bar{C}_{k-1} = 0\right)}

In practice it is one more logistic model — the outcome being whether this person was censored in this interval — and everyone still under follow-up is weighted by the inverse of their probability of having stayed. The people who remain speak for those like them who left.

This simulation has no censoring, so it demonstrates only the IPTW half. Omitting the other half on real data quietly assumes that dropout is completely random — the non-informative censoring assumption from censoring and truncation, and in a population that switches and stops treatment it is usually the least defensible assumption in the analysis.

How to read it in a paper

An MSM usually survives into a paper as three or four sentences and a table. Five things to check:

  1. Whether the variables in the weight models are listed, numerator and denominator separately. Adjusted using IPTW says nothing. Covariates leaking into the numerator is a common implementation error, and it shows up on no diagnostic plot.
  2. Whether weights were computed at every visit, or only at baseline. An analysis with baseline weights only is not an MSM; it is baseline IPTW, and it cannot handle feedback.
  3. Whether the weight distribution is reported. At minimum the mean (stabilised weights should sit near 1), the maximum, and the effective sample size. A mean alone hides the extremes; the ESS here is 92.5% of the nominal n, and dropping below about half is a signal to go back and check overlap.
  4. Whether weights were truncated, where, and with a sensitivity analysis. Truncation trades a little bias for variance and is a defensible choice, but it must be stated in advance and the untruncated result shown alongside.
  5. Where the standard errors came from. You want to see the word bootstrap or robust or sandwich. A weighted regression with no statement about variance is most likely reporting the unusable one.

And one that gets skipped: the outcome model’s right-hand side should carry treatment (and any baseline covariates) and nothing time-varying. An MSM with LkL_k in its outcome model is not an MSM; it is the analysis this page warned about three sections ago.

Common misuse

MisuseWhy it is wrong
Putting the time-varying covariate in the weight model and the outcome modelThat weights and then conditions, so over-adjustment and collider bias both return
Covariates in the numeratorAdds back the confounding the denominator removed; stabilisation stops meaning anything
Calling baseline weighting an MSMBaseline weights cannot handle treatment changing the covariate that decides the next treatment
Reporting the mean weight onlyA mean near 1 and the absence of extreme values are different claims; report the maximum and the ESS
Reading the standard error off the weighted regressionThe weights are estimated and the weighted rows are not independent, so intervals come out too narrow
Bootstrapping without refitting the weight modelsLeaves the weight models’ uncertainty out, which is precision you did not pay for
Treating non-integer #successes as a broken modelIt is expected under weighting; the point estimates are unaffected
Never checking positivityIf nobody with a given covariate pattern is treated, or everybody is, the weights explode or cannot be estimated
Forgetting IPCWOnce deviation from protocol triggers censoring, the censoring is informative and IPTW alone is not enough
Reading an MSM coefficient as the effect for one patientIt is a marginal parameter: the whole cohort treated against the whole cohort untreated
Comparing an MSM odds ratio against a conditional odds ratio by sizeNon-collapsibility means they differ even with no confounding at all

Where this page sits

  • Where the weighting idea starts — see propensity score weighting (IPTW). That page covers the single-timepoint version; its diagnostics (balance tables, weight distributions, ESS, truncation) apply here unchanged, except that they have to be done at every visit.
  • How to draw the arrows and decide what to condition on — see DAGs and the structure of confounding. The collider bias on this page is that page’s collider rule instantiated along a time axis.
  • Time-varying covariates in a Cox model — see time-varying covariates and the long table. That page teaches how the long table is built and what tmerge() does, which is the data work that precedes stage 1 here. The two part company on whether the covariate is affected by exposure: if it is not, the standard Cox model on that page is right; if it is, weighting is required.
  • Where a per-protocol contrast lands — see target trial emulation. That page defines the estimand (intention-to-treat or per-protocol) and time zero; this page supplies the estimator for the per-protocol one.
  • The censoring assumption — see censoring and truncation. The IPCW section replaces how non-informative censoring is handled, not the requirement itself.
  • Where it shows up clinically — see the database study chapter; dispensing records in claims data are time-varying exposure by nature.

Rerun every number on this page

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

Three routes, three joint log odds ratios: putting the time-varying covariate in the regression gives -1.427, no adjustment at all gives -0.975, the MSM gives -1.173, and the truth is -1.247. What is wrong with putting it in?

Show the answer and why

Correct answer: It blocks the real path through that variable and, as a collider on it, opens a spurious path through the unmeasured one, ending 0.180 from the truth

That time-varying covariate is three things at once: a descendant of the first treatment, a confounder of the second, and a common effect of the first treatment and the unmeasured variable. Conditioning on it therefore commits two errors in opposite directions - blocking a real causal path (over-adjustment) and opening a spurious one it had been blocking (collider bias). The net is a miss of 0.180, landing further from the null than the truth: -1.427 against -1.247. No adjustment is not the remedy either; -0.975 leaves the second period's confounding untouched and misses by more, in the other direction. The weighted estimate of -1.173 sits between them and closest to the truth, which is what this simulation exists to show.

In panel A the unadjusted first-period estimate is -0.622, closer to the truth of -0.624 than the MSM's -0.593. Does that make no adjustment the better method?

Show the answer and why

Correct answer: No. The first treatment is randomised in this simulation, so that cell had no confounding to handle; the same analysis returns -0.353 in the second period

A method landing near the truth in one cell is not evidence that it did the right thing. The first treatment is randomised, so no adjustment had nothing to correct there, and -0.622 sitting beside -0.624 is a gift from the design. The same analysis returns -0.353 in the second period, where the truth is again near -0.624, and the gap is visible at a glance. The MSM's -0.593 is slightly further out, but the MSM targets the joint contrast, so ranking methods by a single cell means nothing. Picking a winner by one coefficient is the same disease as picking an analysis by its p-value.

The first-period stabilised weight in this simulation is exactly 1. Can real data copy that?

Show the answer and why

Correct answer: No. The first treatment is randomised here with no baseline covariates, so the two models coincide and the ratio is identically 1.00

The first-period weight of 1.00 is a gift from the simulation's setup: the first treatment is randomised and there are no baseline covariates, so the numerator and denominator models for that visit coincide. Real data has no such luck - age, comorbidity and disease severity determine both the first treatment and the outcome, so that period needs its own weight, of exactly the same form as the second. Reading this as no weight needed at the first visit drops the entire baseline confounding. The value 1.52 is the largest weight overall and 0.93 the effective sample size as a share of nominal; both describe how well behaved the weights are, and how well behaved they are is a different question from whether the first period needs a model.

With the same denominator models, replacing the numerator by 1 moves the weights from a range of 0.65 to 1.52 into a range of 2.76 to 7.43. What is the point of that change?

Show the answer and why

Correct answer: The effective sample size drops to 0.88 times the nominal one: once the weights spread out a few people carry more of the analysis, and the variance of the estimate rises with them

The point is not that the weights got bigger but that they got wider. A mean of 4.00 is bookkeeping: replacing the numerator with a constant multiplies everyone's weight by the same factor, so no sample size was gained and no precision with it. The real cost is written in the effective sample size, which falls to 0.88 of nominal, because the spread of the weights is what decides whether a few people can dominate the analysis. The ratio of 4.90 at the maximum is a symptom of that spread, not a detail to wave through; a mean that matches guarantees nothing about the spread, which is precisely why the effective sample size, the maximum and the count in each pattern all have to be looked at. Moving from 0.65 to 1.52 into 2.76 to 7.43 is that spread made visible.

In the equation generating the outcome, each period's treatment coefficient is -0.7. Yet this page compares everything against a truth of -1.247. Which statement is right?

Show the answer and why

Correct answer: -1.247 is the marginal parameter - the log odds difference in whole-cohort risk between treating everyone in both periods and treating nobody

The two numbers answer different questions. The value -0.700 is a conditional parameter: how much treatment shifts the log odds with the unmeasured variable held fixed. The value -1.247 is a marginal parameter: the log odds difference in whole-cohort risk between treating everyone and treating nobody. An odds ratio is not collapsible, so dropping a predictive variable from the model shrinks the coefficient towards zero even with no confounding at all - a property of the scale, not a bias. The MSM targets the marginal parameter, so -1.247 is what it must match; comparing -1.173 against the structural coefficient yields the false conclusion that the MSM is biased too, when part of that gap is only the scale. The same point in a real paper is that an adjusted and an unadjusted odds ratio were never going to agree.

The MSM's joint estimate is -1.173 against a truth of -1.247, a gap of 0.074, and the bootstrap standard error of that joint estimate is 0.057. Which reading of the residual holds up?

Show the answer and why

Correct answer: The gap is barely more than one standard error of the estimate itself (0.057), so its size cannot be read as a measure of how much bias is left - the working-model misspecification this page points out is visible in the model's assumptions, not in this difference

Put the residual next to its own uncertainty first. The bootstrap standard error of the joint estimate is 0.057 and the residual is 0.074, barely more than one standard error - a single simulation run producing a gap this size is fully explained by noise, so the number cannot be used to measure how much bias is left. The page is right that the working model is misspecified: main effects only on the right-hand side, while the two first-period contrasts in the truth, -0.635 and -0.612, are not equal. But that is visible in the model's assumptions, not in the residual, and the distance between those two contrasts is an order of magnitude smaller than 0.074, so it cannot be what the residual is made of. As for the weights, the standard deviation of 0.284 describes how spread they are; more bootstrap replicates only pin down the standard error itself, they do not shrink the estimator's own sampling error and they certainly do not pull the point estimate towards the truth.

Why can the standard errors printed by summary() on a weighted regression not be read off directly?

Show the answer and why

Correct answer: Because it treats each weighted row as an independent binomial observation and the weights as known constants; the 0.0445 in the table comes from resampling instead

The standard error printed by a weighted glm makes two assumptions, neither of which holds. It treats the weighted rows as independent observations - a person with a weight of 1.5167 is one person, not one and a half - and it treats the weights as known constants, when they were estimated from the same people and carry sampling error of their own. The 0.0445 in the table is the bootstrap figure: individuals are resampled and the weight models refitted on every replicate. Refitting only the outcome model and carrying the weights in as fixed numbers would import the second error into the bootstrap intact. The 0.0455 is the sandwich standard error, a second workable route, but it also treats the weights as known, and the two agreeing this closely is a fact about these data rather than a rule. Rescaling every weight by a constant changes nothing, because the problem is where the weights came from, not how large they are.

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.