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
| Output | Requires | Use for |
|---|---|---|
html_document | knitr, rmarkdown | Sharing over the web, interactive tables |
pdf_document | A LaTeX engine such as TinyTeX | Print, formal delivery |
word_document | rmarkdown only | Colleagues who will edit the file |
beamer_presentation | LaTeX | Slides from the same source |
Quarto .qmd | Quarto CLI | New 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.lockand never commitrenv/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.Related
Package management with CRAN, renv and Bioconductor Statistical modelling: lm, glm and the formula interface
Last refreshed 2026-09-18.