INTERACTIVE COMPANION · METHODS · STATISTICS
The Paradox Constructor
Two machines below. The first lets you build Simpson's paradox with your hands: drag five groups around a plot and watch the line fitted to all of them disagree with every one of them. The second measures how often that disagreement turns up on its own, by simulating datasets in your browser and counting.
Both run the same arithmetic as analysis/simpsons-paradox.py, and the page opens on
the settings that produce the article's headline numbers.
Model 1. Build one by hand
Five groups sit on the plot. Each one holds points arranged on a line of slope \(b_w\), which starts at \(+1\), and each group's filled marker is its mean. Drag a marker and the group moves with it. The single line fitted to all the points is drawn heavy and dark.
The identity underneath is the one the article turns on. Write \(T_w\) for the spread of \(x\) inside groups, \(T_b\) for the spread of the group means, and \(w = T_w/(T_w+T_b)\). Then
$$b_{\text{pooled}} = w\,b_w + (1-w)\,b_b,$$with \(b_b\) the slope through the group means weighted by group size. The readout prints both the fitted pooled slope and the value this identity predicts, so you can watch them stay equal to the last decimal while everything else moves.
–
Model 2. Count how often it happens by accident
Now stop building and start counting. Each trial generates a fresh dataset: \(G\) groups totalling 240 points, every group carrying a true slope of \(+1\), residual standard deviation 1. A group-level lurking variable \(z_g\) is drawn standard normal, shifts each group's mean in \(x\) by \(c z_g\) and its level in \(y\) by \(c\theta z_g\), and the loading \(\theta\) is redrawn from \(N(0,\tau^2)\) every trial. So the confounder pushes with the real effect half the time and against it half the time, which is what “by accident” has to mean.
A trial counts as a reversal when every within-group slope has one sign and the pooled slope has the other. The gated count is stricter: every one of those slopes must also clear a two-sided \(t\) test at \(\alpha = 0.05\), so it is the rate at which a careful analyst would end up holding two confident findings pointing opposite ways.
Three things to check by hand
A model you cannot check is a cartoon. Three of the numbers on this page can be verified with arithmetic, and if any of them disagrees with what you see, the page is broken and we would like to know.
- Model 1, opening settings
- pooled slope −1.000000, within-group slopes +1.000000
- the sum behind it
- T_w = 85.714286, T_b = 600, w = 1/8, b_b = −9/7
- and so
- (1/8)(+1) + (7/8)(−9/7) = 0.125 − 1.125 = −1 exactly
- Model 1, group 1 mean
- y = −(9/7)(−4) = 36/7 = 5.142857
- Model 1, stacked preset
- T_b = 0, w = 1, so the pooled slope equals the size-weighted average of the within-group slopes exactly
- Model 2 at c = 0
- reversal rate exactly 0, however long you run it
- Model 2 at the defaults
- converges on 0.09695 ± 0.00209, the article's 20,000-trial value
- Model 2 at 30:1 sizes
- converges on 0.04855, about half the balanced rate
The last two are the only ones with sampling error in them. This page runs fewer trials than the article does, so expect the estimate to wander by a few thousandths before it settles. The band drawn around the running curve is one standard error and the dashed line is the article's answer, which carries a standard error of its own. The readout compares the two on the standard error of their difference rather than on this page's alone, because comparing an estimate to another estimate as though the second were exact is a way of manufacturing disagreements. We ran 40,000 trials of this code outside the browser and got 0.09405, which is 1.14 such standard errors from the article's 0.09695. Past three would mean one of the two implementations has a bug in it.
What this page cannot show you
It cannot tell you which slope is the right one to report. Nothing can, from the data alone. The paradox is a fact about weighted averages and carries no information about what caused what, so deciding between the within-group answer and the pooled answer needs a causal model that no amount of dragging will supply.
It also flatters the world in three specific ways. Every group here shares one slope, which real groups never do, and unequal slopes make reversals easier rather than harder. The errors are Gaussian, and heavier tails would raise every frequency on the second panel. And the groups are labelled: you can see them. The case that should actually worry a reader is the one where the confounder was never recorded, where the arithmetic is identical and the plot is a single undifferentiated cloud with one line through it.