Regression Adjustment for Experiments
Randomization identifies the assignment effect; regression can improve precision. It should not redefine which students count, repair post-treatment selection, or turn an outcome-informed model search into a confirmatory result.
The estimand remains the all-randomized mean ITT score effect. This worked example uses the observed final scores and the pre-randomization baseline score. It therefore inherits the missing-outcome limitation from the unadjusted analysis.
Prerequisites
Complete Difference in Means and Uncertainty and know which variables were measured before assignment.
Learning goals
- Distinguish precision adjustment from confounding control.
- Use a prognostic pre-treatment covariate without conditioning on treatment response.
- Report adjusted magnitude and uncertainty alongside the unadjusted result.
A prespecified baseline adjustment
For a continuous outcome, one transparent working model includes assignment, centered baseline score, and—when the sample supports it—an assignment-by-baseline interaction. The analysis plan must state the covariates, transformations, interactions, variance estimator, and treatment of missing values before outcomes are inspected.
The compact calculation below estimates a common within-arm baseline slope, constructs adjusted outcomes, and compares their assignment-group means. Its unequal-variance normal interval is a teaching approximation; larger analyses should use the preregistered estimator and a design-compatible robust variance calculation.
import csv
import math
import statistics
with open("fixtures/experimentation-causal-inference/ab-test-users.csv", newline="") as handle:
complete = [row for row in csv.DictReader(handle) if row["final_score"]]
groups = {
arm: [row for row in complete if row["assigned_arm"] == arm]
for arm in ("control", "treatment")
}
means = {
arm: (
statistics.mean(float(row["baseline_score"]) for row in group),
statistics.mean(float(row["final_score"]) for row in group),
)
for arm, group in groups.items()
}
numerator = sum(
(float(row["baseline_score"]) - means[arm][0])
* (float(row["final_score"]) - means[arm][1])
for arm, group in groups.items() for row in group
)
denominator = sum(
(float(row["baseline_score"]) - means[arm][0]) ** 2
for arm, group in groups.items() for row in group
)
theta = numerator / denominator
grand_baseline = statistics.mean(float(row["baseline_score"]) for row in complete)
adjusted = {
arm: [
float(row["final_score"])
- theta * (float(row["baseline_score"]) - grand_baseline)
for row in group
]
for arm, group in groups.items()
}
effect = statistics.mean(adjusted["treatment"]) - statistics.mean(adjusted["control"])
se = math.sqrt(
statistics.variance(adjusted["treatment"]) / len(adjusted["treatment"])
+ statistics.variance(adjusted["control"]) / len(adjusted["control"])
)
print(f"baseline slope={theta:.3f}")
print(f"adjusted effect={effect:.2f} score points")
print(f"95% interval=({effect - 1.96 * se:.2f}, {effect + 1.96 * se:.2f})")
Adjustment should be reported as a planned estimator, not as evidence that randomization “failed.” Comparing adjusted and unadjusted results is useful; choosing whichever crosses a threshold is not.
Never adjust for a consequence of treatment
Sessions, questions answered, adherence, and satisfaction occur after assignment here. Conditioning on them can block part of the total effect, create selection through a collider, or target an undefined subset. A mediation or per-protocol question needs a new estimand and additional assumptions; it is not ordinary precision adjustment.
Covariates should be measured before assignment, prognostic of the outcome, available for nearly everyone, and chosen without inspecting treatment effects. Flexible or high-dimensional adjustment requires its own cross-fitting and inference plan. With small samples, model instability and chance extrapolation deserve explicit diagnostics.
Common mistakes
- Adding covariates until the treatment p-value becomes favorable.
- Adjusting for sessions or another post-treatment variable.
- Omitting blocks, pairs, clusters, or assignment probabilities from the analysis.
- Reporting a model coefficient without defining its marginal estimand.
- Assuming model adjustment fixes missing outcomes or noncompliance.
Practice
Classify baseline score, assigned arm, sessions, received sprint, and final score by measurement time. Mark which can enter a prespecified precision adjustment for the total ITT effect and explain every exclusion.
Next steps
Binary, Count, and Ratio Metrics changes the outcome scale while preserving student-level assignment.
Primary sources
Checked 2026-10-15: Lin, “Agnostic Notes on Regression Adjustments to Experimental Data” and FDA, Adjusting for Covariates in Randomized Clinical Trials.