Challenges · C09 · ★★★★☆

Job training: can observational data stand in for an experiment?

Difficulty
★★★★☆
Time
4-5 hours
Data
LaLonde (1986) / Dehejia & Wahba (1999): the randomised National Supported Work experiment, plus 15,992 comparison men from the Current Population Survey
Skills
Likelihoods for zero-heavy, long-tailed outcomes · causal DAGs and adjustment sets · g-computation with pm.observe and pm.do · propensity scores and overlap · trimming to common support · sensitivity analysis · turning a posterior into a policy statement

The brief

A labour ministry ran a job-training programme for disadvantaged workers in the mid 1970s and wants to know: did it raise participants' earnings, and by enough to justify the cost? The ministry's analysts have only what ministries usually have - records of the participants, and a large national survey of people who did not take part. Their first comparison says participants earn $8,500 a year less than non-participants, and someone has already drafted the memo closing the programme.

You hold something they do not: the programme was in fact run as a randomised experiment, so you know the answer they should get. That makes this a rare chance to grade observational causal inference against the truth - the test Robert LaLonde set in 1986, which most methods of the day failed.

Your job: establish the experimental benchmark, then put yourself in the ministry's shoes, forget the experimental controls, and see how close a careful Bayesian analysis of the observational data can get - and how you would know, without the benchmark, whether to believe it.

How this notebook works

  • Each task states what to deliver, not how. Write your code in the YOUR CODE HERE cells.
  • Stuck? h.hint("task3") reveals hints one level at a time: nudge → approach → code skeleton. Try to get by on nudges.
  • h.check("task3", att_full=...) compares your numbers with the reference solution.
  • A full worked solution lives in notebooks/solutions/. Open it only when you are done (or truly stuck).
import logging
import warnings

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm
from scipy.special import expit

from pymc_challenges import Hints, data

RANDOM_SEED = 1986
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-variat")

h = Hints("C09")
h.tasks()

C09 - Job training: can observational data stand in for an experiment?

  • task0 - Look at the experiment (2 hints)
  • task1 - The experimental benchmark (3 hints, 2 check(s))
  • task2 - The ministry's dataset and the naive comparison (3 hints, 2 check(s))
  • task3 - DAG, generative model, pm.observe and pm.do (3 hints, 1 check(s))
  • task4 - Overlap, propensity scores and trimming (3 hints, 2 check(s))
  • task5 - Sensitivity to thresholds and adjustment sets (3 hints, 1 check(s))
  • task6 - What do you tell the ministry? (3 hints, 1 check(s))

The data

nsw is the experiment: men who applied to the National Supported Work programme and were randomly assigned to training (treat = 1) or not. cps is a national sample of men from the Current Population Survey, none of whom were in the programme (treat = 0).

column meaning
treat 1 = received NSW training
age, educ age and years of schooling
black, hisp, marr, nodegree indicators: Black, Hispanic, married, no high-school degree
re74, re75 real earnings in 1974 and 1975 (USD) - before the programme
re78 real earnings in 1978 (USD) - the outcome
data.describe("nsw")
nsw = data.load("nsw")
nsw.head()
nsw: Randomised job-training experiment: 185 treated, 260 controls, earnings in 1978.
  source: LaLonde (1986) / Dehejia & Wahba (1999), National Supported Work experiment.
  url:    https://vincentarelbundock.github.io/Rdatasets/csv/causaldata/nsw_mixtape.csv
data_id treat age educ black hisp marr nodegree re74 re75 re78
rownames
1 Dehejia-Wahba Sample 1 37 11 1 0 1 1 0.0 0.0 9930.045898
2 Dehejia-Wahba Sample 1 22 9 0 1 0 1 0.0 0.0 3595.894043
3 Dehejia-Wahba Sample 1 30 12 1 0 0 0 0.0 0.0 24909.449219
4 Dehejia-Wahba Sample 1 27 11 1 0 0 1 0.0 0.0 7506.145996
5 Dehejia-Wahba Sample 1 33 8 1 0 0 1 0.0 0.0 289.789886
data.describe("cps")
cps = data.load("cps")
cps.describe().round(1)
cps: 15,992 non-experimental 'controls' with the same covariates as `nsw`.
  source: Current Population Survey comparison sample used by LaLonde (1986).
  url:    https://vincentarelbundock.github.io/Rdatasets/csv/causaldata/cps_mixtape.csv
treat age educ black hisp marr nodegree re74 re75 re78
count 15992.0 15992.0 15992.0 15992.0 15992.0 15992.0 15992.0 15992.0 15992.0 15992.0
mean 0.0 33.2 12.0 0.1 0.1 0.7 0.3 14016.8 13650.8 14846.7
std 0.0 11.0 2.9 0.3 0.3 0.5 0.5 9569.8 9270.4 9647.4
min 0.0 16.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
25% 0.0 24.0 11.0 0.0 0.0 0.0 0.0 4403.5 4398.8 5669.3
50% 0.0 31.0 12.0 0.0 0.0 1.0 0.0 15123.6 14557.1 16422.0
75% 0.0 42.0 13.0 0.0 0.0 1.0 1.0 23584.2 22923.7 25564.7
max 0.0 55.0 18.0 1.0 1.0 1.0 1.0 25862.3 25243.6 25564.7

Task 0 · Look at the experiment

Deliver

  1. A plot of the distribution of re78 in each experimental arm, and the numbers a likelihood would have to reproduce: share of exact zeros, median, mean, maximum.
  2. A table showing whether randomisation balanced the pre-treatment covariates.
  3. One sentence: which textbook likelihoods are ruled out for re78, and why?
Your turn Work this task in the notebook.

Task 1 · The experimental benchmark

Because of randomisation, the difference between the arms is the causal effect of training for men like these. Estimate it properly.

Deliver

  1. A Bayesian model of re78 by arm with the most obvious likelihood, and a posterior predictive check that shows specifically what it gets wrong.
  2. A better likelihood, with priors you can defend on the dollar scale, clean diagnostics, and a posterior predictive check that passes where the first failed.
  3. The posterior of the average treatment effect in dollars (difference in mean earnings, zeros included), with a 94% interval and the probability that it exceeds $1,000 a year. This is the benchmark for everything that follows.
Your turn Work this task in the notebook.

Task 2 · The ministry's dataset

Now forget the experimental controls. The ministry has the 185 participants and the CPS.

Deliver

  1. The observational dataset (NSW treated + all CPS men) and the naive estimate: the difference in mean 1978 earnings between participants and non-participants.
  2. A covariate balance table - means by group and standardised mean differences - side by side with the same table for the experiment.
  3. A short paragraph for the ministry explaining why the naive number says nothing about the programme. Look closely at the CPS earnings columns too: is there anything about how they were recorded that a model should know?
Your turn Work this task in the notebook.

Task 3 · A causal model, conditioned and intervened on

Deliver

  1. The causal DAG you are assuming, drawn in a markdown cell, with the adjustment set it implies and the assumptions that are not testable from these data.
  2. One generative PyMC model of the observational data - treatment assignment and 1978 earnings given the covariates - written without any observed data. Condition it on the data with pm.observe, fit it, and then use pm.do to set treat to 0 and to 1 for the participants. From those two interventions compute the posterior of the ATT (average effect on the 185 participants) by g-computation.
  3. Compare with the benchmark. How far off is it, and in which direction?

Use all 16,177 rows here. Sampling takes around a minute; storing a 16,177-long deterministic for every draw is what you need to avoid.

Your turn Work this task in the notebook.

Task 4 · Where do the counterfactuals come from?

The ATT needs, for each participant, a prediction of what he would have earned without training. Those predictions are only as good as the non-participants who resemble him.

Deliver

  1. A diagnosis: estimate every man's probability of being a participant given his covariates (the propensity score - you may already have it) and compare its distribution in the two groups. What share of the CPS has a propensity below 1%?
  2. Evidence, not using the experimental controls, that the Task 3 outcome model is wrong in the region of covariate space where the participants live.
  3. A fix, a refit with clean diagnostics, and the new ATT next to the benchmark. State exactly which population your new estimate refers to.
Your turn Work this task in the notebook.

Task 5 · How fragile is the answer?

You made choices in Task 4 that the ministry cannot audit. Show what they are worth.

Deliver a table and a plot of the ATT posterior under

  1. at least three trimming thresholds, and
  2. at least three adjustment sets on your preferred trimmed sample: all covariates, without re74, and without any pre-programme earnings.

Then answer: which choice matters most, and what does that say about the DAG in Task 3?

Your turn Work this task in the notebook.

Task 6 · What do you tell the ministry?

The ministry's economists say the programme pays for itself if it raises participants' earnings by more than $1,000 a year (an illustrative threshold).

Deliver

  1. $P(\text{effect} > \$1{,}000)$ under the experiment, under the naive full-sample adjustment, and under your preferred observational analysis.
  2. A short note to the ministry: what the observational data can and cannot support, what you would need to see before trusting an observational answer when there is no experiment to check it against, and whether the memo closing the programme should go out.
Your turn Work this task in the notebook.

Going further

  • Respect the top-coding. A quarter of CPS men have re78 recorded at the cap. Model that explicitly with a censored likelihood for positive earnings (you will have to split the hurdle into its two parts to do it). Does the full-sample estimate move? Does the trimmed one?
  • Weight instead of trim. Use the propensity scores to weight CPS men by $e(x) / (1 - e(x))$ in the outcome likelihood (pm.Potential), or combine weighting and outcome modelling into a doubly-robust estimate. Propagate the uncertainty in $e(x)$.
  • A more flexible response surface. Replace the linear predictors by an HSGP or BART-like additive model in re74, re75 and age. Does flexibility substitute for trimming, or only hide the extrapolation?
  • Unmeasured confounding. Add a sensitivity parameter: a latent binary $U$ that raises both the odds of participation and earnings. How strong must it be to push $P(\text{effect} > \$1{,}000)$ below one half?
h.progress()

C09: 0/20 hints used, 9/9 checks passed.

Reference solution

A complete, executed solution with commentary on every task — including the ways the obvious model fails. Try the hint ladder first.

Open the worked solution →