Make Your Analysis Reproducible: A Document That Updates Itself
Every time you get a new version of your data — a corrected file, a late entry, the final clean dataset — you have two options. You can work through the same cleaning and preparation again, clicking the same buttons, running the same checks, copying the same numbers into Word. Or you can press a button and have it done in seconds.
Which one you get depends entirely on whether you set the document up correctly the first time.
I'm a fairly average researcher — a clinical psychologist who also does statistics — and I've written around twenty papers. The single change that did the most to make that manageable wasn't a cleverer method or a faster way of writing. It was writing my analysis in a document that reruns itself. This is what people mean by reproducible research, but I want to make it concrete and practical rather than a principle you nod along to. Done right, it's not extra discipline you impose on yourself. It's the version that saves you time from the first data change onward.
A note on terms before we start. I'll use RMarkdown as the umbrella word throughout, because it's the more widely recognised name. Everything here applies identically to Quarto — the newer engine, .qmd files instead of .Rmd. The workflow, the reasoning, and the payoff are the same whichever you use. (This post assumes you've already written your Methods and Results first, using a model article — that's the companion piece. Here we make those same sections update themselves.)
📄 Free: I've built a prefilled RMarkdown/Quarto notebook that implements this exact workflow — the document structure, the source() calls, the inline R, and placeholder prose you can write over. It's ready to download and start from. Get the notebook here.Set up a document that runs itself
The setup is simpler than it sounds. Open your model article, go to the data-analytic section, and work through it line by line. For each piece of output your article will need — each table, each test statistic, each effect size — write a code chunk that produces the equivalent from your own data. The document structure follows the model article: same sections, roughly the same order, the same reporting.
As you do this, three layers accumulate in one file:
- The target output — what the model article shows, which tells you what your field expects.
- Your code — the R (or Python) that produces your version.
- Your explanation — prose between the chunks that says not just what the analysis does but why, with citations where a choice rests on a convention or a methodological decision.
Three layers, same place, same time.
That's the key insight. When those layers are separated — output in Word, code in an R script, reasoning in your head — they drift apart. Six months later you can't find why you made a choice. You can't easily explain it to a reviewer. And you can't rerun the analysis without manually repeating every step.
The common objection is that this sounds complicated — that learning RMarkdown is an extra burden on top of an already hard task. But the cost of not doing it compounds. Every data change, you repeat the work by hand. Every reviewer question about why you chose a test, you dig through old files or try to remember. Every collaborator, you walk through the pipeline step by step.
I've watched colleagues spend an hour re-cleaning and re-preparing data every time a new file arrived. Most of the time it's manageable. But I've also seen what happens when new data lands the day before a deadline: you're at your computer at two in the morning, manually entering changes into a spreadsheet, re-running analyses one by one, hoping you haven't missed anything.
That situation isn't bad luck. It's a pipeline that was never designed to run itself. Set the document up once, correctly, and new data stops being a crisis. It becomes a button press.
Keep the document a document, not a script
The structure is simple: your .qmd file is a document, not a script. The prose reads like a Methods section — it describes what you did and why, in the language you'd use in the final article. The code chunks are minimal. They call external scripts with source() and produce nothing visible themselves. The analysis happens elsewhere; the document just orchestrates it.
Say you have a section on correlations. In the document, you write a sentence or two describing what you did and why — which variables, which method, any relevant decisions. Below that, one chunk:
source("R/analysis_correlations.R") # -> cor_resultsThat's all the chunk does. The correlation code itself — however many lines it takes — lives in analysis_correlations.R. The document stays readable. The analysis stays contained.
The rule of thumb: when a piece of analysis runs past roughly ten lines, it gets its own script. Setup, data preparation, each major analysis — each in its own file. The .qmd becomes an index: what analyses ran, in what order, with the reasoning in plain language around each call.
The most common mistake is to write the analysis directly in the document — a running file mixing code and prose with no structure. It works for a first pass and becomes unmanageable fast: long chunks break the reading flow, it gets unclear where analysis ends and explanation begins, and the file grows until nobody, you included, can follow it.
Let inline R handle the numbers
Inline R handles the statistics. Instead of typing "N = 150" in the text, you write `r n_participants`. The number is produced by the code and woven into the prose automatically. When the data changes, the number updates. You never hand-edit a statistic again.
The Results section is where this pays off most, because by the time you reach it the analysis is already done. The scripts sourced in Methods have run; the objects are in memory. Results just calls them. One line produces a formatted table:
apa_regression_compare(reg_results[["cbtkomp"]], caption = "Table 3. Regression predicting CBT competence.")The surrounding prose might read: "On the imputed data, the model for CBT competence was statistically significant, `r apa_fit_inline(...)`, with knowledge a significant predictor, `r apa_term_inline(...)`." The numbers aren't typed — they're called. When the data changes and the document reruns, every number updates. (This is also why the one-sentence results paragraph works so cleanly here: the prose around each call is minimal by design.)
There's a quieter benefit to calling rather than typing. A colleague of mine spent the evening before a deadline checking his results — not running new analyses, checking. He'd been at it for hours, tired and anxious. As the night wore on he started finding small discrepancies, which made him less certain, which made him check more. By two in the morning he'd introduced errors he wouldn't have made at nine. He submitted less confident than when he'd started.
The problem wasn't that he checked. It was that he was checking manually at the worst possible moment. If the pipeline runs itself, there's nothing to check. You run the document once. The numbers are right because the code is right, and the code was written when you weren't exhausted and frightened by a deadline.
Write the pipeline in quiet time. Run it when you're stressed.
📄 Free: Don't build the document from a blank page. My prefilled RMarkdown/Quarto notebook has the full structure from this post — the source() calls, the inline R, and placeholder prose you write straight over. Download it here and set up your self-updating methods and results this week.Comment WHY, not WHAT
The most useless comment you'll ever write is # remember this. The most useful is a reference.
That distinction is the whole point. Commenting code isn't about volume — it's about what kind of information a comment actually preserves.
What the code does can always be recovered. The function name tells you. The documentation tells you what the arguments mean. The code itself, read carefully, usually shows exactly what's happening. None of that needs a comment.
What can't be recovered is why you made this specific decision, at this specific moment, for this specific study. That reasoning lived only in your head when you wrote the code. If you don't write it down, it's gone.
So every comment should answer one of two questions. Either why this, and not something else? — explaining the decision. Or where does this number come from? — pointing to the source.
Compare these two comments on the same line:
# This code imputes data using micepred <- quickpred(data, mincor = 0.2)# predMatrix limits which variables predict which — speeds up imputation# 0.2 correlation threshold from van Buuren (2018), p. 163pred <- quickpred(data, mincor = 0.2)The first describes what the function does — which you can read from the function name. The second explains why this function with this argument, and points to the source of the number. Six months later, when a reviewer asks why you chose 0.2, the answer is already there.
The opposite failure mode is over-commenting: a lengthy explanation of a method that already has a canonical reference isn't a comment, it's a poorly placed literature review. If the decision rests on a paper, cite the paper. Keep the comment short — one line of reason, or a pointer to where the reason lives.
This is also why keeping analysis notes in a separate document — or a zettelkasten — and pointing to them from comments works well. The comment says # see note 4.6 on imputation strategy; the note holds the full discussion. The code stays clean; the reasoning stays findable.
I've returned many times to data I analysed during my undergraduate degree. The comments in those scripts read like messages from someone who assumed I'd always have context I no longer have. # remember this. # this is important. One script had a comment that said simply # check. Check what? The thing that needed checking, the reason, the outcome — all gone. It's a reminder with no content, worse than no comment, because it implies there's something to know without saying what.
Write for the person who has lost access to your reasoning — which is you, in two years.
The payoff: revisions in minutes
A major revision request is supposed to be frightening. Sometimes it isn't.
When a reviewer asks you to redo your analyses accounting for a nested data structure, the usual experience is several days of work: re-running every model, copying updated numbers into Word, checking that every table and every in-text statistic still matches, hoping you haven't introduced an error somewhere. It's the kind of request that makes you dread opening the email.
With a well-built pipeline, it's different. You open the document, change one line, and run it. Every table updates. Every inline statistic updates. You read through the output, confirm it's what you expect, and write your response to the reviewer.
I recently had exactly this. An article with fourteen structural equation models — each sourced from its own script, results called inline throughout the document. It took a few hours to build the pipeline correctly. The reviewer asked that all models account for the nested structure of the data. I changed one argument in the configuration file. The document re-ran. The revision was done.
Fourteen models. One line.
That's the full payoff of this workflow — not just reproducibility as a principle, but the concrete experience of a request that should have taken days taking minutes instead.
The cost people underestimate isn't the setup — it's the alternative. Researchers who work in Word and manual pipelines feel that redoing an analysis is just "an hour here and there." The first time, maybe. But it compounds. Every revision cycle, every data correction, every sensitivity analysis a reviewer requests — the same manual steps again, each one a fresh chance for error: a number that doesn't update, a table that goes out of sync with the text, a model run on the wrong version of the file. Those errors aren't always obvious. Sometimes they survive to publication.
The document-based pipeline has a real setup cost, paid once. After that, change propagates automatically. The document is always consistent with the code because it's produced by the code. There's nothing to check manually, because there's nothing entered manually.
The quieter payoffs accumulate the same way. Sharing the analysis means handing a collaborator one folder; they run the document and see the same output. Explaining a decision to a reviewer means pointing to the line of prose that already describes it, next to the code that implements it. Returning to a study two years later means reading the document rather than reconstructing it from memory and disconnected scripts.
None of these are dramatic. Collectively, they're the difference between a pipeline you trust and one you're always slightly uncertain about.
Set it up correctly once. Then let it work.
📄 Free: This is the fastest way to start: my prefilled RMarkdown/Quarto notebook already has the sourced-script structure, the inline R, and the WHY-commented chunks from this post. Download it here and build your self-updating analysis this week.
Still copying numbers into Word by hand the night before a deadline? Hit reply and tell me your worst re-run story — the 2am spreadsheet, the table that wouldn't match the text, the number that didn't update. I read every reply.
Member discussion