A 36-scenario proof-of-concept simulation (100,000 runs each) with correctly specified parametric models. All adjusted estimators are near-unbiased; the trial-only estimator is biased by 0.66-0.89 under strong selection on an effect modifier; OM has the smallest variance, DR estimators are close, and IOW (especially unnormalized) is far noisier.
Overview
Purpose: examine finite-sample bias and variance of eqs. (1)-(6) for potential-outcome means and treatment effects in the target, in nested trial designs with continuous outcomes. Binary-outcome and non-nested results are in the Appendix (not in the PDF).
Selection: logitPr[S=1∣X]=β⊤X, β=(β0,…,β3), β0 solved numerically so that nRCT hits its target; β1∈{0,1} (none/strong selection on X1); β2=β3=1.
Potential outcomes Ya=θa⊤X+ϵa, ϵa∼N(0,1); θ1=(1,θ11,1,1) with θ11∈{2,1}, θ0=(0,1,1,1). So θ11=2 means effect modification by X1 on the mean-difference scale.
Treatment: A∼Bernoulli(0.5) among S=1 (marginally randomized). Y=AY1+(1−A)Y0.
Estimators applied: eqs. (1)-(6) plus trial-only. All working models correctly specified: outcome models with main effects, fit separately by arm; logistic participation and treatment models with main effects.
Bias. All estimators except Trial are essentially unbiased, with one exception: IOW2 shows small positive finite-sample bias (0.008 to 0.075), largest when the trial is small relative to the cohort. This is the usual ratio-estimator bias, traded against its massive variance reduction vs IOW1 (e.g., 1.13 vs 4.45 at 200/2000).
Variance. OM lowest; DR2/DR3 roughly 2x the variance of OM (1.97x at 200/2000, 2.3x at 1000/2000); DR1 (unnormalized) worse; IOW1 an order of magnitude worse than OM. Variance falls with nRCT and, for the weighted estimators, grows mildly with cohort size n (more extreme weights as the target grows relative to the trial).
Trial-only. Biased for E[Y1−Y0∣S=0] whenever selection depends on the effect modifier, but unbiased for E[Y1−Y0∣S=1]. Its variance is low — precision about the wrong estimand.
Normalization. With strong selection and a trial much smaller than the target, normalized weights (IOW2, DR2, DR3) beat unnormalized (IOW1, DR1). The authors “recommend avoiding weighted estimators that do not use normalized weights”.
The paper’s Appendix provides R functions (one per estimator), a geex sandwich-variance implementation and Stata code to reproduce the study; these are not in the PDF.
Examples
Independent mini-replication (Python)
A 300-replication run of one cell (n=5000, nRCT≈500, β1=1, θ11=2, known ea=0.5, estimand = finite-cohort mean of Y1−Y0 among non-participants) gave bias/variance: Trial +0.764 / 0.035; OM +0.011 / 0.031; IOW1 +0.042 / 0.865; IOW2 +0.048 / 0.565; DR1 -0.005 / 0.074; DR2 -0.006 / 0.062; DR3 -0.004 / 0.062. The Trial row matches the paper’s 0.7654 for the same cell; the qualitative ordering of variances matches Table 3 (absolute values differ because of 300 vs 100,000 runs, known treatment probabilities, and a finite-sample target).
import numpy as npfrom scipy.optimize import brentqdef logit_fit(X, s): Z = np.c_[np.ones(len(X)), X]; b = np.zeros(Z.shape[1]) for _ in range(50): p = 1/(1+np.exp(-Z@b)); b += np.linalg.solve(Z.T@(Z*(p*(1-p))[:,None]), Z.T@(s-p)) return 1/(1+np.exp(-Z@b))def wls(Xtr, ytr, Xall, w=None): Z = np.c_[np.ones(len(Xtr)), Xtr]; w = np.ones(len(Xtr)) if w is None else w return np.c_[np.ones(len(Xall)), Xall] @ np.linalg.solve(Z.T@(Z*w[:,None]), Z.T@(w*ytr))def one(rng, n=5000, nrct=500, b1=1.0, th11=2.0): X = rng.standard_normal((n,3)); Xd = np.c_[np.ones(n), X] b0 = brentq(lambda b: (1/(1+np.exp(-(Xd@np.r_[b,b1,1,1])))).sum()-nrct, -20, 5) S = rng.random(n) < 1/(1+np.exp(-(Xd@np.r_[b0,b1,1,1]))) Y1 = Xd@np.array([1,th11,1,1]) + rng.standard_normal(n); Y0 = Xd@np.array([0,1,1,1]) + rng.standard_normal(n) A = rng.random(n) < .5; Y = np.where(A, Y1, Y0); truth = (Y1-Y0)[~S].mean() p = logit_fit(X, S.astype(float)); mu = {} for a in (0,1): sel = S & (A==a); g = wls(X[sel], Y[sel], X); w = np.where(sel, (1-p)/(p*.5), 0.) mu[a] = dict(OM=g[~S].mean(), IOW1=(w*Y).sum()/(~S).sum(), IOW2=(w*Y).sum()/w.sum(), DR2=(w*(Y-g)).sum()/w.sum()+g[~S].mean(), DR3=wls(X[sel], Y[sel], X, w[sel])[~S].mean()) out = {k: mu[1][k]-mu[0][k]-truth for k in mu[1]} out["Trial"] = Y[S&A].mean()-Y[S&~A].mean()-truth return outrng = np.random.default_rng(1); R = [one(rng) for _ in range(300)]for k in R[0]: v = np.array([r[k] for r in R]); print(k, round(v.mean(),3), round(v.var(),3))
Connections
Takeaway for analysts: pick OM or a normalized DR estimator by default; keep the trial-only number for comparison; report both.