rmarkdown::render("practicals/report.Rmd", output_format = "html_document",
knit_root_dir = getwd())
rmarkdown::render("practicals/report.Rmd", output_format = "word_document",
knit_root_dir = getwd())
rmarkdown::render("practicals/report.Rmd", output_format = "pdf_document",
knit_root_dir = getwd())14 Reproducible reports with R Markdown and Quarto
14.1 Learning outcomes
Combine explanation with executable analysis, distinguish .Rmd from .qmd, add tables and figures, render HTML/PDF/Word reports, and share a project that runs in a fresh session.
14.2 A report is an argument supported by evidence
A useful report states a question, describes data and design, explains decisions, presents results, and discusses limitations. Embedding R code keeps numerical evidence connected to its computation. If a dataset changes, re-rendering updates the results. This reduces transcription errors but cannot correct an unsuitable analysis.
R Markdown usually uses .Rmd source files and rmarkdown::render(). Quarto uses .qmd source files and the quarto render command. Both can execute R through knitr. Their configuration differs: R Markdown uses output: whereas Quarto uses format:. Simply renaming an old bookdown file is not a complete migration; chapter configuration, references, chunk options, and dependencies need checking.
14.3 Anatomy of a Quarto document
The following is a complete small document. The YAML header defines metadata and formats, ordinary Markdown supplies prose, and the R chunk computes a table. Save it as report.qmd in the project folder. The separate practicals/report.qmd provides a fuller working example.
---
title: "Plant growth: a reproducible analysis"
author: "Student name"
format:
html: default
pdf: default
docx: default
---
# Question
How do dry-weight measurements vary among the documented groups?
::: {#tbl-weight .cell tbl-cap='Mean dry weight by group in PlantGrowth.'}
```{.r .cell-code}
data("PlantGrowth", package = "datasets")
tab <- aggregate(weight ~ group, PlantGrowth, mean)
knitr::kable(tab, digits = 2)
```
::: {.cell-output-display}
|group | weight|
|:-----|------:|
|ctrl | 5.03|
|trt1 | 4.66|
|trt2 | 5.53|
:::
:::
Interpret the estimates in @tbl-weight using the dataset documentation.Use descriptive chunk labels; figure labels begin with fig- and table labels with tbl- for cross-references. A figure chunk can use #| fig-cap: "...". A hidden setup chunk may set #| include: false, but keep the source available to readers. echo: false hides code, not results. eval: false displays an example without executing it. Avoid hiding warnings until they have been investigated.
14.4 Dynamic values, figures, and interactive output
Inline R inserts a computed value into a sentence. For example, write the literal source 30 to report the number of observations. This keeps prose synchronized with the data. Do not hard-code a p-value in the discussion while the model updates elsewhere.
Use knitr::kable() for simple portable tables and ordinary R plots for graphics. HTML widgets such as Plotly need an HTML output context. In multi-format documents, test knitr::is_html_output() and use a static plot for PDF and Word, as demonstrated in the visualization chapter.
14.5 R Markdown remains part of the syllabus
The supplied practicals/report.Rmd demonstrates the same analysis in R Markdown. Its header lists html_document, pdf_document, and word_document. Run these commands in the R Console from the project root:
14.7 Building the report in stages
Let us develop a report in the same order as an analysis. First create the document with its question, data source, and design, even before the final model is chosen. Then add a data-check section, followed by descriptive tables and figures. Only after examining those results should the methods and model sections be completed. This sequence makes the scientific argument visible rather than hiding it behind a final table of p-values.
An effective data section identifies the observational unit, sample size, variable units, collection period, and treatment or sampling scheme. State how the raw file was obtained and whether permission is needed to share it. In the cleaning section, explain missingness, corrections, and exclusions with their consequences for the analysis. Avoid including personal or confidential identifiers merely because they happen to be in the original spreadsheet.
The methods section should name the response, predictors or factors, model, and assumptions in terms that match the actual code. If the text describes a paired test but the code fits an unpaired test, a successful render has reproduced an inconsistency. Read both parts together. The results section should lead with the size and direction of important effects, followed by uncertainty and evidence. Keep mechanistic explanations and generalization limits in the discussion.
14.8 Managing code chunks and dependencies
Each document is easiest to understand when it can be executed from top to bottom in a new session. The first chunk loads packages and reads inputs. Later chunks depend on those named objects. Avoid loading data by hand into the Environment pane and then assuming they will be available during rendering.
::: {#tbl-treatment-means .cell tbl-cap='Mean simulated grain yield by management treatment.'}
```{.r .cell-code}
means <- trial |>
group_by(treatment) |>
summarise(n = n(), mean_yield = mean(yield_t_ha), .groups = "drop")
knitr::kable(means, digits = 2)
```
::: {.cell-output-display}
|treatment | n| mean_yield|
|:---------|--:|----------:|
|Compost | 20| 3.84|
|Control | 20| 3.12|
|Mineral | 20| 4.36|
:::
:::The setup chunk is hidden in the displayed report but remains in the source. It does not install packages. The table chunk visibly records the summary operation. A future reader can distinguish a count of plot records from an estimate of mean yield and can rerun both.
Caching and freezing can save time in large projects, but they can also leave stale output when dependencies are misunderstood. During textbook revision, this project uses freeze: false so examples are executed again. Do not use old cached results to claim that revised code has been checked. For serious projects, record the environment and consider a lockfile, while recognizing that external data sources can change independently of package versions.
14.9 Tables, figures, and numerical precision
A table title should say what was measured, how observations were grouped, and what the numbers represent. Put units in column headings where possible. Report enough digits for interpretation without suggesting unrealistic measurement precision. A yield measured to 0.01 t/ha should not normally be presented to eight decimal places merely because R printed them.
A figure caption should allow the graph to be understood away from the surrounding paragraph. Distinguish observed points from fitted lines, and describe error bars or shaded intervals precisely. “Error bars” is insufficient: are they SD, SE, confidence intervals, or prediction intervals? If an interactive figure permits hovering, retain axis labels and a caption so the static version remains meaningful.
Cross-references are preferable to manually typed numbers. After inserting a new table, @tbl-treatment-means updates its reference automatically. Manually writing “Table 3” can become wrong after reorganization. The same applies to chapter references and figure references. The final review should include checking that a referenced object actually supports the claim made in the sentence.
14.10 Reproducibility is also a classroom assessment
Ask a classmate to open a copy of the complete project, restart R, and render the report. If it fails, identify whether the missing component is a file, package, path, or undocumented manual step. Record the correction in the project instead of asking the classmate to imitate your Console history.
Finally, read the rendered document as a reader. Are headings in a logical order? Do tables fit? Are captions readable? Do the conclusions match the estimates? A report can be computationally reproducible and scientifically weak, or scientifically thoughtful but computationally incomplete. Undergraduate training should develop both skills together.
14.11 Practical 15 and exercises
- Replace the example report’s question with a question you can answer from a documented dataset. Include source, units, design, one table, one figure, and an interpretation.
- Render HTML and Word. If LaTeX is available, render PDF and inspect all pages.
- Change one observation in a teaching copy of the data and re-render. Identify every result that should change.
- Explain the difference between
echo: falseandeval: false. - Give the complete project to a classmate. Ask them to render it without relying on your Console history.
Chapter review. Reproducibility means that data, code, and computational environment support the report’s claims. A successful render is a necessary check, not a substitute for scientific review.
14.12 Live R laboratory
Generate a reproducible result sentence
Edit the code, then select Run R. The first run downloads R into your browser and may take a little time. Copy code also lets you use this example in RStudio.
yield <- c(3.2, 3.8, 4.0, 3.5, 4.6, 3.9)
result <- data.frame(n = length(yield), mean = mean(yield), SD = sd(yield))
result
cat(sprintf("Across %d simulated plots, mean yield was %.2f t/ha (SD %.2f).\n",
result$n, result$mean, result$SD))Investigate. Change a yield and rerun: both the table and sentence update. In a Quarto or R Markdown report, use an executable chunk for the table and inline R for values in the narrative. Rendering an entire report requires RStudio and Quarto.
Record the input you changed, the output you obtained, and your interpretation. These live examples use base R and work independently. The full chapter examples, including package-based analyses, remain available with copy buttons for RStudio. Browser objects last until this page is reloaded; Reset code restores the editor text. Internet access is needed on first use; long or interactive desktop tasks belong in RStudio.
14.13 Vibe coding and AI-assisted R programming
14.13.1 From an idea to a first draft
Vibe coding is an informal term for developing software by describing what you want in ordinary language, asking an AI system to generate code, running it, and requesting changes. The term is often associated with accepting implementation details without examining them closely. For a statistical analysis, we need to go further: understand the code, verify the calculations, and justify the model. In this book, we use the conversational approach as an entry into reviewed AI-assisted programming. See the overview of the term.
For example, a student might begin with “Make a colourful graph of crop yields.” An AI assistant can draft a plot quickly, but that request omits the data structure, units, experimental unit, and purpose. A graph of treatment means, a distribution of individual plot yields, and a fitted response curve answer different questions. Clear agricultural reasoning makes a better prompt and a better analysis.
14.13.2 Using AI with R
One approach is to ask an AI chat assistant for a small R code draft, inspect it, and run it in RStudio. Another is to use an assistant integrated into an R development environment. Current examples include Posit Assistant in RStudio and Positron; their capabilities, access requirements, and settings are described in the RStudio guide and Positron documentation. This lesson does not require a paid assistant or a particular model.
Begin with a small task: explain a function, summarize a table, improve axis labels, or diagnose a reproducible error. Ask for the reason behind each change. Read unfamiliar functions in R’s help system and confirm that suggested packages and arguments exist. A plausible function name is not evidence that a function is available.
14.13.3 Write a prompt that specifies the analysis
I am learning R. I have independent plot observations from a teaching experiment. The columns are treatment and yield_t_ha; yield is measured in tonnes per hectare. Use base R. Produce a table with total records, nonmissing yields, missing yields, mean, and standard deviation for each treatment. Keep missing values in the data and exclude them only from calculations that require observed yields. Do not replace missing values with zero, delete outliers, or run significance tests. Include a small simulated example, explain the code, and show how I can check the results manually.
Specify the question, experimental unit, column names and units, design, missing-value rule, and requested output. State restrictions such as “use base R” or “do not install packages.” Ask for one step at a time so you can inspect its result before proceeding. Share a small simulated example or a permitted, de-identified excerpt when explaining a problem; check your institution’s rules before sending research data to an external service.
14.13.4 Worked example: review a generated summary
The following is a teaching reference solution to the prompt, not a transcript of a particular AI response. The simulated data contain a missing yield in the Control group. We deliberately count both rows and observed yields so the denominator is visible.
ai_trial <- data.frame(
treatment = c("Control", "Control", "Control", "Compost", "Compost", "Compost"),
yield_t_ha = c(3.0, 4.0, NA, 4.0, 4.5, 5.0)
)
summarize_yield <- function(x) {
observed <- x[!is.na(x)]
c(total = length(x), observed = length(observed),
missing = sum(is.na(x)),
mean = if (length(observed)) mean(observed) else NA_real_,
SD = if (length(observed) > 1) sd(observed) else NA_real_)
}
ai_summary <- do.call(rbind, lapply(
split(ai_trial$yield_t_ha, ai_trial$treatment), summarize_yield
))
ai_summary total observed missing mean SD
Compost 3 3 0 4.5 0.5000000
Control 3 2 1 3.5 0.7071068
# Independent checks against calculations we can do by hand
stopifnot(ai_summary["Control", "total"] == 3,
ai_summary["Control", "observed"] == 2,
ai_summary["Control", "missing"] == 1,
isTRUE(all.equal(unname(ai_summary["Control", "mean"]), 3.5)),
isTRUE(all.equal(unname(ai_summary["Compost", "mean"]), 4.5)))The Control mean is \((3+4)/2=3.5\) t/ha. Dividing by three, or replacing the missing observation with zero, would answer a different question. Its sample SD is approximately 0.707 t/ha; the Compost SD is 0.5 t/ha. With no observed yields, the mean is undefined; with only one observed yield, the sample SD is undefined. The function returns NA for those cases. Summary values alone do not demonstrate a treatment effect.
14.13.5 Refine, debug, and verify
Useful follow-up prompts include:
- “Explain why the number of observed yields differs from the number of records.”
- “My error is
object 'yield' not found. Here isnames(ai_trial)and the failing line. Explain the mismatch and change only that line.” - “Review this model for a randomized block experiment. Explain how the block factor enters the formula before changing the code.”
- “Add units and a caption to this plot without changing the observations or fitted model.”
When a draft fails, provide the exact error and the smallest example that reproduces it. Avoid repeatedly asking an assistant to repair a long script without checking what it changes. A coding error and a statistical error are different: aov(yield ~ treatment) can execute successfully while omitting a block factor required by the design. A paired test, an independent-samples test, and a repeated-measures model are not interchangeable repairs.
Inspect row counts, units, missingness, group sizes, model formulas, and assumptions. Verify at least one result independently. Check that plots and written conclusions match the computed output. Do not ask AI to keep changing an analysis until a p-value becomes significant. You remain responsible for the method and interpretation.
14.13.6 Save a reproducible record
Keep the accepted R code in the project and render the report in a fresh session. Record package versions, relevant prompts, substantive AI contributions, and the corrections you made. Follow course and publication rules for acknowledging assistance. AI may help draft a methods paragraph, but the paragraph must describe the analysis actually performed. Never invent citations, observations, or results to complete a report.
Closing practical. Give an assistant the model prompt above, or write your own proposed solution if no assistant is available. Compare its code with the reference calculation. Test an all-missing group and a group with one observed yield. Submit the prompt, reviewed code, verification results, and a short account of what you accepted or corrected. Explain every line you submit.