AE 20: Causal Inference from a Randomized Experiment
Suggested answers
Nowak-Węgrzyn et al. (2019) conducted a multicenter, randomized, double-blind, placebo-controlled clinical trial to evaluate whether vital wheat gluten (VWG) oral immunotherapy (OIT) is effective at increasing wheat tolerance in children and young adults with wheat allergy. In today’s AE, you will analyze a dataset simulated to represent this study using the tools from lecture.
(https://pubmed.ncbi.nlm.nih.gov/30389226/)
Packages
Data
set.seed(42)
n <- 46
wheat_trial <- tibble(
id = 1:n,
age = round(runif(n, 4, 22), 1),
baseline_wheat_ige = round(rexp(n, rate = 1/30), 1),
vwg_oit = c(rep(1, 23), rep(0, 23))
) |>
mutate(
max_dose_mg = pmax(0, round(
150 + 950 * vwg_oit - 3 * baseline_wheat_ige + 12 * age + rnorm(n, sd = 90)
))
)
glimpse(wheat_trial)Rows: 46
Columns: 5
$ id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, …
$ age <dbl> 20.5, 20.9, 9.2, 18.9, 15.6, 13.3, 17.3, 6.4, 15.8,…
$ baseline_wheat_ige <dbl> 13.3, 7.1, 30.8, 32.4, 38.6, 17.1, 90.6, 14.9, 10.6…
$ vwg_oit <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ max_dose_mg <dbl> 1331, 1391, 1126, 960, 1197, 1175, 1052, 1184, 1384…
Variables:
| Variable | Description |
|---|---|
id |
Unique participant identifier |
age |
Participant age (years) |
baseline_wheat_ige |
Baseline wheat IgE level (kU/L) — a measure of allergy severity |
vwg_oit |
Treatment indicator: 1 = VWG OIT, 0 = Placebo |
max_dose_mg |
Maximum tolerated wheat protein dose (mg) at the year-1 food challenge |
Exercise 1: Study design
With your teammate, fill in the following based on the paper and today’s lecture:
Treatment indicator (\(Z\)):
vwg_oit— 1 if the participant received VWG OIT, 0 if placeboTreatment group (\(Z = 1\)): Participants receiving vital wheat gluten (VWG) oral immunotherapy
Control group (\(Z = 0\)): Participants receiving a matched placebo
Outcome (\(Y\)):
max_dose_mg— maximum tolerated wheat protein dose (mg) at the year-1 food challengeCovariates (\(X\)):
age,baseline_wheat_ige(and in the real study: sex, threshold dose at baseline)
Exercise 2: Exploratory data analysis
a. Create a visualization comparing the distribution of max_dose_mg across the two treatment groups. What do you notice?
ggplot(
wheat_trial,
aes(
x = factor(vwg_oit, labels = c("Placebo", "VWG OIT")),
y = max_dose_mg
)
) +
geom_boxplot() +
labs(
x = "Treatment group",
y = "Max tolerated dose (mg)",
title = "Max wheat dose by treatment group"
)The VWG OIT group has a substantially higher median max tolerated dose than the placebo group, and the distributions show little overlap. This suggests a strong treatment effect.
b. Compute the mean and standard deviation of max_dose_mg for each treatment group.
Exercise 3: Modeling
a. Fit a linear regression model predicting max_dose_mg from vwg_oit.
wheat_fit <- linear_reg() |>
fit(max_dose_mg ~ vwg_oit, data = wheat_trial)
tidy(wheat_fit)# A tibble: 2 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 234. 31.3 7.48 2.29e- 9
2 vwg_oit 967. 44.2 21.9 2.79e-25
b. Interpret the slope coefficient for vwg_oit in context. Since this is a randomized experiment, use causal language in your interpretation (e.g., “causes,” “leads to”).
Receiving VWG OIT causes an estimated increase of approximately 950 mg in max tolerated wheat protein dose compared to placebo, on average. Because treatment was randomly assigned, we can attribute this difference to the treatment itself rather than to any confounding variable.
Exercise 4: Inference
Option A: Bootstrap confidence interval
a. Calculate the observed fit using specify() |> fit().
observed_fit <- wheat_trial |>
specify(max_dose_mg ~ vwg_oit) |>
fit()
observed_fit# A tibble: 2 × 2
term estimate
<chr> <dbl>
1 intercept 234.
2 vwg_oit 967.
b. Generate 1000 bootstrap samples and fit a model to each one.
set.seed(1120)
boot_fits <- wheat_trial |>
specify(max_dose_mg ~ vwg_oit) |>
generate(reps = 1000, type = "bootstrap") |>
fit()
boot_fits# A tibble: 2,000 × 3
# Groups: replicate [1,000]
replicate term estimate
<int> <chr> <dbl>
1 1 intercept 225.
2 1 vwg_oit 942.
3 2 intercept 246.
4 2 vwg_oit 932.
5 3 intercept 269.
6 3 vwg_oit 943.
7 4 intercept 201.
8 4 vwg_oit 1021.
9 5 intercept 277.
10 5 vwg_oit 955.
# ℹ 1,990 more rows
c. Compute the 95% confidence interval using the percentile method.
get_confidence_interval(
boot_fits,
point_estimate = observed_fit,
level = 0.95,
type = "percentile"
)# A tibble: 2 × 3
term lower_ci upper_ci
<chr> <dbl> <dbl>
1 intercept 173. 299.
2 vwg_oit 877. 1053.
d. Interpret the confidence interval for vwg_oit in context. Use causal language.
We are 95% confident that receiving VWG OIT causes an increase of between approximately [lower bound] mg and [upper bound] mg in max tolerated wheat protein dose compared to placebo, on average. Because the interval does not include 0, this is consistent with a statistically significant treatment effect.
Option B: Hypothesis test
a. State the null and alternative hypotheses.
\(H_0\): The slope predicting max tolerated dose from VWG OIT treatment is 0 — VWG OIT has no effect on max tolerated dose, \(\beta_1 = 0\).
\(H_A\): The slope is not 0 — VWG OIT has a significant effect on max tolerated dose, \(\beta_1 \neq 0\).
b. Calculate the observed fit and simulate the null distribution using 1000 permutations.
observed_fit <- wheat_trial |>
specify(max_dose_mg ~ vwg_oit) |>
fit()
set.seed(24601)
null_dist <- wheat_trial |>
specify(max_dose_mg ~ vwg_oit) |>
hypothesize(null = "independence") |>
generate(reps = 1000, type = "permute") |>
fit()c. Visualize the null distribution and shade the p-value region.
visualize(null_dist) +
shade_p_value(obs_stat = observed_fit, direction = "two-sided")d. Calculate the p-value.
null_dist |>
get_p_value(obs_stat = observed_fit, direction = "two-sided")# A tibble: 2 × 2
term p_value
<chr> <dbl>
1 intercept 0
2 vwg_oit 0
e. State your conclusion in context.
Since \(p < 0.05\), we reject \(H_0\). We have sufficient evidence of a statistically significant treatment effect: VWG OIT causes a significant increase in maximum tolerated wheat protein dose compared to placebo.
Exercise 5: Further covariate adjustment
a. Fit a model that adds baseline_wheat_ige as an additional predictor. How does the vwg_oit estimate compare to the model in Exercise 3?
wheat_fit_adj <- linear_reg() |>
fit(max_dose_mg ~ vwg_oit + baseline_wheat_ige, data = wheat_trial)
tidy(wheat_fit_adj)# A tibble: 3 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 312. 27.8 11.2 2.22e-14
2 vwg_oit 947. 34.2 27.7 4.57e-29
3 baseline_wheat_ige -1.84 0.328 -5.62 1.31e- 6
The vwg_oit estimate is very similar to the unadjusted model. This is expected: because treatment was randomly assigned, the two groups should already be balanced on baseline_wheat_ige, so including it should not substantially change the treatment effect estimate.
b. Why might we include baseline_wheat_ige in our model even if the two groups are already balanced on this variable?
Even when groups are balanced on a covariate, including it in the model can reduce residual variance and produce more precise (narrower confidence interval) estimates of the treatment effect. In a small trial like this (n = 46), such precision gains can be meaningful.