R Markdown and reproducible reports

Write narrative and code in one file, control chunk behaviour, parameterise a report, and pin the package versions it needs.

Structure of a document

---
title: "Monthly report"
author: "Data team"
date: "`r Sys.Date()`"
output:
  html_document:
    toc: true
    code_folding: hide
params:
  month: "2026-08"
---

## Summary

Revenue for `r params$month` was `r format(total, big.mark = ",")`.
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
                      fig.width = 7, fig.height = 4, dpi = 150)

library(readr)
df <- read_csv("data/sales.csv")

library(ggplot2)
ggplot(df, aes(month, revenue)) + geom_col()

Each fenced chunk marked ```{r} in the source runs as R code; a chunk with include = FALSE runs but shows nothing, which is where setup belongs. Options are inherited from opts_chunk$set() unless a chunk overrides them.

The document is rendered in a fresh R session by default, which is exactly the guarantee you want: if a number appears in the report, the code that computed it appears above it in the same run.

Rendering and parameters

OutputRequiresUse for
html_documentknitr, rmarkdownSharing over the web, interactive tables
pdf_documentA LaTeX engine such as TinyTeXPrint, formal delivery
word_documentrmarkdown onlyColleagues who will edit the file
beamer_presentationLaTeXSlides from the same source
Quarto .qmdQuarto CLINew projects; the successor to R Markdown
Rscript -e 'rmarkdown::render("report.Rmd", output_file = "build/aug.html")'

# parameterised rendering, one report per month
Rscript -e 'rmarkdown::render("report.Rmd", params = list(month = "2026-08"), output_dir = "build")'
# a parameterised render driven from R
library(rmarkdown)
for (m in c("2026-06", "2026-07", "2026-08")) {
  render("report.Rmd",
         params = list(month = m),
         output_file = paste0("report-", m, ".html"),
         envir = new.env())          # a clean environment per run
}
⚠️
Chunk caching stores results on disk and reuses them when the code appears unchanged, which is a fast way to publish a report built on stale data. Cache only genuinely slow chunks, and include a hash of the input file in the chunk so the cache invalidates when the data changes.

Reproducibility with renv

renv::init()            # creates renv/ and renv.lock for the project
renv::snapshot()        # record the versions currently in use
renv::restore()         # install exactly what the lockfile records
renv::status()          # is the library in sync with the lockfile?

sessionInfo()           # R version, platform, loaded packages: put this in the appendix
  • Commit renv.lock and never commit renv/library; the lockfile is the record, the library is a local cache.
  • Set set.seed() before any simulation or random split so the report renders identically each time.
  • A rendered report should state the R version and key package versions, because a changed default can alter the numbers.

FAQ

Should I start a new project in R Markdown or Quarto?
Quarto for anything new: it supports more languages, has a cleaner cross-reference and citation system, and is actively developed. R Markdown remains the right choice when you depend on an existing template or a package that only supports it.
Why does my report fail to render but work in the console?
Rendering starts a fresh session that only sees code inside the document. A variable created interactively does not exist there. Run the chunks top to bottom in a clean session, or use envir = new.env() to prove the document is self-contained.

Package management with CRAN, renv and Bioconductor Statistical modelling: lm, glm and the formula interface

Last refreshed 2026-09-18.