
The gold standard of causal inference
2026-10-26

Source: Polack, Thomas, Kitchin, Absalon, Gurtman, Lockhart, Perez, Pérez Marc, Moreira, Zerbini, Bailey, Swanson, Roychoudhury, Koury, Li, Kalina, Cooper, Frenck, Hammitt, Türeci, Nell, Schaefer, Ünal, Tresnan, Mather, Dormitzer, Şahin, Jansen, and Gruber (2020)
How do we know it works?
Use data to discover patterns,
and the social mechanisms that bring them about.

see how randomisation fixes selection bias;
estimate a causal effect from a real experiment using OLS in R.
The integration paradox: immigrants and their descendants who are better integrated (more educated, longer resident) often report more discrimination, not less (Schaeffer and Kas, 2024).
One proposed mechanism: the better integrated consume more news, become more aware of discrimination, and so report it more often.
Research question: does consuming news increase how often immigrant minorities report discrimination?

280 estimates from 42 studies. Source: (Schaeffer and Kas, 2024)
pacman::p_load( # Load several R packages using the pacman package manager
tidyverse, # A collection of packages for data manipulation and visualization
ggplot2, # Powerful package for creating static, animated and interactive visualizations
estimatr, # Package for fast estimators for regression with weighted data
modelr, # Provides functions for modelling and prediction
kableExtra, # Enhances table creation in R
modelsummary) # Creates tables and plots to summarise statistical modelsOn a typical day, about how much time do you spend watching, reading, or listening to news about politics and current affairs? Please give your answer in hours and then minutes.
How often were you personally discriminated against in the following situations here in Germany? Discrimination means a person is treated worse than others, with no factual justification — e.g. insult, ostracism, harassment. Disadvantaging rules and laws also count. … looking for work · at work · in education
… looking for housing · with officials · in public
- Never (2) Rarely (3) Sometimes (4) Often (5) Very often
APAD <- APAD %>% # Process the APAD data
mutate(
# News consumption in minutes (e.g. 2h 30m -> 150)
news = news_hrs * 60 + news_mins,
# Binary: reads >= 15 minutes of news per day?
news_yn = case_when(
news < 15 ~ 0,
news >= 15 ~ 1,
TRUE ~ as.numeric(NA)),
# Average perceived discrimination across 6 domains
dis_index = rowMeans(
select(., dis_trainee, dis_job, dis_school,
dis_house, dis_gov, dis_public),
na.rm = TRUE),
# z-standardised version (mean 0, SD 1)
z_dis_index = scale(dis_index) %>% as.numeric())# # A tibble: 1,093 × 24
# antidiscr_law dis_index news_hrs news_mins dis_trainee dis_job dis_school dis_house dis_gov dis_public gewFAKT
# <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 5 3.17 1 0 3 3 4 5 2 2 0.105
# 2 4 1.8 1 1 5 NA 1 1 1 1 0.103
# 3 3 2 0 30 1 2 1 3 3 2 0.802
# 4 5 2.83 0 10 3 3 2 5 1 3 0.104
# 5 4 3.2 0 30 2 3 NA 5 3 3 0.639
# 6 5 2.75 2 0 NA 2 NA 4 3 2 0.2
# 7 2 1 3 58 1 1 1 1 1 1 2.42
# 8 4 1.67 5 0 3 1 1 3 1 1 0.102
# 9 5 4 1 15 4 4 4 4 4 4 6.19
# 10 4 1.33 0 30 1 2 1 1 1 2 2.08
# # ℹ 1,083 more rows
# # ℹ 13 more variables: gender <fct>, age <dbl>, imor <fct>, german <fct>, nbh_exposed <dbl>, appearance <fct>,
# # article <chr>, ment_happy <dbl>, leftright <dbl>, gen_trust <dbl>, news <dbl>, news_yn <dbl>,
# # z_dis_index <dbl>

| Reported Discr. | |
|---|---|
| (Intercept) | 2.187*** |
| (0.116) | |
| news_yn | −0.093 |
| (0.129) | |
| Num.Obs. | 1085 |
| R2 | 0.002 |
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 |
To know the causal effect, we’d compare the same person reading news vs. not.
| Person | If reads news (\(Y_1\)) | If no news (\(Y_0\)) | Causal effect |
|---|---|---|---|
| Aisha | 4 | ? | ? |
| Ben | ? | 2 | ? |
| Chen | 5 | ? | ? |
Both worlds at once is impossible. The missing cell is the counterfactual. Not knowing it is the fundamental problem of causal inference.
The raw gap between readers and non-readers mixes two things:
\[ \underbrace{Avg_{n}[Y_{1i}|D_{i}=1] - Avg_{n}[Y_{0i}|D_{i}=0]}_{\text{Difference we actually observe}} = \underbrace{\color{#901A1E}{\kappa}}_{\substack{\text{Causal}\\\text{effect}}} + \underbrace{Avg_{n}[Y_{0i}|D_{i}=1] - Avg_{n}[Y_{0i}|D_{i}=0]}_{\text{Selection bias}} \]
We want the causal effect \(\color{#901A1E}{\kappa}\). Selection bias is the contamination: the groups already differed in baseline \(Y_0\), before any news.
Discuss: do news-readers and non-readers look the same on their background traits?
0
|
1
|
||||||
|---|---|---|---|---|---|---|---|
| Mean | Std. Dev. | Mean | Std. Dev. | Diff. in Means | Std. Error | ||
| age | 36.7 | 12.3 | 43.8 | 15.4 | 7.1 | 2.2 | |
| nbh_exposed | 4.2 | 0.8 | 4.2 | 1.0 | −0.0 | 0.1 | |
| N | Pct. | N | Pct. | ||||
| imor | Child of immigrant | 64 | 29.4 | 399 | 45.6 | ||
| Immigrant | 154 | 70.6 | 476 | 54.4 | |||
| german | Ja | 89 | 40.8 | 561 | 64.1 | ||
| Nein | 129 | 59.2 | 314 | 35.9 | |||
No. Readers are older and more often German citizens. The comparison is confounded.
APAD %>% # Start with the APAD data, then pipe
# Compare readers vs non-readers on background variables
select(news_yn, age, nbh_exposed, imor, german, gewFAKT) %>%
# datasummary_balance() treats a column called `weights` as survey weights
rename(weights = gewFAKT) %>%
datasummary_balance(
formula = ~ news_yn, # Split the table by news_yn (0 vs 1)
data = .,
title = "Who reads the news? Background of readers vs non-readers",
output = "kableExtra"
)
Discuss: the red link is a correlation. Why is it not a causal effect?
Citizenship drives both news use and discrimination → it opens a backdoor path. The link is spurious.
Part 2 of 3
We intervene and let a coin decide who gets the treatment.
We don’t passively observe, but actively intervene.
We decide who gets the treatment \(D\) and who doesn’t.


Discuss: how can our intervention \(\color{red}{I}\) cut the link between treatment \(D\) and confounders \(C\)?

We flip a coin. We randomly decide who gets the treatment:
Because assignment is random, it cannot be driven by citizenship, age, or anything else.

The coin in R:
A fair coin gives everyone the same chance of treatment, regardless of who they are.
So the two groups look alike on average — on age, citizenship, and on things we can’t even measure:
And that includes \(Y_{0i}\)!
Equal baselines:
\(Avg_{n}[Y_{0i}|\text{News}=1] = Avg_{n}[Y_{0i}|\text{News}=0]\)
Randomly split subjects → both groups come from the same underlying population.
\(\rightarrow\) similar on average in every way, including their baseline \(Y_0\).
\(\rightarrow\) so \(E[Y_{0i}|D=1] = E[Y_{0i}|D=0]\), and selection bias = 0.
Beware: randomisation can still fail by chance, especially in small samples.
\[ \begin{aligned} & E[Y_{1i}|D=1] - E[Y_{0i}|D=0] \\[4pt] & = E[Y_{0i} + \color{#901A1E}{\kappa}\,|D=1] - E[Y_{0i}|D=0] \\[4pt] & = \color{#901A1E}{\kappa} + \underbrace{E[Y_{0i}|D=1] - E[Y_{0i}|D=0]}_{= \; 0 \;\text{ if randomised}} \\[4pt] & = \underbrace{\color{#901A1E}{\kappa}}_{\text{the average causal effect}} \end{aligned} \]
Randomising \(I\) means no arrow points into it.
\(\Rightarrow\) no backdoor path from \(I\) to \(Y\).
\(\Rightarrow\) no confounding, no selection bias.

Part 3 of 3
We ran an actual RCT inside the APAD survey.
The APAD survey experiment
We asked subjects to read a news article and randomly decided which one:




0
|
1
|
||||||
|---|---|---|---|---|---|---|---|
| Mean | Std. Dev. | Mean | Std. Dev. | Diff. in Means | Std. Error | ||
| age | 36.7 | 12.3 | 43.8 | 15.4 | 7.1 | 2.2 | |
| nbh_exposed | 4.2 | 0.8 | 4.2 | 1.0 | −0.0 | 0.1 | |
| N | Pct. | N | Pct. | ||||
| imor | Child of immigrant | 64 | 29.4 | 399 | 45.6 | ||
| Immigrant | 154 | 70.6 | 476 | 54.4 | |||
| german | Ja | 89 | 40.8 | 561 | 64.1 | ||
| Nein | 129 | 59.2 | 314 | 35.9 | |||
Beware: the observational split is imbalanced — the groups differed before any treatment.
Contr (N=358)
|
Treat_1 (N=375)
|
Treat_2 (N=360)
|
|||||
|---|---|---|---|---|---|---|---|
| Mean | Std. Dev. | Mean | Std. Dev. | Mean | Std. Dev. | ||
| news_yn | 0.8 | 0.4 | 0.8 | 0.4 | 0.8 | 0.4 | |
| age | 41.8 | 15.7 | 41.2 | 12.4 | 43.8 | 16.9 | |
| nbh_exposed | 4.2 | 0.9 | 4.2 | 1.1 | 4.2 | 1.0 | |
| N | Pct. | N | Pct. | N | Pct. | ||
| imor | Child of immigrant | 156 | 43.6 | 157 | 41.9 | 150 | 41.7 |
| Immigrant | 202 | 56.4 | 218 | 58.1 | 210 | 58.3 | |
| german | Ja | 215 | 60.1 | 222 | 59.2 | 213 | 59.2 |
| Nein | 143 | 39.9 | 153 | 40.8 | 147 | 40.8 | |
Randomised split: Did randomisation balance the groups?
APAD %>% # Start with the APAD data, then pipe
# Keep the treatment indicator, background variables, and survey weights
select(article, news_yn, age, nbh_exposed, imor, german, gewFAKT) %>%
# datasummary_balance() treats a column called `weights` as survey weights
rename(weights = gewFAKT) %>%
datasummary_balance(
formula = ~ article, # Split the table by the randomised treatment
data = .,
title = "Background of the randomly assigned treatment groups",
output = "kableExtra")# Weighted OLS on the survey experiment
ols <- lm_robust(dis_index ~ article, weights = gewFAKT, data = APAD)
# Same, but on the z-standardised outcome (effect in SDs)
zols <- lm_robust(z_dis_index ~ article, weights = gewFAKT, data = APAD)
modelsummary( # Regression table with readable labels
list("Discr." = ols, "Z-Discr." = zols),
stars = TRUE,
coef_map = c(
"(Intercept)" = "Intercept (Venus control)",
"articleTreat_1" = "Article on discrimination",
"articleTreat_2" = "Article on acculturation"),
gof_map = c("nobs", "r.squared"),
output = "kableExtra")| Discr. | Z-Discr. | |
|---|---|---|
| Intercept (Venus control) | 1.979*** | −0.171* |
| (0.067) | (0.074) | |
| Article on discrimination | 0.239* | 0.264* |
| (0.107) | (0.118) | |
| Article on acculturation | 0.137 | 0.151 |
| (0.118) | (0.130) | |
| Num.Obs. | 1085 | 1085 |
| R2 | 0.012 | 0.012 |
| + p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001 |
Because assignment was random, this difference is the causal effect \(\kappa\) without confounding.

plotdata <- zols %>%
tidy() %>% # Coefficients as a tibble
filter(term != "(Intercept)") %>% # Keep only the treatment effects
mutate(term = case_when( # Readable labels for the plot
term == "articleTreat_1" ~ "Read about\ndiscrimination",
term == "articleTreat_2" ~ "Read about\nacculturation"))
ggplot(plotdata, aes(x = estimate, y = term)) +
geom_vline(xintercept = 0, linewidth = 0.6, colour = "grey60") +
geom_pointrange(aes(xmin = conf.low, xmax = conf.high),
colour = "#901A1E", linewidth = 1.1, size = 1) +
geom_text(aes(label = sprintf("%+.2f", estimate)),
vjust = -1.1, size = 5, colour = "#901A1E") +
scale_x_continuous(limits = c(-0.15, 0.6)) +
labs(x = "Effect of news article compared to control n \n in standard deviations of the outcome", y = NULL,
title = "Reported discrimination") +
theme_minimal(base_size = 16) +
theme(panel.grid.major.y = element_blank(),
plot.title = element_text(face = "bold", size = rel(1.05)))
Source: Schaeffer and Kas (2024)
Effects of experimentally-induced awareness of discrimination

Point estimates with 90% and 95% confidence intervals, post-stratification-weighted OLS with (cluster-)robust standard errors.

Most of sociology can’t run experiments.
Over the next weeks you learn what we can do instead.
Look back at the goals from the start of the lecture. Can you tick all three?
lm_robust() on an experiment in R and read the coefficient as a causal effect.Anything feel shaky? That is what this week’s Absalon quiz and the Friday exercise class are for.
estimatr::lm_robust(y ~ d, weights = ...): the RCT effect — a bivariate OLS of the outcome on the randomly assigned treatment (survey-weighted here).modelsummary::datasummary_balance(~ d, data = ...): the balance table — check treatment and control look alike on background traits (the payoff of randomisation).modelsummary(list("Model" = ...), stars = TRUE): a clean regression table.rowMeans(across(...)): average several survey items into one index.Polack, F. P., S. J. Thomas, N. Kitchin, et al. (2020). “Safety and Efficacy of the BNT162b2 mRNA Covid-19 Vaccine”. In: New England Journal of Medicine, pp. 2603-2615.
Schaeffer, M. and J. Kas (2024). “The integration paradox: Does awareness of the extent of ethno-racial discrimination increase reports of discrimination?” In: Political Psychology.
Schaeffer, M., J. Kas, and P. Hagedorn (2023). “The Association between Actual and Perceived Discrimination (APAD): Technical Report”. In: SocArXiv.

Lecture 6 · Randomised Controlled Trials