Chapter 1
The causal–Bayesian workflow
Lectures 1–2 · small worlds, graphs, priors and posteriors: the loop that every later chapter runs
In November 2020, a pharmaceutical company announced that its new COVID-19 vaccine was about 95% effective. The number came from 170 sick people. Eight of them had received the vaccine and 162 had received a placebo injection. From those two counts, and from a careful plan written down months earlier, the company's statisticians produced a statement that billions of people would act on: given these data, the probability that the vaccine's efficacy exceeds 30% is essentially 1.
That statement is Bayesian. It is a probability about something we cannot see (the vaccine's true efficacy), computed from something we can see (the counts), using rules stated in advance. You will reproduce it, exactly, in Section 1.2. But the more important lesson of this chapter is what stands behind such a number. Two kinds of assumption make it meaningful. One is a set of claims about what causes what: that the vaccine was assigned by a coin toss, so nothing else differs systematically between the two groups. The other is a set of claims about how the data were generated and what was plausible beforehand. Make the first kind of assumption wrongly and the most precise posterior in the world answers a question nobody asked. Make the second kind wrongly and the posterior answers the right question badly.
This course is about doing applied Bayesian research well enough to publish it and to rely on it. This chapter sets out the way of working that every later chapter follows. It has two halves that must be learned together:
- a causal half, in which we make our assumptions about the world explicit as a graph, and use the graph to decide which model to fit;
- a Bayesian half, in which we turn those assumptions, plus data, into a posterior distribution, and then check whether the result could plausibly have produced the data we saw.
Neither half is much use alone. A graph with no probability model cannot be fitted. A probability model with no graph can be fitted, but it cannot be interpreted. Here is the route through the chapter.
- Sections 1.1 to 1.3 set up the Bayesian half: what a model is, how a posterior is computed, and how to read one. The running example is the 2020 vaccine trial.
- Sections 1.4 to 1.6 set up the causal half: directed acyclic graphs, the four structures every graph is built from, and the back-door criterion that tells you which variables belong in a model.
- Sections 1.7 and 1.8 put the two halves together in one complete analysis of Antarctic penguins: simulate the priors, fit the model, check it, find what is wrong, and fix it.
- Section 1.9 draws the whole thing as a loop, and shows what the first step looks like for a real research project with ten variables.
Think About It: Before You Start
Think of a claim you have read recently of the form "X increases Y by Z%". What would have to be true about how the data were collected for that sentence to describe what would happen if you changed X yourself? Keep your answer in mind; Section 1.4 gives you a precise way to write it down.
Lecture 1 — Small worlds and the Bayesian update
1.1 Small worlds and large worlds
A statistical model is a small world: a self-contained, fully specified cartoon of reality inside which every event has a probability and every question has an answer. The large world is the messy reality the data actually came from. All inference happens in the small world. Its conclusions are only as trustworthy as the resemblance between the two.
That is why a model can be computed perfectly and still be wrong, and why most of the intellectual work in applied Bayesian analysis is not computation. It is construction (deciding what to put into the small world) and criticism (checking afterwards whether the small world could plausibly have produced what we saw). Computers have made the middle step, computing the posterior, almost free. They have done nothing for the first and last steps, which is where analyses succeed or fail.
Every model in this course has three layers, and we will write each one down explicitly.
- A causal layer: a graph saying what influences what. It decides which variables enter the model and what their coefficients mean.
- A stochastic layer: a likelihood, saying how the data are generated if we knew the unknowns, and a prior, saying which values of the unknowns are plausible before we see the data.
- A computational layer: the algorithm that turns the first two layers into a posterior distribution.
Most failures of applied analysis are failures of layer one disguised as questions about layer three. "Should I run the sampler longer?" is rarely the question that matters. "Should this variable be in the model at all?" often is.
Definition: Likelihood, Prior, Posterior
Let be the unknown quantities (parameters) and the data.
- The likelihood is the probability of the data we observed, as a function of the unknowns. It is the small world's account of how data are produced.
- The prior is a probability distribution over the unknowns, describing what is plausible before seeing .
- The posterior is the distribution of the unknowns after seeing . Bayes' theorem gives it:
The denominator does not depend on ; it only makes the posterior integrate to 1. So the working version is posterior ∝ likelihood × prior.
The posterior is the entire output of a Bayesian analysis. It is not an estimate; it is a distribution, a weighting over every version of the small world that is consistent with the data and the prior. Point estimates and intervals are summaries we extract from it at the end, and we will resist collapsing it to a single number until the last possible moment.
History: An Essay Published by a Friend
Thomas Bayes, a Presbyterian minister in Tunbridge Wells, never published the theorem that carries his name. After his death in 1761, his friend Richard Price found the manuscript among his papers, edited it, and read it to the Royal Society in 1763 as An Essay towards solving a Problem in the Doctrine of Chances. Bayes's problem was exactly the vaccine problem: given the number of times an event has happened and failed, what is the probability that its chance lies between two given values? The general machinery was built independently, and far more completely, by Pierre-Simon Laplace, who from 1774 onward used it to estimate everything from the ratio of boys to girls born in Paris to the masses of Jupiter and Saturn. For most of the twentieth century the approach was out of favour, partly for philosophical reasons and partly because the integral in the denominator could rarely be computed. The second obstacle fell in the 1990s, when simulation methods (Chapter 5) made almost any posterior computable.
Key Takeaways
- A model is a small world. Inference is exact inside it and only as good as its resemblance to the large world.
- Every model has a causal, a stochastic and a computational layer. Write all three down.
- Posterior ∝ likelihood × prior. The posterior is a distribution, not a number.
1.2 A first Bayesian analysis: did the vaccine work?
The data and the question
The trial of the Pfizer–BioNTech vaccine (Polack et al., 2020) randomised about 43,000 adults to two injections of either the vaccine or a saltwater placebo, in equal numbers. Then everyone went back to their lives, and the investigators waited for people to catch COVID-19. By the final analysis, 170 participants had developed symptomatic, laboratory-confirmed COVID-19 at least a week after their second dose. Eight were in the vaccine group. 162 were in the placebo group.
The question is: how effective is the vaccine? Vaccine efficacy is defined as the proportional reduction in the rate of disease:
A VE of 0 means the vaccine does nothing. A VE of 1 means it prevents every case. A negative VE means it makes disease more likely. Regulators had set the bar in advance: a vaccine would be considered effective if VE was convincingly above 30%.
The causal sketch
Before any probability, a graph. The question is causal: what would the rate of disease be if a person received the vaccine, compared with if they did not? Many things affect whether a person catches COVID-19: their age, their job, how many people they meet, how careful they are. Call all of that . In an observational study, would also affect whether people chose to be vaccinated (cautious people get vaccinated and avoid crowds), and the comparison would be hopelessly confounded. Randomisation deletes that arrow.
That one missing arrow, from into , is the entire reason the trial's numbers can be read causally. We will come back to this in Section 1.6. For now, note that the graph has already done its work: it has told us that a simple model comparing the two arms is enough.
A clever likelihood
The trial's statisticians used a neat trick. Because the two arms were the same size and followed for almost exactly the same length of time, we can condition on the total number of cases, 170, and ask a simpler question: of the people who got sick, what fraction were in the vaccine arm? Call that fraction .
If the vaccine did nothing, a sick person would be equally likely to come from either arm, and would be 1/2. If the vaccine worked perfectly, nobody in the vaccine arm would get sick, and would be 0. In general, with equal-sized arms, the case split and the efficacy are linked by
Where does that formula come from?
Let the rate of disease be per person-year among the unvaccinated and among the vaccinated. With equal person-time in each arm, the expected numbers of cases are and . The probability that a given case came from the vaccine arm is the vaccine arm's share of the expected cases:
Solving for VE gives the second formula. Notice that and cancel: the conditional trick removes the unknown background rate of disease from the problem entirely. (In the real trial the person-time was 2.214 versus 2.222 thousand person-years, close enough that the difference changes nothing at the precision reported.)
Given , each of the 170 cases is an independent "draw" that lands in the vaccine arm with probability . So the number of vaccine-arm cases follows a binomial distribution:
A prior written down in advance
Here is the part that made headlines among statisticians. The trial's protocol, published before the data came in, specified the prior:
The strange first parameter was chosen for a reason. The mean of a Beta distribution is , and plugging into the formula above gives VE = 30%. So the prior is centred on the regulators' minimum acceptable efficacy, and the total "weight" makes it worth less than two observations. It is deliberately modest: the prior is centred on "just barely good enough", and is weak enough that the data will decide.
The update
The Beta distribution and the binomial likelihood fit together perfectly. If the prior is Beta and we observe successes in trials, the posterior is again a Beta distribution:
Result: Beta–Binomial Conjugacy
If and , then . The prior acts like earlier "successes" and earlier "failures", which are simply added to the observed counts.
Up to constants that do not involve , the prior density is and the likelihood is . Their product is
which is, up to a constant, the density of a Beta distribution. Since the posterior must integrate to 1, that constant is forced, and the posterior is exactly that Beta distribution. ∎
So the vaccine posterior is . Every question about efficacy can now be answered by transforming this distribution. The easiest way to do that is by simulation: draw many values of , convert each one to VE, and summarise the draws. Here is the first R code of the course. It starts by loading the packages we will use throughout and a plotting theme that matches these notes.
library(tidyverse) # data wrangling and ggplot2
library(brms) # Bayesian regression models, fitted with Stan
library(dagitty) # causal graphs
# colours and a ggplot2 theme matching these notes
harbour <- c(ink = "#10191d", muted = "#4a5f68", grid = "#d5e3e6",
sky = "#1d5187", amber = "#805907", rose = "#ad3b28",
moss = "#1d6b54", lilac = "#5b3a94")
theme_harbour <- function(base_size = 11) {
theme_minimal(base_size = base_size) +
theme(text = element_text(colour = harbour[["ink"]]),
axis.text = element_text(colour = harbour[["muted"]]),
panel.grid.major = element_line(colour = harbour[["grid"]]),
panel.grid.minor = element_blank(),
strip.text = element_text(colour = harbour[["ink"]], hjust = 0),
legend.position = "top")
}
theme_set(theme_harbour())
Now the analysis. Read it line by line: it is the whole of Bayesian inference in six lines.
a_prior <- 0.700102; b_prior <- 1 # the protocol's prior on theta
y <- 8; n <- 170 # cases in the vaccine arm, total cases
a_post <- a_prior + y; b_post <- b_prior + n - y
ve <- function(theta) (1 - 2 * theta) / (1 - theta) # theta -> efficacy
set.seed(2020)
ve_draws <- ve(rbeta(100000, a_post, b_post)) # 100,000 posterior draws of VE
round(quantile(ve_draws, c(0.025, 0.5, 0.975)), 3)
mean(ve_draws > 0.30) # Pr(VE > 30% | data)
pbeta(0.7 / 1.7, a_post, b_post, lower.tail = FALSE) # exact Pr(VE <= 30% | data)
2.5% 50% 97.5%
0.903 0.949 0.976
[1] 1
[1] 1.96e-28
The posterior median efficacy is 94.9%, and the central 95% of the posterior lies between 90.3% and 97.6%. The interval is exactly the 95% credible interval reported to the US Food and Drug Administration in December 2020 (FDA, 2020): 95.0% (90.3%, 97.6%). The point estimate of 95.0% there is not a posterior median but the observed efficacy, one minus the ratio of the two arms' case rates per person-year, ; the exact posterior median is 94.9%. None of our 100,000 draws fell below 30%. The exact calculation in the last line shows why: the posterior probability that VE is 30% or less is about . The protocol required that probability to be below 1.4%.
Picture the prior and the posterior on the scale people care about, the VE scale.
draws <- tibble(
prior = ve(rbeta(100000, a_prior, b_prior)),
posterior = ve_draws
) |>
pivot_longer(everything(), names_to = "distribution", values_to = "VE") |>
filter(VE > -1)
draws$distribution <- factor(draws$distribution, levels = c("prior", "posterior"))
ggplot(draws, aes(VE, fill = distribution, colour = distribution)) +
geom_density(alpha = 0.35, adjust = 1.2, linewidth = 0.8) +
geom_vline(xintercept = 0.3, linetype = "dotted", colour = harbour[["rose"]]) +
facet_wrap(~ distribution, ncol = 1, scales = "free_y") +
scale_fill_manual(values = c(prior = harbour[["amber"]], posterior = harbour[["sky"]])) +
scale_colour_manual(values = c(prior = harbour[["amber"]], posterior = harbour[["sky"]])) +
scale_x_continuous(labels = scales::percent) +
labs(x = "vaccine efficacy", y = "density") +
theme(legend.position = "none")
Notice the shape of the prior on this scale. Efficacy can be at most 100%, but it is unbounded below: a vaccine that tripled your risk would have VE = −200%. The protocol's prior gives such catastrophes some weight (its 2.5th percentile is around −2,600%), but it also gives just over half its weight to efficacy above 40%. A prior that looks modest on one scale can look strange on another, and that is a lesson we will meet again in Chapter 2.
Now experiment with the update yourself. The widget below performs exactly the calculation above, but lets you change the data and the prior.
Three things are worth trying. First, set both counts to zero: the posterior is the prior, because there are no data. Second, replay the trial and watch how quickly the posterior abandons the prior: after twenty or thirty cases, the choice of prior barely matters. Third, switch to the sceptical prior, which is centred on VE = 0 and worth 40 observations, and lower the case counts to something small like 2 and 15. With few data, the prior visibly holds the posterior back. Priors matter most when data are scarce, which is exactly when you should think hardest about them.
Research Practice: Decide Before You Look
The trial's statistical analysis plan stated the prior, the likelihood, the number of cases at which the data would be analysed, and the success criterion (posterior probability that VE > 30% above 98.6% at the final analysis) before any results were seen. That is why nobody could accuse the company of choosing the prior that gave the answer they wanted. For your own work:
- Pre-register confirmatory analyses (for example on OSF or AsPredicted, or as a registered report): the question, the graph, the model, the priors, and the decision rule.
- Keep exploratory analyses, which are valuable, clearly labelled as exploratory.
- If you change the plan after seeing data, say so and say why. Reviewers forgive changes they are told about; they do not forgive changes they discover.
Check Yourself
If the vaccine had no effect at all (VE = 0), what value would , the share of cases in the vaccine arm, take?
Use , or just think: if the vaccine does nothing, is a sick person more likely to come from one arm than the other?
With VE = 0, : the arms are the same size and equally at risk, so a case is equally likely to be in either.
That is the prior. The posterior must add the data.
Check which count goes with which parameter. The first parameter collects the vaccine-arm cases.
The total number of cases is 40, but the parameters add the two counts separately.
Add the vaccine-arm cases (successes, ) to the first parameter and the placebo cases () to the second: Beta(0.700102 + 4, 1 + 36).
Try it in the widget: compare 4 and 81 with 8 and 162.
Both Beta parameters grow, and the variance of a Beta distribution falls roughly like . More data, same proportion, less uncertainty.
Key Takeaways
- A Bayesian analysis needs a likelihood and a prior; the posterior follows from Bayes' theorem.
- For a binomial count with a Beta prior, the posterior is Beta with the counts added to the prior parameters.
- To learn about any function of a parameter (such as VE), transform posterior draws and summarise them.
- The 2020 vaccine analysis gives VE = 94.9% (95% interval 90.3% to 97.6%), with posterior probability of VE ≤ 30% around .
1.3 Reading a posterior
A posterior is a distribution, and a distribution answers many questions at once. Three summaries cover most needs.
- A central value, usually the posterior median or mean: a single "best guess". For the vaccine, the median VE is 94.9%.
- A credible interval: a range containing a stated share of the posterior. The 95% equal-tailed interval runs from the 2.5th to the 97.5th percentile of the draws: 90.3% to 97.6%.
- Posterior probabilities of statements: the share of draws for which a statement is true. Pr(VE > 30% | data) ≈ 1. Pr(VE > 95% | data) is about one half.
Definition: Credible Interval
A credible interval for is an interval with . It is a direct probability statement about the unknown, given the model and the data.
The phrase "given the model" carries a lot of weight. A credible interval is a statement in the small world. If the likelihood is wrong, or the prior is absurd, or (most often) the causal assumptions that make the parameter meaningful are false, the interval is a precise answer to the wrong question. That is not a weakness peculiar to Bayesian methods. A frequentist confidence interval rests on the same model; it just states its conclusion differently.
Credible intervals and confidence intervals: what is the difference?
A 95% confidence interval comes from a procedure that, if the study were repeated many times, would produce intervals covering the true value 95% of the time. Any single interval either contains the truth or it doesn't; the 95% describes the procedure, not this interval. A 95% credible interval says that, given the model, prior and data, there is a 95% probability that the parameter lies inside it.
In large, well-behaved problems with weak priors the two kinds of interval are numerically almost identical, and the difference is one of interpretation. They differ in small samples, with strong priors, near boundaries (efficacy cannot exceed 100%), and when the question is a probability of a hypothesis, which only the Bayesian interval can answer directly. The FDA subgroup analyses in Chapter 2 show a case where they differ a great deal.
Two habits will serve you well. First, report the distribution, not just a point: a posterior median without an interval is a sentence with its verb missing. Second, prefer probabilities of substantive statements to tests against zero. "There is a 97% probability that the programme raised earnings by more than $500" is something a policymaker can use. "The effect is significantly different from zero" is not.
Research Practice: Reporting a Posterior
The Bayesian Analysis Reporting Guidelines (BARG; Kruschke, 2021) are the closest thing the field has to a standard checklist. For each key parameter or effect, a publishable report gives:
- a central value and a credible interval, saying which kind (equal-tailed or highest-density) and what probability;
- posterior probabilities for the substantive claims the paper makes, or for a region of practical equivalence around "no effect";
- enough precision to be useful but not more than the simulation supports (two or three significant figures, not seven);
- a figure of the posterior for the headline quantity, as above.
Check Yourself
The interval itself is fine; look at the interpretation.
Credible intervals are perfectly allowed to be asymmetric; the posterior is skewed.
Credible intervals make a probability statement about the parameter, conditional on the model. Repeated-sampling coverage is the defining property of confidence intervals.
It says that, if the model is right (the vaccine was randomly assigned, the cases were counted correctly, and the binomial model fits), the data leave essentially no doubt that the vaccine prevents more than 30% of symptomatic cases among people like those in the trial. It does not say how long protection lasts, whether it works against new variants, how it performs in groups the trial barely included, or anything about side effects; those are questions the model was not built to answer.
Key Takeaways
- Summarise a posterior with a central value, a credible interval and probabilities of substantive statements.
- Every posterior statement is conditional on the model, including its causal assumptions.
1.4 Drawing assumptions as a graph
The vaccine analysis was easy to interpret because of a coin toss. Most data are not like that. Most data come from observing the world as it is, where the things we want to compare differ in many ways at once. To reason about such data we need a precise language for causal assumptions. This course uses the directed acyclic graph, or DAG.
Definition: Directed Acyclic Graph (DAG)
A DAG is a set of variables (nodes) joined by arrows (directed edges), with no way to follow the arrows from a node back to itself. An arrow is a claim: if we could intervene and change , would tend to change. The absence of an arrow is also a claim, and usually the stronger one: that has no direct effect on .
We draw observed variables as solid circles and unobserved ones as dashed circles.
A DAG is a compact, checkable statement of a scientific theory. It is not a statistical model: it says nothing about the size or shape of any effect. It says only what can affect what. That turns out to be enough to decide which statistical model to fit, and that is the remarkable fact this course is built on.
An example: penguin bills
The palmerpenguins data (Horst, Hill and Gorman, 2020) record measurements of 344 penguins of three species (Adélie, Chinstrap and Gentoo) collected from 2007 to 2009 by Kristen Gorman at Palmer Station, Antarctica. Two of the measurements describe the bill: its length and its depth (top to bottom). Is a longer bill a shallower one?
library(palmerpenguins)
bills <- penguins |> drop_na(bill_length_mm, bill_depth_mm)
# one regression line through all 342 penguins
coef(lm(bill_depth_mm ~ bill_length_mm, data = bills))
# a separate line for each species
bills |>
group_by(species) |>
summarise(n = n(), slope = coef(lm(bill_depth_mm ~ bill_length_mm))[2])
(Intercept) bill_length_mm
20.88547 -0.08502
# A tibble: 3 x 3
species n slope
<fct> <int> <dbl>
1 Adelie 151 0.179
2 Chinstrap 68 0.222
3 Gentoo 123 0.205
Pooling all the penguins, each extra millimetre of bill length goes with a bill about 0.085 mm shallower. Within every one of the three species, each extra millimetre goes with a bill about 0.18 to 0.22 mm deeper. The sign flips. This is Simpson's paradox, and it is not a paradox at all once you draw the graph.
species_cols <- c(Adelie = harbour[["sky"]], Chinstrap = harbour[["amber"]],
Gentoo = harbour[["moss"]])
ggplot(bills, aes(bill_length_mm, bill_depth_mm)) +
geom_point(aes(colour = species), alpha = 0.6, size = 1.6) +
geom_smooth(method = "lm", formula = y ~ x, se = FALSE,
colour = harbour[["muted"]], linetype = "dashed") +
geom_smooth(aes(colour = species), method = "lm", formula = y ~ x, se = FALSE) +
scale_colour_manual(values = species_cols) +
labs(x = "bill length (mm)", y = "bill depth (mm)", colour = NULL)
Species affects both measurements. Gentoo penguins have long, shallow bills; Adélie penguins have short, deep ones. Within a species, bigger birds have bigger bills in every direction, so length and depth rise together. Here is that story as a graph:
There is no arrow between length and depth. Both associations, the negative pooled one and the positive within-species one, are produced by common causes. Pooling mixes the between-species pattern (driven by ) with the within-species pattern (driven by ). Separating by species removes the first and leaves the second. Neither line measures what would happen if you could stretch a penguin's bill.
History: Arrows Before Algebra
The first causal diagrams in science were drawn by the American geneticist Sewall Wright in 1920 and 1921, to explain the coat colours of guinea pigs. His "path analysis" attached a number to each arrow and showed how correlations between variables could be decomposed along the paths connecting them. Statisticians largely ignored the method for half a century; economists and sociologists borrowed it as structural equation modelling (Chapter 13). The modern theory came from computer science. In the 1980s and 1990s Judea Pearl, with Peter Spirtes, Clark Glymour, Richard Scheines and others, worked out exactly what a graph implies about which variables are statistically independent, and when a causal effect can be computed from observational data. Pearl received the Turing Award for this work in 2011.
Check Yourself
The graph makes no claim about the sizes of effects, only about which effects exist.
Body size is drawn dashed: it is in the graph but unmeasured.
The missing arrow between L and D is the claim: neither measurement causes the other. Graphs say nothing about effect sizes, and an unmeasured variable still has consequences.
Species is the common cause that differs a lot between groups; conditioning on it removes the between-species pattern.
Key Takeaways
- A DAG states which variables can affect which. Missing arrows are claims too.
- The same data can show opposite associations depending on what you condition on. Only a causal argument says which, if either, answers your question.
Lecture 2 — Graphs, priors and the workflow
1.5 The four elementary structures
Every DAG, however large, is built from four local patterns. Knowing how each behaves is most of what causal reasoning requires in practice. Let and be two variables whose association we are studying, and a third.
Definition: Fork, Pipe, Collider, Descendant
- Fork (common cause): . affects both, so and are associated even though neither affects the other. Conditioning on removes the association. This is confounding.
- Pipe (mediator, or chain): . carries the effect of on to . Conditioning on blocks the effect: the model then reports only the part of 's effect that does not go through .
- Collider (common effect): . and are not associated through , until you condition on . Conditioning creates an association between two unrelated causes.
- Descendant: a variable caused by . Conditioning on partly does whatever conditioning on would do, because carries information about .
The fork and the pipe behave identically in the data: in both, and are associated, and the association disappears when you hold fixed. The collider is the opposite: no association until you hold fixed. The collider deserves special attention, because it is the structure that defeats intuition most reliably.
Suppose a city's restaurants survive if they are either genuinely good or cheaply located, and that food quality and rent are unrelated across all the restaurants that ever open. Among surviving restaurants, the only ones you can sample, a negative association appears: the expensive survivors must be good, and the mediocre survivors must be cheap. Survival is a collider, quality → survival ← cheap location, and sampling only survivors conditions on it. The association is created entirely by the selection. Explore all four structures in the widget.
The practical warning is blunt. "Control for more variables" is not a safe default. Conditioning on a variable is the right move for a fork and the wrong move for a collider, and only the graph, not the data, tells you which you are facing. The data look the same either way.
History: Berkson's Hospital, and a Pandemic Paradox
In 1946 the Mayo Clinic statistician Joseph Berkson pointed out that two diseases unrelated in the population can appear associated among hospital patients, because having either disease raises your chance of being admitted. Admission is a collider. Seventy-four years later the same mechanism struck again. Early COVID-19 studies, drawn from people who had been tested or hospitalised, reported that smokers seemed protected against severe disease. Griffith and colleagues (2020) showed how this could be pure collider bias: being tested or admitted depended both on smoking-related illness and on COVID-19 severity, so studying only those people manufactured a spurious protective association. The lesson is now a standard reviewer question: how did units get into your sample, and does that process depend on both the exposure and the outcome?
Check Yourself
Hiring depends on both traits. What kind of structure is talent → hired ← conscientiousness?
Hiring is a collider. An employee who is not very talented must be conscientious to have been hired, and vice versa, so the traits become negatively associated among employees.
Z is caused by X; it cannot be a confounder of X.
For Z to be a collider here, something else would need to point into it and also be connected to Y. Is the main structure X → Z → Y?
is a pipe. Conditioning on the mediator removes the effect that flows through it. If the question is the programme's total effect on earnings, the job should not be in the model. (If anything unmeasured affects both the job and earnings, conditioning on also opens a collider path, which is a second reason to leave it out.)
Key Takeaways
- Fork: condition to remove confounding. Pipe: conditioning removes the mediated effect. Collider: conditioning creates a spurious association. Descendant: a weaker version of conditioning on its parent.
- Selection into a sample is conditioning. Ask how your units got into your data.
1.6 Paths, blocking and the back-door criterion
The four rules combine into one procedure for any graph. Between an exposure and an outcome there may be many paths: sequences of arrows connecting them, followed in any direction. A path transmits association unless something blocks it.
Definition: Blocked Path (d-separation)
Given a set of variables we condition on, a path between and is blocked if it contains
- a fork or pipe node that is in , or
- a collider that is not in and has no descendant in .
Otherwise the path is open. If every path between and is blocked, and are d-separated by , and the graph implies that they are independent given .
Paths come in two kinds. A causal path follows the arrows forward from to ; it carries the effect we want. A back-door path starts with an arrow pointing into ; it carries association that is not caused by . The vaccine graph had no back-door paths because nothing pointed into . That is precisely what randomisation buys.
Result: The Back-Door Criterion (Pearl, 1993)
A set of observed variables is sufficient to estimate the total causal effect of on if
- no variable in is a descendant of , and
- blocks every back-door path between and .
Then a model of given and , averaged over the distribution of , gives the effect of setting by intervention. Chapter 13 proves this and develops what to do when no such set exists.
Condition 1 rules out mediators and their descendants (Section 1.5). Condition 2 closes the confounding. You do not have to trace paths by hand: the dagitty package (Textor et al., 2016) reads a graph and returns the sufficient adjustment sets. Here it is on a small example. Suppose we want the effect of exercise on blood pressure, where age affects both, and both affect whether a person enrolled in the study through a health-screening programme.
g <- dagitty("dag {
Age -> Exercise
Age -> BP
Exercise -> BP
Exercise -> Enrolled
BP -> Enrolled
}")
adjustmentSets(g, exposure = "Exercise", outcome = "BP")
isAdjustmentSet(g, c("Age", "Enrolled"), exposure = "Exercise", outcome = "BP")
{ Age }
[1] FALSE
The answer is to adjust for age and nothing else. Adding enrolment status, a collider, is declared invalid, and the data could never have told you that.
It is worth pausing on what has just happened, because it is the organising principle of the whole course. The back-door criterion is a statement about which variables enter the regression, and it was settled before any Bayesian machinery appeared. The causal layer chooses the model; the Bayesian layer fits it and quantifies uncertainty. Reversing the order, fitting first and interpreting causally later, is the most common way good computation produces a wrong conclusion. Collecting more data or running a longer chain cannot repair an adjustment set chosen by habit.
Research Practice: Put the DAG in the Paper
Epidemiology journals increasingly expect causal diagrams, and reviewers in economics, psychology and the social sciences increasingly ask for them. A review of 234 health-research articles that used DAGs (Tennant et al., 2021) found that more than a third did not show the graph at all, and that only 6% of those that did cited any support for its arrows. Do better:
- show the whole DAG as a figure, including unmeasured variables;
- justify each arrow, and each missing arrow that matters, with a citation or argument (a supplementary table works well);
- state the estimand (total or direct effect, for whom) and the adjustment set the graph implies;
- share the
dagittycode so readers can test alternatives.
Check Yourself
Age is not affected by exercise; it comes before it.
Age has arrows out of it, not into it from both sides.
The back-door path enters Exercise through the arrow from Age. Conditioning on the fork node Age blocks it.
1.7 Priors are assumptions, so simulate them
Now we combine the two halves in a complete analysis. The question is practical. Weighing a penguin in the field means catching it and holding it on a scale in a bag, which is stressful for the bird and awkward for the researcher. Measuring a flipper with calipers is quick. How well can we predict a penguin's body mass from its flipper length?
First the graph. Flipper length does not cause body mass: stretching a penguin's flipper will not make it heavier. Both are consequences of the bird's overall size, which depends on its species and its sex.
For prediction we do not need an arrow from to . Flipper length is useful because it is a descendant of size: it carries information about , which drives . The graph also warns us that species and sex will matter, and that a model ignoring them will mix several different relationships. We will start with the simplest model anyway, and let the checks tell us what is missing.
Measuring mass in kilograms and flipper length in millimetres, centred at 200 mm, the first model is a straight line with normal scatter:
Now we need priors for (the expected mass of a penguin with 200 mm flippers), (the change in expected mass per millimetre of flipper) and (the scatter). It is tempting to use "uninformative" priors, such as Normal(0, 10) for both. Before using any prior, we should look at what it says.
Definition: Prior Predictive Simulation
To see what a prior implies, draw parameters from the prior, push them through the model, and look at the data or relationships the model considers plausible before seeing any data. If they are absurd, the prior is absurd, however harmless it looked on the parameter scale.
set.seed(11)
n_lines <- 50
prior_lines <- tibble(
prior = rep(c("vague: Normal(0, 10) on both",
"weakly informative: Normal(4, 1), Normal(0, 0.05)"), each = n_lines),
alpha = c(rnorm(n_lines, 0, 10), rnorm(n_lines, 4, 1)),
beta = c(rnorm(n_lines, 0, 10), rnorm(n_lines, 0, 0.05))
)
ggplot(prior_lines) +
geom_segment(aes(x = 170, xend = 235,
y = alpha + beta * (170 - 200), yend = alpha + beta * (235 - 200)),
colour = harbour[["sky"]], alpha = 0.45) +
geom_hline(yintercept = c(0, 22), linetype = "dashed", colour = harbour[["rose"]]) +
facet_wrap(~ prior) +
coord_cartesian(ylim = c(-15, 35)) +
labs(x = "flipper length (mm)", y = "body mass (kg)")
# share of prior lines that imply an impossible penguin somewhere in 170-235 mm
prior_lines |>
mutate(lo = pmin(alpha - 30 * beta, alpha + 35 * beta),
hi = pmax(alpha - 30 * beta, alpha + 35 * beta)) |>
group_by(prior) |>
summarise(impossible = mean(lo < 0 | hi > 22))
# A tibble: 2 x 2
prior impossible
<chr> <dbl>
1 vague: Normal(0, 10) on both 1
2 weakly informative: Normal(4, 1), Normal(0, 0.05) 0.02
Under the vague priors, every one of the 50 lines implies penguins that weigh less than nothing, or more than any bird alive, somewhere within the range of real flipper lengths. Under the weakly informative priors, one line in fifty does. The weakly informative priors were not chosen by looking at these data. They came from things anyone could look up: penguins of these species weigh a few kilograms (so ), and across the 60 mm range of flipper lengths the mass is unlikely to change by more than a few kilograms (so , allowing slopes up to about ±0.1 kg per mm). Notice that we did not even assume the slope is positive.
Experiment with the priors yourself.
Warning: "Uninformative" Is Not Neutral
A very wide prior is not an absence of assumptions. It is the assumption that absurd values are as plausible as sensible ones. In small samples and in models with many parameters (Chapters 6 and 10), wide priors actively harm inference: they let the model chase noise, and they can make the sampler fail. Weakly informative priors regularise, ruling out the absurd while letting the data speak about everything else. Chapter 2 shows how to choose them in a principled way.
Check Yourself
Under the prior , a slope two standard deviations above zero is 0.1 kg per mm. How many kilograms would expected mass change across the 30 mm from 185 mm to 215 mm flippers at that slope?
Multiply the slope by the change in flipper length.
kg: large, but possible. Under the vague prior, a two-sd slope of 20 kg per mm would give 600 kg.
1.8 Fit, diagnose, check, revise
Fit
With the graph drawn and the priors simulated, we fit the model. The brms package translates a model formula into a program in the probabilistic language Stan, compiles it, and draws samples from the posterior using Hamiltonian Monte Carlo (Chapter 5). Treat the sampler as a black box for now; we will open it in Chapter 5.
pen <- penguins |>
drop_na(body_mass_g, flipper_length_mm, sex) |>
mutate(mass = body_mass_g / 1000, # kilograms
flipper = flipper_length_mm - 200) # mm, centred at 200
m1 <- brm(
mass ~ 1 + flipper, data = pen, family = gaussian(),
prior = c(prior(normal(4, 1), class = Intercept),
prior(normal(0, 0.05), class = b),
prior(exponential(1), class = sigma)),
seed = 1, refresh = 0, file = "fits/ch1_m1" # 'file' caches the fitted model
)
summary(m1)
Family: gaussian
Links: mu = identity; sigma = identity
Formula: mass ~ 1 + flipper
Data: pen (Number of observations: 333)
Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
total post-warmup draws = 4000
Population-Level Effects:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
Intercept 4.16 0.02 4.12 4.20 1.00 2006 1767
flipper 0.05 0.00 0.05 0.05 1.00 5705 3167
Family Specific Parameters:
Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
sigma 0.39 0.02 0.36 0.43 1.00 2157 2134
Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
Read the table from the bottom up. sigma, the typical deviation of a penguin from the line, is about 0.39 kg. The Intercept says a penguin with 200 mm flippers is expected to weigh 4.16 kg (95% interval 4.12 to 4.20). The flipper coefficient says expected mass rises about 0.05 kg, or 50 g, for each millimetre of flipper.
Diagnose
Before believing those numbers, check that the computation worked. Three columns of the summary do that job.
- Rhat compares the four independent chains with each other. If they have all explored the same distribution, Rhat is 1.00. Values above 1.01 mean the chains disagree, and the summary should not be trusted.
- Bulk_ESS and Tail_ESS are effective sample sizes: how many independent draws the correlated samples are worth, for the centre and for the tails of the distribution. A few hundred is enough for most summaries.
- Any warnings about divergent transitions (there were none) mean the sampler met geometry it could not handle (Chapter 5).
Here every Rhat is 1.00 and every ESS is in the thousands, so the computation is sound. An algorithm that fails these checks has not given you a slightly worse answer. It has given you no answer.
Research Practice: What to Report About Computation
Following Vehtari et al. (2021) and the BARG, a methods section should state: the software and version (here brms 2.20 with Stan 2.32), the number of chains, warm-up and sampling iterations, that all , the minimum bulk and tail ESS (at least 400 is a common requirement), and the number of divergent transitions (ideally zero). Put the full model code and the random seed in a public repository. Reviewers increasingly check.
Check
The computation worked. Does the model? A posterior predictive check draws parameters from the posterior, simulates new datasets, and compares them with the data actually observed. If the model is a good small world, its simulated penguins should look like the real ones in every way we care about. Our graph told us that species and sex matter, so a natural check is: does the model reproduce the average mass of each species and sex?
We also fit the model the graph suggests, adding species and sex, so that the check can compare the two.
m2 <- brm(
mass ~ 1 + flipper + species + sex, data = pen, family = gaussian(),
prior = c(prior(normal(4, 1), class = Intercept),
prior(normal(0, 1), class = b),
prior(normal(0, 0.05), class = b, coef = flipper),
prior(exponential(1), class = sigma)),
seed = 1, refresh = 0, file = "fits/ch1_m2"
)
round(fixef(m2), 3)
Estimate Est.Error Q2.5 Q97.5
Intercept 3.643 0.047 3.548 3.732
flipper 0.020 0.003 0.014 0.026
speciesChinstrap -0.089 0.046 -0.178 0.001
speciesGentoo 0.829 0.084 0.667 0.997
sexmale 0.528 0.038 0.452 0.602
group_means <- function(fit) {
yrep <- posterior_predict(fit, ndraws = 500) # 500 simulated datasets
grp <- paste(pen$species, pen$sex)
sims <- apply(yrep, 1, function(y) tapply(y, grp, mean))
tibble(group = rownames(sims),
lo = apply(sims, 1, quantile, 0.05),
hi = apply(sims, 1, quantile, 0.95))
}
set.seed(3)
checks <- bind_rows("flipper only" = group_means(m1),
"flipper + species + sex" = group_means(m2), .id = "model")
observed <- pen |> group_by(group = paste(species, sex)) |> summarise(mass = mean(mass))
ggplot(checks, aes(y = group)) +
geom_linerange(aes(xmin = lo, xmax = hi, colour = model), linewidth = 3,
position = position_dodge(width = 0.55)) +
geom_point(data = observed, aes(x = mass), shape = 18, size = 3.6,
colour = harbour[["ink"]]) +
scale_colour_manual(values = c("flipper only" = harbour[["amber"]],
"flipper + species + sex" = harbour[["sky"]])) +
labs(x = "average body mass (kg)", y = NULL, colour = NULL)
# the same comparison as a table
checks |>
left_join(observed, by = "group") |>
mutate(inside = mass >= lo & mass <= hi) |>
select(model, group, observed = mass, lo, hi, inside) |>
arrange(group, model) |>
print(n = 12, width = 82)
# A tibble: 12 x 6
model group observed lo hi inside
<chr> <chr> <dbl> <dbl> <dbl> <lgl>
1 flipper + species + sex Adelie female 3.37 3.32 3.47 TRUE
2 flipper only Adelie female 3.37 3.47 3.64 FALSE
3 flipper + species + sex Adelie male 4.04 3.94 4.09 TRUE
4 flipper only Adelie male 4.04 3.69 3.88 FALSE
5 flipper + species + sex Chinstrap female 3.53 3.29 3.50 FALSE
6 flipper only Chinstrap female 3.53 3.63 3.88 FALSE
7 flipper + species + sex Chinstrap male 3.94 3.98 4.18 FALSE
8 flipper only Chinstrap male 3.94 4.05 4.27 FALSE
9 flipper + species + sex Gentoo female 4.68 4.65 4.82 TRUE
10 flipper only Gentoo female 4.68 4.70 4.89 FALSE
11 flipper + species + sex Gentoo male 5.48 5.36 5.52 TRUE
12 flipper only Gentoo male 5.48 5.13 5.34 FALSE
The flipper-only model fails the check badly. It predicts that female Adélies average 3.47 to 3.64 kg (they actually average 3.37 kg), that male Adélies average 3.69 to 3.88 kg (they average 4.04 kg), and that male Gentoos average 5.13 to 5.34 kg (they average 5.48 kg). Look at the pattern: every female group is over-predicted, and the heavier Adélie and Gentoo males are under-predicted. The model is not merely noisy; it is systematically wrong in exactly the way the graph warned, because it has no way to know that males are heavier than females with the same flippers. The model with species and sex fits four of the six groups, and its residual misses are informative: it over-predicts Chinstrap males and under-predicts Chinstrap females, which suggests that the sex difference in mass is smaller in Chinstraps than in the other species. That calls for a species-by-sex interaction, and the loop goes round again.
Adding species and sex also changes the flipper coefficient, from 0.050 to about 0.020 kg per mm. Within a species and sex, flipper length is a weaker guide to mass than across all penguins, because much of the pooled relationship was between-group differences (the same mechanism as Simpson's paradox in Section 1.4). Neither number is "the effect of flippers on mass"; the graph told us there is no such effect. They are two different predictive summaries, answering different questions.
For prediction, we can also ask directly which model would predict new penguins better. Approximate leave-one-out cross-validation, computed by the loo package from the posterior draws without refitting the model, estimates each model's expected log predictive density for new data (Section 9.7 explains the importance-sampling idea behind it):
loo_compare(loo(m1), loo(m2))
elpd_diff se_diff
m2 0.0 0.0
m1 -93.6 11.2
The richer model is better by about 94 units of expected log predictive density, with a standard error of 11: an overwhelming difference.
Check Yourself
The diagnostics say the computation is fine. Is that the same as the model being right?
More draws give a more precise answer to the same question. Will that fix a systematic misfit?
Diagnostics check the computational layer; posterior predictive checks test the stochastic and causal layers. A perfectly computed posterior for a bad model is still a bad answer.
Species and sex are common causes (forks, via size) of flipper length and mass. The pooled slope mixes between-group and within-group relationships; conditioning removes the between-group part.
Key Takeaways
- Simulate priors before fitting. Choose priors that rule out the absurd, using knowledge you could have had without these data.
- Diagnose computation with , ESS and divergences. Check the model with posterior predictive checks aimed at what the graph says matters.
- A failed check sends you back to the model, not on to the conclusions.
1.9 The workflow loop, and graphs that are not small
Putting the pieces together gives the loop below, which follows the "Bayesian workflow" of Gelman et al. (2020). It is a loop, not a pipeline: a failed check sends you back to the graph or the likelihood, never onward to the write-up.
Three principles travel with the loop. Build models you understand: start simple and add structure when a check demands it, as we did with species and sex. Computation is part of the model: a sampler that fails to converge has given you no answer. And criticism is not optional: the posterior predictive check is where the small world is held up against the large world, and skipping it skips the only honest test the method offers.
When the graph is not small
The graphs so far have three to five nodes. That is deliberate: a small graph isolates one structure so that its rule can be seen. But a DAG drawn for a question somebody actually needs answered routinely has ten or twenty nodes, several of them unobserved, mixing all four structures. The reasoning does not change; what changes is that you can no longer trace the paths in your head.
Here is a causal sketch for a real research question: do New York City neighbourhoods receive equitable emergency medical service? Specifically, does a neighbourhood's racial and ethnic composition affect the clinical outcome of residents after a 911 call, and does it do so by way of ambulance response time ? The graph encodes that hypothesis and every rival explanation a sceptical reviewer would raise.
Read the graph as a set of claims. The latent node is the analyst's honesty made visible. Historical segregation and disinvestment shaped, together, the racial composition of neighbourhoods (), their present wealth (), and the siting of emergency infrastructure (). Population density drives traffic, distance to units and how busy ambulances are. Socioeconomic status shapes how severe calls are, through baseline health, and affects outcomes directly. Response time is produced by four operational variables and, possibly, by the exposure itself: the arrow is the disparity the project exists to detect.
The target is the total effect of on , which flows along the causal paths from through to . Before reaching for software, try to find an adjustment set by hand. Four open back-door paths leave through the latent : one through wealth (), two through wealth and call acuity ( and ), and one through where stations were built (). Conditioning on blocks the first three and conditioning on blocks the fourth, so looks like the answer. Press condition on {E, W} in the widget.
It fails. Distance to the nearest unit has two parents, structural history and population density: . So is a collider on the path , and conditioning on it opens that path (and a similar one through ). Blocking one path opened two others. This is exactly the kind of mistake that is easy to make by hand and impossible to make with software:
ems <- dagitty("dag {
L [latent]
L -> R L -> E L -> W
P -> W P -> G P -> V
E -> K E -> O
R -> RT
W -> RT K -> RT G -> RT V -> RT
K -> O RT -> O
}")
adjustmentSets(ems, exposure = "R", outcome = "O")
isAdjustmentSet(ems, c("E", "W"), exposure = "R", outcome = "O")
isAdjustmentSet(ems, c("E", "W", "P", "K", "G", "V", "RT"), exposure = "R", outcome = "O")
{ E, G, V, W }
{ E, P, W }
[1] FALSE
[1] FALSE
There are two minimal sufficient adjustment sets: (also condition on density, which closes the opened paths at their source) and (condition on traffic and availability instead). Which to use is a practical question: population density is measured precisely from census data, while traffic and ambulance availability at the moment of each call are harder to obtain. Neither set is the one that "control for everything available" would produce. That habit would condition on response time, the mediator, which erases the very effect under study, and because is a collider with five parents, it would open still more non-causal paths. Try it in the widget. The graph turns a vague instinct into a precise instruction, and the instruction is different from both the instinct and the first careful guess.
One feature of the graph carries the deepest lesson. The set works only if structural history affects outcomes through no other route. Suppose it also raises residents' chronic exposure to environmental hazards, and so directly affects health: an arrow . Press add arrow L → O in the widget. A back-door path opens that no observed variable can block, because is unobserved.
ems_lo <- dagitty("dag {
L [latent]
L -> R L -> E L -> W L -> O
P -> W P -> G P -> V
E -> K E -> O
R -> RT
W -> RT K -> RT G -> RT V -> RT
K -> O RT -> O
}")
adjustmentSets(ems_lo, exposure = "R", outcome = "O") # prints nothing: no valid set
length(adjustmentSets(ems_lo, exposure = "R", outcome = "O"))
[1] 0
No adjustment set exists. The total effect is then not identified from these observational data, however large the sample. The right response is not to delete the arrow so that the graph looks solvable. It is to draw the arrow if you believe it belongs, and to say honestly that the effect can only be bounded, or reached through other designs such as instruments, natural experiments or sensitivity analysis (Chapter 13). A graph is allowed to tell you that your question cannot be answered with the data in hand. That message, delivered before any model is fitted, is one of the most valuable things the causal layer produces, and no Bayesian computation downstream can substitute for it.
Project Connection: EMS Equity
For a publishable version of this study, the graph above is the first figure of the paper. Each arrow needs a source (the literature on redlining and health, NYC Fire Department operational reports on dispatch), and the arrow needs an explicit argument. Expect a reviewer to propose it. A strong paper estimates the effect under , checks that gives a similar answer (two valid adjustment sets that disagree are evidence against the graph), then reports a sensitivity analysis asking how strong an unmeasured path would have to be to explain the result away (Chapter 13 shows how).
Research Practice: Reproducible from the First Line
Every analysis in these notes is a script that runs from top to bottom: data are read from a stable source, random seeds are set, and fitted models are cached with file =. For a paper, also record package versions (sessionInfo(), or better, an renv lock file), keep raw data read-only and do all cleaning in code, and deposit the code and data (or instructions to obtain them) in a public archive with a DOI, such as Zenodo or OSF. A reader who cannot rerun your analysis cannot fully trust it, and increasingly neither can an editor.
Check Yourself
Is the problem with RT about measurement, or about its position in the graph?
Look at the arrow R → RT. What does that make RT?
The back-door criterion forbids descendants of the exposure, and is the main one. is a non-collider on the back-door path through station siting, so conditioning on it closes that path; because it is also a collider between and , the set must then include (or and ) as well.
: conditioning on the common effect connects and , opening and the similar path through .
Leaving out of the graph does not remove its effects from the world. Its arrows determine which back-door paths exist, and so which adjustment sets work, and whether the effect is identified at all. Drawing it (dashed) lets dagitty tell you which observed variables block its influence, or that nothing can, which is a conclusion you need to report.
Key Takeaways
- The workflow is a loop: DAG → likelihood and priors → prior check → fit → diagnose → posterior check → critique, and back again.
- For real graphs, let software (
dagitty) find adjustment sets: a variable can block one path and open another. The judgement about the graph remains yours. - A DAG can tell you an effect is not identified. Report that; do not hide it.
1.10 Exercises
Each exercise has a hint and an answer button. Try the exercise first, open the hint if you are stuck, and look at the answer only to check your own.
Exercises
A smaller trial. A vaccine trial with equal arms observes 3 cases in the vaccine arm and 17 in the placebo arm. Using the protocol prior Beta(0.700102, 1), (a) write down the posterior for ; (b) use R to compute the posterior median and 95% equal-tailed interval for VE; (c) compute Pr(VE > 30% | data). Would this trial have met the 98.6% success criterion?
Adapt the
vax_postcode withy <- 3; n <- 20. For (c), usepbeta(0.7 / 1.7, a_post, b_post).(a) Beta(3.700102, 18). (b) The posterior median VE is about 80.9%, with a 95% interval from about 46.1% to 95.0%. (c) Pr(VE > 30% | data) =
pbeta(0.7/1.7, 3.700102, 18)= 0.994, which exceeds 0.986, so the criterion would be met. But the interval is wide: twenty cases establish that the vaccine works, not how well.Prior sensitivity. Repeat Exercise 1 with a flat prior Beta(1, 1) and with the sceptical prior Beta(20, 20). Report the posterior median VE under each. Which prior moves the answer most, and why?
Change
a_priorandb_prior. The sceptical prior is worth 40 observations centred on , that is, VE = 0.Flat prior: median VE about 79.2% (95% interval 43% to 94%), almost the same as the protocol prior, because both priors are worth fewer than two observations. Sceptical prior: median about 38.2% (95% interval −3% to 64%), and Pr(VE > 30%) falls to 0.68, because the prior's 40 pseudo-cases, split evenly between the arms, outweigh the 20 real cases. With small samples, the weight of the prior relative to the data is what matters.
Name the structure. For each, name the structure (fork, pipe, collider) linking and through , and say whether you should condition on to estimate the total effect of on : (a) = years of education, = occupation, = income; (b) = ice-cream sales, = temperature, = drownings; (c) = a student's maths aptitude, = admitted to an elite university, = artistic talent (admission rewards either).
Ask in each case: which way do the arrows between and the other two variables point?
(a) Pipe, education → occupation → income: do not condition (that would remove the mediated effect). (b) Fork, sales ← temperature → drownings: condition on temperature (and you will find no effect of ice cream). (c) Collider, aptitude → admission ← talent: do not condition; among admitted students the two will look negatively associated.
dagitty practice. Write the following graph in
dagittyand find the adjustment sets for the effect of on : , , , , , with latent. Then check whether is a valid adjustment set.Use
U [latent]inside the graph string, thenadjustmentSets()andisAdjustmentSet().adjustmentSetsreturns{ C }.isAdjustmentSet(g, c("C","M"), "A", "Y")isFALSE: is a descendant of (a mediator), and it is also a collider on , so conditioning on it would open that path.Selection into the sample. A university studies only students who hold its scholarships, which are awarded for high grades or for financial need (or both). Among scholarship holders, students from wealthier families have higher grades. Draw a DAG and explain how this association could arise even if family wealth and grades are unrelated among all students.
Holding a scholarship is a selection variable. What points into it? Studying only holders means conditioning on it.
The graph is grades → scholarship ← need, with need determined by (low) wealth: wealth → need. Scholarship is a collider. Among holders, a student from a wealthy family (low need) can only have qualified through high grades, while a poorer student may have qualified through need alone. So among holders, wealth and grades are positively associated, even if they are independent in the whole student body. The association is created by the selection, not by any effect of wealth on grades.
Prior predictive in R. Using the prior predictive code in Section 1.7, try and (which forces a positive slope). For each, report the share of 1,000 simulated lines that imply an impossible penguin. Which would you use, and why?
Replace the
rnormcall forbetawithrlnorm(n_lines, -3, 0.5), and setn_lines <- 1000.With and 1,000 lines: Normal(0, 0.5) for gives about 80% impossible lines, so it is still far too wide. Lognormal(−3, 0.5), whose median is kg per mm, gives about 5%, almost all from the uncertainty in (Normal(0, 0.05) gives about 4% with 1,000 lines). If you are confident that larger penguins weigh more, the lognormal prior encodes that honestly; if you want the data alone to establish the sign, keep Normal(0, 0.05). Either is defensible if it is stated and justified.
A species-by-sex model. The posterior predictive check in Section 1.8 suggested that the sex difference in mass differs by species. Fit
mass ~ flipper + species * sexwith the same priors asm2, repeat the group-means check, and compare the three models withloo_compare. Does the interaction fix the Chinstrap misfit?Change the formula, give the fit a new
filename, and pass the new fit togroup_means().All six observed group means now fall inside the model's 90% predictive intervals, including both Chinstrap groups. The
speciesChinstrap:sexmalecoefficient is about −0.33 kg (95% interval −0.50 to −0.16): the sex difference in mass is about 0.58 kg in Adélies and Gentoos but only about 0.24 kg in Chinstraps.loo_compareprefers the interaction model tom2by about 8 units of elpd (standard error about 5): a real but modest improvement, much smaller than the jump fromm1tom2.Read a reported result. A paper reports: "Treatment reduced symptoms (β = −0.42, 95% CI [−0.80, −0.04], p = 0.03), controlling for age, baseline symptoms and adherence." Adherence was measured during treatment. Identify one causal problem and one reporting problem, and rewrite the sentence as a Bayesian analysis following the BARG.
Is adherence measured before or after treatment begins? What is missing from the report of the uncertainty?
Causal problem: adherence is post-treatment, likely a mediator (and possibly a collider with unmeasured motivation), so conditioning on it biases the total effect. Reporting problem: no statement of the estimand, the model or the priors, and a dichotomous significance claim. A better sentence: "The total effect of treatment on symptoms (adjusting for age and baseline symptoms, as implied by the DAG in Figure 1) had posterior median −0.45 (95% CrI −0.82 to −0.08) on the symptom scale; the posterior probability of a reduction larger than the minimal important difference of 0.2 was 0.91. Priors and sensitivity analyses are in the supplement."
Further reading and references
Further Reading
- McElreath, Statistical Rethinking (2nd ed., 2020), Chapters 1–2 (small worlds, Bayesian updating) and 5–6 (DAGs, the four structures, back-door paths). The most readable introduction to this way of working.
- Gelman et al., "Bayesian Workflow" (2020). The definitive account of the loop in Section 1.9, with many worked failures.
- Pearl, Glymour and Jewell, Causal Inference in Statistics: A Primer (2016). A short, rigorous treatment of d-separation and the back-door criterion.
- Kruschke, "Bayesian Analysis Reporting Guidelines" (2021). Print it and keep it next to your keyboard.
- Berkson, J. (1946). Limitations of the application of fourfold table analysis to hospital data. Biometrics Bulletin, 2(3), 47–53.
- FDA (2020). Vaccines and Related Biological Products Advisory Committee Briefing Document: Pfizer-BioNTech COVID-19 Vaccine, 10 December 2020.
- Gelman, A., Vehtari, A., Simpson, D., et al. (2020). Bayesian workflow. arXiv:2011.01808.
- Griffith, G. J., Morris, T. T., Tudball, M. J., et al. (2020). Collider bias undermines our understanding of COVID-19 disease risk and severity. Nature Communications, 11, 5749.
- Horst, A. M., Hill, A. P. and Gorman, K. B. (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package. Data originally from Gorman, Williams and Fraser (2014), PLoS ONE 9(3), e90081.
- Kruschke, J. K. (2021). Bayesian analysis reporting guidelines. Nature Human Behaviour, 5, 1282–1291.
- McElreath, R. (2020). Statistical Rethinking: A Bayesian Course with Examples in R and Stan (2nd ed.). CRC Press.
- Pearl, J. (1993). Comment: graphical models, causality and intervention. Statistical Science, 8(3), 266–269.
- Pearl, J. (2009). Causality: Models, Reasoning, and Inference (2nd ed.). Cambridge University Press.
- Polack, F. P., Thomas, S. J., Kitchin, N., et al. (2020). Safety and efficacy of the BNT162b2 mRNA Covid-19 vaccine. New England Journal of Medicine, 383, 2603–2615.
- Tennant, P. W. G., Murray, E. J., Arnold, K. F., et al. (2021). Use of directed acyclic graphs (DAGs) to identify confounders in applied health research: review and recommendations. International Journal of Epidemiology, 50(2), 620–632.
- Textor, J., van der Zander, B., Gilthorpe, M. S., Liśkiewicz, M. and Ellison, G. T. H. (2016). Robust causal inference using directed acyclic graphs: the R package 'dagitty'. International Journal of Epidemiology, 45(6), 1887–1894.
- Vehtari, A., Gelman, A., Simpson, D., Carpenter, B. and Bürkner, P.-C. (2021). Rank-normalization, folding, and localization: an improved for assessing convergence of MCMC. Bayesian Analysis, 16(2), 667–718.
- Wright, S. (1921). Correlation and causation. Journal of Agricultural Research, 20(7), 557–585.