
Chris Moreh
Applied replication for data skills · a CodeMoreh workshop
A full-day workshop · four parts
You leave with: a reproduced result · an estimand and a DAG of your own · a preregistered analysis · your dot on a live class multiverse
Work along in Positron (Route 1) or in the browser lab (Route 2) – solo or in pairs
What I want you to understand
What I want you to be able to do
Do this now: Route 1 – open your workspace folder in Positron. Route 2 – open the browser lab tab and leave it loading; the engine warms while we talk.
Part 1 · 90 minutes
Making claims about the world that could turn out to be wrong – and saying how we would know.
A claim is not private. It is offered to others so they can check it, use it, argue with it, build on it.
That handover is the whole enterprise. Everything today is about whether it works.
Different fields, different designs, and in both the result depended on who ran the analysis.
The mechanism is ordinary and structural:
Ask researchers anonymously, with an incentive to answer truthfully, and a majority admit to at least one of these practices – not fraud, the ordinary kind (John et al. 2012)
None of these steps requires a dishonest person. That is exactly why the problem is hard: you cannot fix it by hiring better people.
The question. Are football referees more likely to give red cards to dark-skinned players than to light-skinned ones?
The data. One dataset, sent to everyone: 2,053 players in the top divisions of England, Germany, France and Spain, the 3,147 referees they played under, and 146,028 player–referee pairs. Skin tone rated from profile photographs by two coders who did not know the research question.
The design. Twenty-nine teams analysed it independently. They reviewed each other’s approaches before anyone had seen a result, revised, and only then compared answers.
This is the first many-analysts study. Every one since is a variation on it – including the one you are inside today.
Silberzahn et al. (2018)
Silberzahn et al. (2018), Figure 3, rebuilt from the published values at osf.io/gvm2z.
Replicationcrises
Another replication
crisis – terminological confusion?
The term reproducibility
has been used as a synonym for a number of different characteristics – reproducibility, robustness, replicability, repeatability, credibility, and trustworthiness.
Successfully rerun the same code on the same data? Re-analyse the same data a different way? Collect fresh data and analyse it in the same way? Using the same data and same analysis but asking a different question? Asking the same question but answering it with fresh data and a different analysis?
Repeatability is used when referring to reproducibility, robustness, and replicability as a constellation of concepts intended to assess whether answers to scientific questions are unchanged when different steps of the research process are repeated.
Credibility and trustworthiness refer to the confidence one can have in research findings. Because scientific knowledge claims are tentative, they are not synonymous with truth.
For example, credibility is advanced by addressing potential competing interests in the conducting and reporting of research, by taking existing knowledge into account, by using methods that have been validated, by controlling for biases in the research context, by pursuing precise and reliable evidence, and by calibrating the strength of the claims to the uncertainty of the evidence.
Nosek et al. (2025)
You submit your paper and replication package. It goes out to reviewers.
Reviewer 1wants to ensure that your results are reproducible, so she sources your R script to run the entire analysis. You’re in luck – she hasn’t yet updated her
dplyr, so the code runs.Reviewer 2is very eager. He’s unconvinced by your modelling choices, so he fits your data to another function to check if your findings are truly robust. You’re in luck again – he recovers your point estimate to a reasonable precision, although the confidence intervals have widened.
Reviewer 3takes a special interest because he’s just working on a similar paper on data they published recently. Do your results replicate when your methods are applied to that state-of-the-art dataset?
You submit your paper and replication package. It goes out to reviewers.
Reviewer 2
just doesn’t want to let go…
Reviewer 3that he just read about, if applying his new fancy method to it?
| Practice | What it makes possible |
|---|---|
| Open data | Someone else can re-analyse your evidence |
| Open code | Someone else can rerun your exact analysis |
| Open materials | Someone else can run your study again |
| Open access | Someone else can read the claim in the first place |
| Preregistration | A dated record of the plan, before the results exist |
| Registered reports | Peer review of the plan, with publication decided before results |
Each one removes a specific obstacle. None of them is a general-purpose guarantee. The programme, set out at length: (Munafò et al. 2017)
Sánchez-Tójar et al. (2026)
Miske et al. (2026).
The most substantial barrier … was the unavailability of author data.
Reproducibility success does not mean that the finding is correct.
Miske et al. (2026).
Aczel et al. (2026).
Aczel et al. (2026).
Tyner et al. (2026).
Tyner et al. (2026).
Reproducible ≠ robust ≠ replicable.
Across SCORE the three are essentially uncorrelated – knowing one barely predicts the others.
So there is no single number for trust
. You have to ask which repetition you mean.
And two things I could not see: who the other analysts were, and what they were finding.
Aczel et al. (2026). Registered protocol: osf.io/7snkz
The results show, first, that poor economic performances increase negative and decrease positive dimensions of EU framing.
Teney (2016). The full methodological reading is Part 2.
Task 1 – free reanalysis. Reanalyse the claim however you see fit. Record one categorical conclusion: does the evidence support it?
Task 2 – constrained reanalysis. Months later, three instructions arrive that fix the question:
Task 1 measures whether we agree. Task 2 makes our numbers comparable – you cannot average five answers to five different questions.
Standardising is what makes comparison possible – and it quietly assumes the five are estimating the same quantity. Hold that thought until Part 2.
Task 2 results as recorded in the public Multi100 dataset (osf.io/q5h2c).
.R script, an .Rmd, a .qmdThe software did not cause the spread. There is no Stata answer and no R answer to this claim.
But because each of us submitted a document that somebody else could open and run, it became possible, afterwards, to find out what did cause it.
That is what the toolchain is for, and it is what you spend this afternoon doing.
lm(), of feols(), of every package we load today, is publicReproducibility has a price, and a proprietary toolchain quietly puts that price on your reader.
Pythongeneral purpose, strong ML
Juliafast numerics
Positrontoday’s Route 1
VS Codeeverything, via extensions
Jupyternotebooks, Python-first
Quartotext + code → anything

Ringed in orange: what today runs on. The rest is the map, so you know what you are choosing between.
PythonA general language that also does data. Better for pipelines, scraping, text and machine learning.
JuliaFast, young, small ecosystem. Worth it when your model takes hours.
You do not have to choose once and forever. Quarto runs R, Python and Julia chunks in the same document, and today’s browser lab is R compiled to run inside a web page.
PositronThe newer IDE from Posit. R and Python as equals, VS Code underneath.
VS CodeDoes anything, once configured. Big extension ecosystem.
JupyterNotebook-first. Beware: cells can run out of order.
NeovimFor the keyboard-only. Steep, and very fast once climbed.
The editor is the least consequential choice on this slide. It changes how you work; it changes nothing about what a reader receives.
Trap worth naming: a notebook lets you run cells out of order, so it can show output that no clean run would produce. Restart and run everything before you believe it.
Positron and RStudio are both from Posit; both are free, and RStudio is open source.
.RCode only. Comments carry the prose.
.mdProse only. Headings, lists, links – no code.
.RmdProse + R, rendered together. R only.
.qmdProse + R, Python or Julia. Today’s format.
Every one of these is a plain text file. You can open it in anything, diff it, email it, and read it in twenty years. A .docx with pasted output is none of those things – and it has no idea which numbers came from which code.
You write one source file. The rendering step runs your code, drops the results in, and produces whatever output you asked for – all from the same source.
tidyverseReading, reshaping, plotting. One grammar across all of it.
easystatsModel output as tidy data: parameters, performance, effect sizes.
modelsummaryRegression tables you can publish, from the fitted objects.
marginaleffectsPredictions, contrasts and marginal effects from almost any model.
Today also loads fixest (fast fixed effects – the estimator behind our baseline), dagitty and ggdag (causal graphs). The full setup is on the setup page; the browser lab has it all preloaded.
Git is a program on your machine. It records a complete, dated snapshot every time you say so, and it never forgets one.
GitHub is a website that hosts those snapshots so other people can reach them – and it is not the only one (GitLab, Codeberg, one hosted by your university).
What it gives you that a folder of final_v3_REALLY_final.R cannot:
We use none of it today – deliberately. There is a companion module on the site, and your workspace folder is the ideal thing to practise on.
OSFProjects, files, and registrations. Free, and where our case’s trail lives.
A repository is not an archive. GitHub can be force-pushed, renamed, or deleted tomorrow. An archived deposit gets a DOI, a fixed version, and a commitment to keep serving it. Serious work usually needs both.
| What | Common choice | Why |
|---|---|---|
| Code | MIT · GPL | MIT: use it however you like. GPL: and share your changes back |
| Data | CC0 · CC BY | CC0 puts it in the public domain; CC BY asks for attribution |
| Text, figures, slides | CC BY · CC BY-NC | Attribution, optionally no commercial reuse |
| Someone else’s data | their terms | Eurobarometer is GESIS-licensed – I may not redistribute it, and neither may you |
No licence
is not a neutral default. It means all rights reserved – legally, nobody may reuse it at all.
Route 1 – Positron
fixest fits your modelsRoute 2 – the browser lab
github.com/CodeMoreh/replication-lab – download zip → unzip → open in Positron
A perfectly reproducible pipeline can be a perfectly reproducible answer to a question you never stated.
None of this software will tell you what your estimand is, whether your controls belong in the model, or whether the association you found means what you think it means.
Tooling is the cheap half of the problem. We have now done it. The rest of the day is the expensive half.
Task 1 – the setup check. Whichever route you are on, get to the point where a line of R runs and the data are in front of you.
working_script.R runs its first block · the report skeleton renderseuframes has 270 rowsTake the rest of this part and the break for it. Type a green circle in chat when you are through; type STUCK and I will come to you.
If Route 1 will not cooperate, do not fight it – switch to Route 2 and carry on. Nothing today depends on which one you use.
30 minutes – the exact return time is in the chat · keep setting up if you need to
Part 2 · 90 minutes
The results show, first, that poor economic performances increase negative and decrease positive dimensions of EU framing.
What does the EU mean to you personally?
Peace;
Democracy;
Freedom to travel, study and work anywhere in the EU;
Cultural diversity;
Stronger say in the world)
Loss of our cultural identity;
More crime;
Not enough control at external borders;
Unemployment)
Bureaucracy;
Waste of money)
Teney (2016).
… poor economic performances … decrease positive dimensions of EU framing
Teney (2016: 619)
Anchor result, Table 3 Model 1 – unemployment on cosmopolitan framing: b = −0.00340 · t = −4.03 · p < .001
Teney (2016).
Two models, one paper, and support for the claim depends on which one you read. The paper reports both, which is more than many do.
For today, that matters because the sixth analyst’s problem – which defensible specification do I report? – was already live inside the original study, before any of us arrived.
OA’s data
Today’s panel – EUframes_cy.csv
The aggregate answers the contextual question directly: does a country’s framing move when its unemployment moves? What it cannot answer is the compositional one – whether it is the same people framing differently, or a different mix of people. There is a companion module that takes that up on simulated individual data.
The required data to replicate my analyses can be downloaded on the Gesis repository website. I am not allowed to forward the Eurobarometer data. Enclosed you´ll find the code to combine the different Eurobarometer data waves and the code for the analysis of the ESR piece
The reproduction attempt failed because the authors used file names that did not correspond with the names of the files available for download. Additionally 16 datasets are listed on the website as being used but the code references 18 files
not reproduced: could neither extract nor re-calculate the original effect size
Analysis One – the focal test on which SCORE evaluates the claim
Analysis Two – the still studying
and no full-time education
categories in the variable age when finished full-time education
were not converted to missing values
Both give a positive coefficient for unemployment on cosmopolitan framing, significant at p-values of 0.05 or less, after controlling for selected predictors and for wave and country-level effects
By the criteria used by SCORE, Analysis Two did not replicate the claim
| Analyst | Free-phase predictor | Free-phase outcome | Estimator |
|---|---|---|---|
| 018OL | GDP per capita (level) | positive framing, 7-item mean | crossed random intercepts |
| KEVF1 | GDP growth | positive framing, 7-item sum | random intercepts |
| PRL47 | log GDP + growth + crisis dummy | three positive-framing shares | three-level mixed |
| KQXUE | GDP growth and unemployment | cosmopolitan + utilitarian shares | country FE |
| C6HJR | GDP growth and unemployment | positive framing, 7-item share | two-way FE |
evidence for the claim
Free-phase records: the public Multi100 dataset (osf.io/q5h2c) – categorical verdicts and free-text reports only; the models themselves read from the analysts’ public code.
Our own case has just shown us the pattern in miniature. It was described first, and at far greater scale, in the two studies that the rest of this part rests on.
Silberzahn and colleagues (2018) – the red-card study from this morning. Twenty-nine teams, one dataset, one question. Twenty of the twenty-nine intervals excluded 1; nine did not.

Auspurg and Brüderl (2021) went back through all twenty-nine of those analyses and asked a question that the original had not: what was each team actually trying to measure?
The first establishes that careful people disagree. The second explains why – and that explanation is what the whole of Part 2 is built on.
Auspurg & Brüderl re-read the 29 analyses and found four different estimands:
descriptive association · discrimination net of mediators · variance-maximising · exploratory
The dispersion was largely a question-multiverse, not an analysis-multiverse.
Auspurg and Brüderl (2021)
Auspurg and Brüderl (2021). Top row: the 29 published estimates. Bottom row: drawn to their reported median (1.28) and SD (0.06) – the 486 values are not published.
Auspurg and Brüderl’s title asks it directly: has the credibility of the social sciences been credibly destroyed?
If the twenty-nine teams answered four different questions, their disagreement is not evidence that analysis is unreliable.
It is evidence that the question was under-specified – an ordinary, fixable defect.
So this literature reads as a diagnosis of vague research questions rather than a verdict on social science.
More optimistic – and more demanding, because it hands the problem to whoever writes the question.
Auspurg and Brüderl (2021)
Lundberg et al. (2021).
X = predictor, Y = outcome. Green = the causal effect you want; coloured = the path you must reason about.

The same slope fits four different stories:
The data look identical in all four. Only an assumption about how the data arose tells them apart – that is what a DAG writes down.
Association flows along every open path, not only along the causal arrow. A fork or a chain is open until you adjust the variable sitting on it. A collider is the reverse: shut by default, and opened by the very act of adjusting for it.

Control for everythingsilently answers a question you never asked
The expected change in [unit quantity] among [target population] for a 1-percentage-point difference in unemployment, holding [assumptions] fixed.
In chat, now: your one-sentence estimand for the claim. Type it, hold it, send on go
.
Observational panel · 27 countries · no assignment mechanism · feedback between the variables · a decade of shared shocks.
Nothing in this design licenses unemployment causes a shift in EU framing
.
So why spend half an hour on causal graphs?
Because the graph does three jobs that survive the loss of identification:
The graph and the estimand are only worth anything if they are written down where someone else can read them:
In Part 4 you write exactly that down – the estimand, the graph-based justification, the one specification, and the rule for what would count as support – and you do it before you see a result.
The technical name for that is preregistration, and what it does is stop the result from choosing the question.

The same graph in R. Every arrow is a formula, read outcome-first – so a # takes an arrow out and the code still runs:
adjustmentSets()Four things and you can read most R: assign with <- · call a function with name(...) · reach into an object with $ · chain with the pipe |>.
What comes back is a data frame: a rectangle, one row per observation, one column per variable, each column of a single type. Almost everything in R takes a data frame and gives you one back.
library() makes the functions in a package available. It does not install it – that is install.packages(), and it happens once, not every time.
This is not a warm-up you skip. Every serious error I have made in twenty years of this was visible in the data before it reached a model – a duplicated year, a missing value coded as -9, a scale running 1–10 where I assumed 0–1.
Read it downwards as a sentence: take the panel, then keep 2008 onwards, then keep four columns, then add a centred unemployment column, then sort by unemployment, highest first.
The sixth verb is summarise(), usually with group_by(): collapse many rows into one per group. That is precisely how the panel you are working on was built from 390,000 respondents.
The tilde ~ reads outcome on the left, predictors on the right. Add predictors with +. The formula is an object in its own right – it does not know or care which function will eventually use it.
Almost every modelling function in R takes this same shape: something(formula, data = ...). Learn the pattern once and lm, glm, lmer, feols and betareg all become variations.
The bar | separates the regression from the fixed effects. Everything after it is absorbed: cntry gives every country its own intercept, year gives every year its own.
The equivalent with dummies would be mcosmo ~ unemp_c + factor(cntry) + factor(year) – same estimate, 36 extra rows of output you do not want to read.
This is the model you reproduce in Part 3. t = −3.804.
broom::tidy() returns the table as a data frame, so you can filter it, join it, plot it. That is how 840 model fits become one chart this afternoon.
Three parts, always: the data, the mapping (aes) from variables to visual properties, and one or more geoms that draw something. Layers stack with +.
facet_wrap is the one worth stealing today: it repeats the whole plot once per group. Twenty-seven small country panels, one line of code, and you can see which countries move.
Each line is one arrow, read outcome-first – exactly the same convention as a model formula. Which means you can comment an arrow out with a # and the code still runs.
adjustmentSets() reads the graph and tells you which variables close every back-door path. Change an arrow, rerun, and watch the answer change.
| What you see | What it usually means |
|---|---|
could not find function "feols" |
The package is not loaded – add library(fixest) above |
object 'euframes' not found |
You ran this chunk before the one that creates it – go back up and run in order |
object 'unemp_c' not found |
The column does not exist yet – you skipped the mutate() that makes it |
+ on the console, nothing happens |
An unclosed bracket or quote. Press Esc and look for the missing ) |
Read the error. R’s messages are terse but almost always literally true – it is telling you the name of the thing it could not find.
Task 2, in either route – three pieces:
# off the arrows you are willing to defend, rerun adjustmentSets(), and see what it now demandsTwo sentences for your notes: what do two-way fixed effects adjust for, and what can they not fix?
Whatever you decide here becomes the model you defend in Part 3, and the justification you preregister in Part 4.
An hour – the return time is in the chat · lunchtime browsing, if you like: the specification menu
Part 3 · 90 minutes
Focus on the
Cosmopolitan
dimension for EU framing and on the unemployment rate as the independent variable; disregard contextual variables.
Three instructions. Everything else – estimator, sample, weighting, functional form, how you build the scale – was left to us.
That is the design working as intended. Fix just enough to make the numbers comparable; leave the rest free, so that what remains is a measurement of analytical variability itself.
The cosmopolitan dimensionassumes the five items form one scale, and that a mean of them is the right summary. A sum, a share, or a factor score would all be defensible
The unemployment rateassumes the claim is carried by unemployment. The paper’s own abstract says
poor economic performances– which also names GDP growth
Disregard contextual variablesremoves the country-level controls. That buys comparability and gives up any defence against a time-varying confounder
None of these is wrong. Each one is a decision, made for you, that a different study could reasonably have made differently.
EUframes_cy.csv – 270 rows, one per country-year · 27 countries · 2004–2013
| Column | What it is |
|---|---|
cntry, year |
the cell |
mcosmo |
mean cosmopolitan framing in that country-year, 0–1 |
mutil, mcomm, mlib |
the other three framing scales |
mpos, mneg |
the two composites – all positive items, all negative items |
unemp, growth |
national unemployment rate and GDP growth |
n_cy |
how many survey respondents are behind the cell |
bailout |
1 if an EU/IMF assistance programme was running |
n_cy is the interesting one: cells are built from very different numbers of people, which is why weighting is a live choice on the menu.
What Multi100 asked: focus on the cosmopolitan
dimension and on the unemployment rate; disregard the contextual variables
What I submitted: a two-way fixed-effects model, mcosmo ~ unemp_c | cntry + year
What is on record: t ≈ −3.80 (df = 233, N = 270, partial r ≈ −0.242)
mcosmo, unemployment centred, country and year fixed effectsEUframes_cy.csv, loaded as euframesTarget: t = −3.804 – the value on record for analyst C6HJR. When you have it, type your t in chat and hold it until I say go
.
Thirty of you just landed on t = −3.804, to the third decimal. That is reproducibility, and it is the easy R.
You all ran the same code. The five Multi100 analysts each wrote their own – and came back with five different answers.
None of them was careless. So what were they doing differently? The menu, next.
| Axis | Options |
|---|---|
| Outcome | mcosmo · mutil · mcomm · mlib · mpos · mneg |
| Outcome family | gaussian · beta (logit link, for a bounded 0–1 scale) |
| Predictor | unemployment · GDP growth · both |
| Predictor form | raw · log (unemployment only – growth goes negative) |
| Copredictor | none · the other macro measure |
| Estimator | two-way FE · country FE · random effects · pooled with clustered SE |
| Sample | all · 2004–2008 · 2009–2013 · no bailout countries · no Greece and Spain |
| Weights | none · n_cy |
Eight axes. Copy-paste code for every option is on the specification menu page.
Two were on the board in the red-card study already: its 29 teams split across four response distributions (outcome family) and four ways of handling non-independence (estimator).
The three instructions fixed the outcome dimension, the predictor and the contextual controls. Everything else stayed open – including one thing nobody thinks of as a choice:
The unit of analysis. Two of the five fitted models on individual respondents; three worked on aggregates. Task 2 never said which, and the wording did not imply one.
Their five answers, all obeying all three instructions, still ran from r = −0.006 to r = −0.45. The remaining freedom was more than enough.
Keep that in mind this afternoon: the choices that move an estimate most are usually the ones made before the model is fitted at all.
For the rest of today the rule is: change one axis, hold the other seven at baseline.
Baseline = the specification you just reproduced: mcosmo · gaussian · unemployment · raw · no copredictor · two-way FE · all years · unweighted.
Why one at a time? Because it makes the class chart readable. Every dot in the room differs from a common baseline in exactly one declared way, so when a dot moves you can say what moved it.
And one convention binds everyone: claim-alignment. The negative framings are reverse-coded, 1 − y, so that supports the claim
reads as a negative sign whatever your outcome. Without it the class chart would straddle zero by construction and look like noise.
EU framing
| Scale | Items | Claim predicts |
|---|---|---|
mcosmo |
peace · democracy · free movement · diversity · voice | negative |
mutil |
prosperity · social protection | negative |
mcomm |
unemployment · identity · crime · borders | positive |
mlib |
bureaucracy · waste of money | positive |
mpos |
all seven positive items | negative |
mneg |
all six negative items | positive |
The claim says decrease positive dimensions
– mpos is the more literal reading, mcosmo is what was instructed. That one disagreement is worth roughly half the variance in the grid.
Five binary items per respondent. Three defensible summaries:
These are not cosmetic. A share is relative to the respondent’s own total, so somebody who ticked eight boxes contributes differently from somebody who ticked two. Mean and sum are not.
Among the five Multi100 analysts: one used a mean, one a sum, one a share, one three separate shares. All defensible; none the same quantity.
Reverse-coding a bounded scale flips the sign of b, t and r, and leaves the magnitude, the standard error, the |t| and the p-value untouched. Exactly so for the beta family too, since logit(1 − μ) = −logit(μ).
Nothing about your evidence changes. Only the direction convention it is reported in.
It is still a real analytic decision, which is why it rides on an explicit claim_align switch in your preregistration rather than sitting silently inside the fitting code.
Our outcome is a proportion: it lives strictly between 0 and 1, and its variance is smallest near the ends. A linear model assumes neither of those things.
On this claim, the two agree: the beta twin of the baseline gives b(logit) = −0.01399, z = −4.075, r = −0.258, against the gaussian r = −0.242. Same direction, still significant, marginally stronger.
The seminar argument about distributional assumptions is real, and on this claim it moves almost nothing.
The abstract says poor economic performances
. That names at least two things:
Fitted on our panel, the growth mirror of the baseline gives b = +0.00167, t = 1.59 – the right direction, not significant.
Across the whole growth half of the grid: 70.5% in the direction predicted by the claim, but only 46.9% significant. Direction without significance – a different kind of support from the unemployment story.
unemp – a one-point rise in the rateThose two are genuinely different models and can give different answers.
But centring and standardising are not. Subtracting a mean, or dividing by a standard deviation, is an affine change of scale: it moves the coefficient and its standard error by the same factor and leaves t, r and p exactly unchanged.
Which is why the grid holds no separate centred rows. If a robustness check varies only the centring, it is not a robustness check – it is the same analysis, restated.
| Estimator | What it compares | What it assumes |
|---|---|---|
| Pooled, clustered SE | countries with each other and over time | no unobserved country differences |
| Country FE | each country with itself over time | country differences are stable |
| Two-way FE | the same, net of EU-wide year shocks | shocks hit everyone alike |
| Random effects | a weighted blend of both comparisons | country effects uncorrelated with the predictor |
These do not answer the same question. Pooled asks whether high-unemployment countries frame the EU differently; fixed effects ask whether a country frames it differently when its own rate rises.
And it shows: of the 41 wrong-sign specifications in the cosmopolitan universe, 33 are pooled.
They do absorb
They do not touch
This is the same statement as the DAG you drew before lunch. adjustmentSets() on the committed graph returns country and year – exactly what two-way fixed effects absorb. The graph and the baseline model make the same claim.
Put GDP growth in beside unemployment and you have made a choice you should be able to name:
poor economic performance. Now you are asking which one carries the claim
Same line of code, three different estimands. And remember the Table 2 fallacy: the growth coefficient in that model is not automatically the causal effect of growth.
The last two exclusions test whether the association is carried by a handful of extreme cases. If it is, removing them should worry you – and it does something visible here: 24 of the 41 wrong-sign specifications sit in bailout-excluding samples.
An exclusion is a claim about who the finding is meant to be about. That is an estimand question, not a housekeeping one.
Each cell is a mean of very different numbers of respondents. Weighting by n_cy says a cell built from 2,000 people should count more than one built from 500.
n_cy – cells count in proportion to the people behind them. You are closer to studying peopleThat is a shift of estimand, not a technical refinement. Which is why the day’s convention is to declare it.
Practical note: random effects are never fitted with weights in the grid – the combination is not well defined for this design, so those rows do not exist.
A coefficient from a beta model on a share and one from a weighted pooled model on a sum are not on the same scale. The answer from Multi100, and ours:
Now every specification in the room, and all 840 in the grid, sit on one axis. report_result() does the conversion for you.
The cost is worth naming: standardising makes numbers comparable, not equivalent. It cannot rescue two analyses that estimate different quantities – it can only put them side by side.
Silberzahn’s teams hit the same wall: four of the twenty-nine reported a correlation or a standardised difference rather than an odds ratio. The authors converted everything to one scale and reported the median. That is a different answer to the same problem – a median is one number, a curve is a distribution – and which you want depends on what you mean to say next.
Task 4 is exploration, and it is meant to be. Try several specifications, look at what each one does, and get a feel for which axes move the estimate.
Your dot lands on the live Multiverse page within seconds. We read the room’s dots at the start of Part 4.
This is not your preregistered analysis. You are allowed to look, change your mind, and try again – that is the whole point of naming this phase separately.
30 minutes – then we read what the room built · leave the Multiverse page open
Part 4 · 90 minutes
You ran three or four specifications, all defensible, and watched the estimate move.
Nothing about that is unusual. Every analysis you have ever published sat inside a space like that – you simply reported one point from it.
The multiverse idea is not a new estimator. It is the proposal that the space is the finding, and that showing one point without it is a form of incomplete reporting.
Different names, one move: stop treating your analytical choices as invisible, and enumerate them.
Four binary choices give sixteen analyses. Eight axes with several options each give hundreds. The published paper reports one red line and no tree.
A multiverse is not every analysis you could run. Two filters have to be applied before anything goes in:
Drop the first filter and you get a cloud of nonsense that makes any finding look fragile. Drop the second and you are averaging over answers to different questions – which is the red-card mistake, at scale.
Today’s grid is a curated 840, chosen against both filters. That curation is itself an analytical decision, and it is the one that a critic should attack first.
Enumerate
Cross every option on every axis. Complete, mechanical, reproducible.
Risk: the set fills up with specifications nobody would defend, and the spread stops meaning anything.
Curate
Include only the specifications you would actually defend.
Risk: you are choosing the set, so a sceptic can ask whether you chose it to get the picture you wanted.
There is no way out of this. Either way, the set is a claim, and it belongs in the paper alongside the curve – ideally declared before the curve is drawn.
Our menu, multiplied out:
Two consequences. Compute stops being free, so somebody must decide what is worth running. And you cannot look at 2,520 numbers – you need a picture, and a way to ask which axis is doing the work.
Rohrer, Hullman and Gelman’s question, and it has three different answers that want three differently built universes:
Today’s grid was built for reflection. Reading it as inference is the mistake we take apart later in this part.
Rohrer et al. (2026)
Once you have hundreds of estimates and the choices that produced each one, you can ask a question that no single analysis can answer: how much of the variation does each axis account for?
The answer is the most useful thing a multiverse produces, because it tells you where to spend your argument. An axis that moves nothing does not need defending. An axis that moves everything needs a paragraph.
On this claim, the answer surprises almost everybody – which is why you are going to bet on it in a minute before I show you.
Five questions to ask any curve you meet, in any paper:
You are about to answer all five for your own curve. That is a publishable robustness section.
Pre-computed over the specification menu (data/spec_grid_full.csv), claim-aligned – negative framings reverse-coded and growth predictors sign-flipped, as recorded in your Task 5 preregistration (claim_align = TRUE).
Your orange dot lands here → live on the Multiverse page as the room submits
report_result() gives you a one-click submission link; results stream onto the Multiverse page.
Six axes: outcome · predictor form · estimator · copredictor · sample · weights
Which axis moves the estimates most across all 840 specifications?
Type your guess – hold it – send on go
.
Computed over the committed data/spec_grid.csv; imported as data/fork_importance.csv.
your percentilecell in the browser lab – both routes use it for this one; it runs in any browser tab, no install
Paste your percentile in chat – hold – send on go
.
A curve is a map of the menu you wrote. It cannot tell you the menu was the right one.
Description – map how the estimate moves across every defensible choice. (What we have been doing all day.)
Inference – ask whether the curve as a whole is surprising if there were no effect at all. (A different, harder job.)
The two are detachable, and most published curves stop at description.
Every specification re-uses the same 270 country-years. The estimates are deeply dependent.
63.9% significant
describes the curve. It is not a p-value for the claim.
Vote-counting across dependent specifications has no null distribution. You cannot tally the stars.
The design is worked through in your inference reading; permutation inference after Phipson and Smyth (2010).
Discussion (2 minutes, then chat): the constrained cosmopolitan-only universe has 140 specs: 70.7% negative, 57.1% negative and significant. Strong evidence? Against what null?
Everything descriptive survives: the 49.7% outcome fork, the analysts’ placement, the sign-flip pattern, your dot.
What waits for the permutation is one sentence: the curve as a whole is unlikely under no effect
.
Careful reporting says which of the two layers a claim lives in.
Robustness is better assessed with a few thoughtful models than with billions of regressions(Auspurg 2025; answering Ganslmeier and Vlandas 2025) – only justified models targeting the same estimand belong in one curve. Today’s grid is a curated 840, not billions, and the fork chart does the job that every critic endorses: it locates where the argument must happen
There is only one correct analysisonce the question is fully specified (Lakens et al. 2026) – your declared specification answers cherry-picking, and their deeper point is that declaring in advance does not yet make a choice justified, which is what the estimand and DAG work were for
What’s a multiverse good for anyway?– reflection, persuasion, or inference, and each purpose wants a differently built universe (Rohrer et al. 2026). Today’s was built for reflection, and how much more to read into it is the open debate
There is only one correct analysis
Lakens and colleagues push back hard, and the argument deserves to be taken seriously:
Take it back to the red cards. Twenty-nine teams modelled one count of red cards as linear, logistic or Poisson. Those are claims about how the outcome was generated, and they cannot all be right – so on this argument the spread is not diversity, it is mostly error.
Lakens et al. (2026)
Grant the argument. One correct analysis exists. Now: which one is it?
Getting there needs the estimand and the identification assumptions and agreement about the measurement – and on this claim, reasonable people disagree about all three.
So the curve is not a rival to the one-correct-analysis view. It is a map of the remaining disagreement, drawn because we cannot yet settle it.
And it points at what to argue about: on this claim, at the outcome, not at the estimator.
It does settle
It does not settle
A badly-reasoned analysis declared in advance is still badly reasoned – now with a timestamp
Which is why the estimand and the DAG came first today. Preregistration protects a choice; it is the causal reasoning that makes the choice worth protecting.
Before the break you explored: several specifications, tried in order to find out what they do. That was the right way to learn them
Now you commit: one specification, declared in writing before it runs, with a reason that your DAG can carry
The difference is not the code. It is that one of these can be chosen because of its result and the other cannot
OSF, the Open Science Framework: a timestamped, citable, public home for data, code, and registrations.
A registration is a frozen, dated snapshot of a plan. Anyone can check what you said you would do, and when.
You need no account today. We are going to read OSF the way that reviewers do.
osf.io/8rtwe – the archival Multi100 record: analyst C6HJR, frozen as submitted
osf.io/6zqct – the maintained fork: the script, the codebook, today’s data
osf.io/h7432 – the full SCORE dossier on the EU-frames case: every verdict, every artefact
Aczel et al. (2026). Project: osf.io/7snkz
Preregistration Template from AsPredicted.org)
| AsPredicted asks | Your block answers |
|---|---|
| Q1 Have data been collected? | It’s complicated – I already hold them+ what I already know |
| Q2 Main question / hypothesis? | the claim, in your one sentence |
| Q3 Dependent variable? | your outcome axis |
| Q4 Conditions? | your other five axes, held at baseline |
| Q5 Analyses? | your model + your support/undermine rule |
| Q6 Outliers / exclusions? | your sample axis |
| Q7 Sample size? | N = 270 country-years – fixed |
| Q8 Anything else? | your DAG-grounded justification |
declaredin chat – then wait for the room
Nobody runs a model until the room is declared.
report_result() prints a one-click link with your numbers already in it – click, submitWatch what the two waves look like together. If the declared cloud sits systematically to one side of the exploratory cloud, that is worth a conversation.
Stuck? Chat STUCK
· reload and rerun · second browser · follow my screen · last resort: the grid lookup on the menu page gives you your numbers by hand
the common single-path analyses in social and behavioural research should not be simply assumed to be robust to alternative analyses
(Aczel et al. 2026)
A blueprint: precise question → causal reasoning (DAG) → multiverse → sensitivity analysis (Auspurg and Brüderl 2021)
There is no privileged single analytical path – only justifiable ones, explored openly.
You made one justifiable choice today – transparently, with a reason, inside a multiverse.
And that was the workshop.
codemoreh.github.io/applied-replication
Fork anything. Everything is open.
fixest & ggdag docsFeedback form
(link in the chat · two minutes, while the chart is still on screen)
