Causal Design / Assignment Mechanisms

RCTs and Quasi-Experimental Designs

RCTs create exchangeability through controlled randomization; quasi-experiments search for as-if random assignment in rules, thresholds, lotteries, timing shocks, or policy boundaries.

Mechanism Lab

Animation: how assignment mechanisms create causal evidence

The animation places units into baseline blocks, randomizes treatment, estimates the mean contrast, routes noncompliance through Z to D, and maps quasi-experimental rules as assignment mechanisms.

Step 1 / 5

Eligible sample

Define the sample and baseline blocks before assigning treatment.

i=1,...,N; blocks b

Animation Control

Reduced-motion users receive the same step states without continuous motion.

01 / Intuition

Core Intuition

Good causal design first answers why units receive treatment, then estimates how large the effect is. If the assignment mechanism is credible, a simple contrast can carry strong interpretation.

The strength of an RCT is that the researcher controls assignment Z or D, making treated and control groups have the same potential-outcome distribution in expectation.

A quasi-experiment is not a license to run arbitrary regressions without randomization. It treats an external rule as a natural assignment mechanism and builds diagnostics around that rule.

02 / Math

From random assignment to quasi-experimental identification

01 / Complete random experiment

Choose N1 treated units out of N and leave N0 in control. The potential-outcome table is fixed before assignment; randomness comes only from D.

sum_i D_i = N_1,  N_0=N-N_1
Y_i^obs = D_i Y_i(1) + (1-D_i)Y_i(0)

02 / Exchangeability

Randomization makes assignment independent of potential outcomes, so observed treated and control means are unbiased for their potential-outcome population means.

D independent (Y(1),Y(0))
E[bar Y_1^obs] = bar Y(1),  E[bar Y_0^obs] = bar Y(0)

03 / Unbiased difference in means

The difference-in-means estimator has expectation equal to the finite-population ATE.

tau_hat = (1/N_1) sum_{D_i=1} Y_i^obs - (1/N_0) sum_{D_i=0} Y_i^obs
E_D[tau_hat] = (1/N) sum_i {Y_i(1)-Y_i(0)}

04 / Neyman variance

Under complete randomization, variance depends on both potential-outcome variances and treatment-effect heterogeneity. Because S_tau^2 is not observed, the usual estimator is conservative.

Var(tau_hat) = S_1^2/N_1 + S_0^2/N_0 - S_tau^2/N
V_hat = s_1^2/N_1 + s_0^2/N_0

05 / Randomization inference

Under a sharp null, all missing potential outcomes are known, so one can enumerate or simulate the assignment distribution of a statistic.

H_0: Y_i(1)-Y_i(0)=tau_0 for all i
p = Pr_D(|T(D)| >= |T(D_obs)| | H_0)

06 / Blocked randomization

Randomize within baseline risk groups, schools, or regions, then aggregate block-specific effects by block size. This often reduces variance by comparing like with like.

tau_hat_blocked = sum_b (N_b/N) tau_hat_b

07 / Noncompliance and ITT/LATE

When the randomized variable is an offer or encouragement Z and actual participation D is imperfect, report ITT first. With relevance, exclusion, and monotonicity, Z identifies complier LATE.

ITT_Y = E[Y|Z=1]-E[Y|Z=0]
ITT_D = E[D|Z=1]-E[D|Z=0]
LATE = ITT_Y / ITT_D

08 / Quasi-experimental designs

The issue is not model complexity. The issue is whether a rule, boundary, or timing shock creates as-if random variation locally or within a defensible window.

assignment rule -> as-if random variation -> diagnostic checks -> causal estimand

03 / Code

Python code: RCT, randomization inference, blocks, and noncompliance

The simulation demonstrates four ideas: difference-in-means for an RCT, a randomization-inference p-value, a blocked estimator, and ITT/LATE under noncompliance.

import numpy as np
import pandas as pd

rng = np.random.default_rng(123)
n = 800
school = rng.integers(0, 8, size=n)
baseline = rng.normal(0, 1, size=n) + 0.25 * school

# Fixed potential outcomes.
y0 = 50 + 5 * baseline + rng.normal(0, 4, size=n)
tau = 3.0 + 1.2 * (baseline < 0)
y1 = y0 + tau
true_ate = np.mean(tau)

# Complete random assignment.
n1 = n // 2
d = np.zeros(n, dtype=int)
d[rng.choice(n, size=n1, replace=False)] = 1
y = d * y1 + (1 - d) * y0

def diff_in_means(outcome, treat):
    return outcome[treat == 1].mean() - outcome[treat == 0].mean()

tau_hat = diff_in_means(y, d)
se_neyman = np.sqrt(y[d == 1].var(ddof=1) / d.sum() + y[d == 0].var(ddof=1) / (n - d.sum()))

# Randomization inference under the sharp null of no effect.
observed_t = diff_in_means(y, d)
null_stats = []
for _ in range(5000):
    d_perm = np.zeros(n, dtype=int)
    d_perm[rng.choice(n, size=n1, replace=False)] = 1
    null_stats.append(diff_in_means(y, d_perm))
p_value = np.mean(np.abs(null_stats) >= abs(observed_t))

# Blocked estimate: compare within school, then aggregate.
df = pd.DataFrame({"Y": y, "D": d, "school": school})
blocked = (
    df.groupby("school")
      .apply(lambda g: pd.Series({
          "n": len(g),
          "tau_b": diff_in_means(g["Y"].to_numpy(), g["D"].to_numpy())
      }))
      .reset_index()
)
tau_blocked = np.average(blocked["tau_b"], weights=blocked["n"])

# Noncompliance: randomized offer Z, actual take-up D.
z = np.zeros(n, dtype=int)
z[rng.choice(n, size=n1, replace=False)] = 1
complier = rng.binomial(1, 0.72, size=n)
always_taker = rng.binomial(1, 0.08, size=n)
d_actual = np.maximum(always_taker, z * complier)
y_nc = y0 + d_actual * tau + rng.normal(0, 2, size=n)

itt_y = diff_in_means(y_nc, z)
itt_d = diff_in_means(d_actual, z)
late = itt_y / itt_d

print({
    "true_ate": round(true_ate, 3),
    "rct_tau_hat": round(tau_hat, 3),
    "neyman_se": round(se_neyman, 3),
    "randomization_p": round(p_value, 4),
    "blocked_tau_hat": round(tau_blocked, 3),
    "itt_y": round(itt_y, 3),
    "itt_d": round(itt_d, 3),
    "late_wald": round(late, 3),
})

04 / Case

Case: lottery offers and quasi-experimental extensions for an education bootcamp

  • Question: does a summer data bootcamp improve later project scores? If applicants exceed seats, a lottery offer can serve as randomized encouragement.
  • The primary analysis reports ITT: whether being offered a seat raises average outcomes. This matches the policy effect of offering seats or invitations.
  • If some offered students do not attend and some non-offered students find another route in, actual participation D is not fully randomized. With defensible exclusion and monotonicity, offer Z can estimate complier LATE.
  • Without a lottery, assignment may come from a cutoff, regional pilot, grade cohort, or policy timing. That becomes quasi-experimental, and the rule itself must justify local as-if random variation.
  • A credible report includes the experiment flow, preregistration or sample flow, baseline balance, attrition, noncompliance, spillovers, multiple testing, randomization inference or clustered standard errors, and all deviations from the original design.

05 / Risks

Common Pitfalls

Saying “we controlled for many variables” without explaining why treatment assignment is as-if random.
Interpreting the ITT of a randomized offer as the effect of actual participation.
Ignoring attrition, crossover, spillovers, or reassignment during implementation.
Failing to analyze blocked randomization by block, or ignoring clustered dependence in school or regional experiments.
Calling a design DID, RD, or IV without diagnostics around the actual institutional rule.

References