install.packages("rmarkdown")8 Data Science Tools for Reporting
8.1 Documentation and Reporting Tools
An analysis nobody can read is an analysis nobody acts on. This chapter covers the tools for turning one into a document — and specifically into a document that rebuilds itself from the data, so a corrected figure costs a re-render rather than an afternoon.
8.1.1 Markdown: The Foundation of Documentation
Markdown is a lightweight markup language that’s easy to read and write. It forms the basis of many documentation systems.
It is what GitHub renders, what Jupyter cells hold, and what Quarto and R Markdown are built on, so it’s worth ten minutes even if you learn nothing else here.
8.1.1.1 Basic Markdown Syntax
# Heading 1
## Heading 2
### Heading 3
**Bold text**
*Italic text*
[Link text](https://example.com)

- Bullet point 1
- Bullet point 2
1. Numbered item 1
2. Numbered item 2
Table:
| Column 1 | Column 2 |
|----------|----------|
| Cell 1 | Cell 2 |
> This is a blockquote
`Inline code`
```python
# Code block
print("Hello, world!")
```The language name after the opening backticks is optional and only controls syntax highlighting. Later in this chapter you’ll see fences written as ```{python} with braces — that is Quarto and R Markdown’s own extension, marking a block as code to execute instead of merely display. Plain Markdown has no such concept.
The point of the syntax is that the raw file still reads as text. Asterisks around a word make it italic and also look like emphasis; a row of hashes marks a heading and also looks like one.
8.1.2 R Markdown
R Markdown combines R code, output, and narrative text in a single document that can be rendered to HTML, PDF, Word, and other formats.
The idea behind it — “literate programming”, writing the explanation and the code as one document — was proposed by Donald Knuth in 1984 (Knuth 1984).
8.1.2.1 Installing and Using R Markdown
If you’ve installed R and RStudio as described earlier, R Markdown is just a package installation away:
To create your first R Markdown document:
- In RStudio, go to File → New File → R Markdown
- Fill in the title and author information
- Choose an output format (HTML, PDF, or Word)
- Click “OK”
RStudio creates a template document with examples of text, code chunks, and plots, so you don’t start from scratch.
A typical R Markdown document consists of three components:
- YAML Header: The block fenced by
---at the top, holding metadata like title, author, and output format. YAML is a plain-text format ofkey: valuesettings, with indentation indicating nesting; you’ll meet it again in Docker Compose files and GitHub Actions workflows - Text: Written in Markdown for narratives, explanations, and interpretations
- Code Chunks: R code that can be executed to perform analysis and create outputs
For example:
---
title: "My First Data Analysis"
author: "Your Name"
date: "2026-04-30"
output: html_document
---
# Introduction
This analysis explores the relationship between variables X (carat) and Y (price).
## Data Import and Cleaning
```{r setup, eval=FALSE}
# load the diamonds dataset from ggplot2
data(diamonds, package = "ggplot2")
# Create a smaller sample of the diamonds dataset
set.seed(123) # For reproducibility
my_data <- diamonds |>
dplyr::slice_sample(n = 1000) |>
dplyr::select(
X = carat,
Y = price,
cut = cut,
color = color,
clarity = clarity
)
# Display the first few rows
head(my_data)
```
## Data Visualisation
```{r visualization, eval=FALSE}
ggplot2::ggplot(my_data, ggplot2::aes(x = X, y = Y)) +
ggplot2::geom_point() +
ggplot2::geom_smooth(method = "lm") +
ggplot2::labs(title = "Relationship between X and Y")
```Note that we’ve used the namespace convention (package::function()) in the code above rather than loading each package with library(). This is a matter of preference, but benefits include:
- Avoids loading the full package with
library() - Prevents naming conflicts (e.g.
dplyr::filter()vs.stats::filter()) - Keeps dependencies explicit and localised right next to each call
For the same reason the pipe above is |>, R’s native pipe, which is part of base R since version 4.1 and needs no package. The older %>% comes from magrittr and requires library(dplyr) or library(magrittr) first.
When you click the “Knit” button in RStudio, the R code in the chunks is executed, and the results (including plots and tables) are embedded in the output document. Code, results, and narrative live in one file, so there is no step where a number is copied from a console into a paragraph and then goes stale. If your data changes, re-knit the document and every figure and table updates.
That link between data, analysis and conclusion is what makes the document checkable. Someone who doubts a figure can read the chunk that produced it instead of asking you.
8.1.3 Jupyter Notebooks for Documentation
Notebooks do the same job as R Markdown from the Python side: code, output and prose in one file. The difference is that a notebook stores its output alongside the code, so it can be read without being re-run — convenient for sharing, and the reason two notebooks that look identical can disagree.
8.1.3.1 Exporting Jupyter Notebooks
Jupyter notebooks can be exported to various formats:
- In JupyterLab or Notebook 7, go to File → Save and Export Notebook As
- Choose from options like HTML, PDF, Markdown, etc.
Alternatively, you can use nbconvert from the command line:
jupyter nbconvert --to html my_notebook.ipynbOne notebook, several outputs: PDF for a formal report, HTML for a website, Markdown for a repository. Worth knowing because it stops you maintaining three versions of the same analysis.
8.1.3.2 Jupyter Book
For larger documentation projects, Jupyter Book builds on the notebook format to create complete books:
# Install Jupyter Book
pip install jupyter-book
# Create a project and start a live-reloading preview
jupyter book startJupyter Book organises multiple notebooks and markdown files into a book with navigation, search, and cross-references — useful for multi-page documentation, tutorials, or course materials.
Jupyter Book 2, built on the MyST document engine, is what pip install jupyter-book now gives you, and the command you type — its command-line interface, or CLI — is jupyter book, two words. Version 1’s commands were hyphenated and different: jupyter-book create my-book followed by jupyter-book build my-book/. Most tutorials you’ll find still show the v1 form, which fails with a “command not found” error against a current install.
8.1.4 Quarto: The Next Generation of Literate Programming
Quarto is a newer system that works with both Python and R, unifying the best aspects of R Markdown and Jupyter notebooks.
# Install Quarto CLI from https://quarto.org/docs/get-started/
# Create a new Quarto project (website, book, manuscript, etc.)
quarto create project default my-project
# Or, for a standalone one-off document, simply create a file
# named document.qmd in your editor (no command needed).
# Render a document to HTML (or PDF, docx, etc.)
quarto render document.qmdQuarto is R Markdown’s successor and works the same way for R, but it doesn’t assume R. Python, Julia and Observable chunks all execute, and a single document can mix them. If you work in both Python and R, it means one document format rather than two.
8.1.4.1 Quarto Dashboards
Since Quarto 1.4 (released in 2024), Quarto can render a document directly into a dashboard layout with rows, columns, value boxes, and tab sets, with no Shiny, Dash, or Streamlit required for the static case. If all you need is a periodic refresh of a dashboard view over your data, this is by far the lightest way to get there: you write a normal .qmd file with a format: dashboard YAML key, and Quarto handles the layout.
---
title: "Sales Overview"
format: dashboard
---#| title: "Revenue by Quarter"
ggplot(sales, aes(quarter, revenue)) + geom_col()For interactive behaviour (filters, user inputs), Quarto dashboards can embed Shiny, Observable JS, or even Python/R running in the browser via webR/Pyodide. For readers whose dashboards are read-only and updated nightly, this alone replaces a lot of what used to require a Shiny or Dash server.
8.1.4.2 Parameterised Reports
One of the highest-value Quarto (and R Markdown) features for a business analytics audience is parameterised reports: a single template that you can render with different inputs to produce many tailored outputs. For example, a monthly sales report that takes a region and month parameter and can be rendered once for every region without copy-pasting the document.
---
title: "Sales Report"
format: html
params:
region: "South Africa"
month: "2026-03"
---How you read those values inside the document depends on the engine.
With the knitr engine (R), the YAML block above is all you need — the values arrive in a params list:
cat("Report for", params$region, "covering", params$month)With the Jupyter engine (Python), Quarto follows papermill’s convention instead: you tag one code cell parameters, assign defaults there as ordinary variables, and refer to them as ordinary variables everywhere else. There is no params object.
#| tags: [parameters]
region = "South Africa"
month = "2026-03"print(f"Report for {region} covering {month}")Either way, you render with -P:
quarto render sales.qmd -P region:"EU" -P month:"2026-03"This single pattern replaces a surprising amount of the ad-hoc “one notebook per client” sprawl that plagues data science teams.
8.1.5 LaTeX for Professional Document Creation
When a report needs to look like a journal article or a formal business document, LaTeX is the typesetting system that gets it there. Markdown handles simple documents well; LaTeX gives you precise page layout, mathematical notation to a standard nothing else matches, figures and tables that number and cross-reference themselves, and bibliographies through BibTeX — all from plain text, so it versions in Git like any other source file.
LaTeX works differently from word processors: you write plain text with special commands, then compile it to a PDF. You don’t need to learn those commands to get the benefit. Quarto and R Markdown generate them from your document and run the compiler for you, so what follows is how to install that compiler and configure Quarto to drive it, rather than a course in LaTeX itself. If you do end up writing raw LaTeX, Quarto’s PDF documentation and the TeX Stack Exchange are where to go.
8.1.5.1 Installing LaTeX for Quarto and R Markdown
The easiest way to install LaTeX for use with Quarto or R Markdown is to use TinyTeX, a lightweight LaTeX distribution:
In R:
install.packages("tinytex")
tinytex::install_tinytex()In the command line with Quarto:
quarto install tinytexTinyTeX is built for R Markdown and Quarto users. It installs a minimal set of LaTeX packages rather than the several gigabytes a full distribution takes, and pulls down anything else it needs the first time a document calls for it.
8.1.5.2 Using LaTeX with Quarto
Quarto makes it easy to incorporate LaTeX features while keeping your document source readable. Here’s how to configure Quarto for PDF output using LaTeX:
8.1.5.2.1 YAML Configuration
In your Quarto YAML header, specify PDF output with LaTeX options:
---
title: "Analysis Report"
author: "Your Name"
format:
pdf:
documentclass: article
geometry:
- margin=1in
fontfamily: libertinus
colorlinks: true
number-sections: true
fig-width: 7
fig-height: 5
cite-method: biblatex
biblio-style: apa
---8.1.5.2.2 Customising PDF Output
You can further customise the LaTeX template by:
Including raw LaTeX: Use the
rawattribute to include LaTeX commands```{=latex} \begin{center} \large\textbf{Confidential Report} \end{center} ```Adding LaTeX packages: Include additional packages in the YAML
format: pdf: include-in-header: text: | \usepackage{siunitx} \usepackage{algorithm2e}Using a custom template: Create your own template for full control
format: pdf: template: custom-template.tex
8.1.5.2.3 Equations in Quarto
Quarto supports LaTeX math syntax directly:
The linear regression model can be represented as:
$$
y_i = \beta_0 + \beta_1 x_i + \epsilon_i
$$
where $\epsilon_i \sim N(0, \sigma^2)$.8.1.5.2.4 Citations and Bibliography
For managing citations, create a BibTeX file (e.g., references.bib):
@article{knuth84,
author = {Knuth, Donald E.},
title = {Literate Programming},
year = {1984},
journal = {Comput. J.},
volume = {27},
number = {2},
pages = {97--111}
}Then cite in your Quarto document:
Literate programming [@knuth84] combines documentation and code.And configure in YAML:
bibliography: references.bib
csl: ieee.csl # Citation style8.1.6 Troubleshooting LaTeX Issues
LaTeX can sometimes produce cryptic error messages. Here are solutions to common issues:
8.1.6.1 Missing Packages
If you get an error about a missing package when rendering:
! LaTeX Error: File 'tikz.sty' not found.
TinyTeX normally resolves this for you: when it hits a missing .sty file it looks up which TeX Live package provides it, installs it, and recompiles. That happens by default, so most of the time the error above is one you never see.
When it doesn’t resolve automatically, point TinyTeX at the compilation log and let it work out what’s missing:
tinytex::parse_install("document.log")Installing by hand is a last resort, and the trap is that the TeX Live package name rarely matches the file name — tikz.sty ships in a package called pgf, not tikz:
tinytex::tlmgr_install("pgf")8.1.6.2 Figure Placement
If figures aren’t appearing where expected:
\begin{figure}[!htbp] % The ! makes LaTeX try harder to respect placement8.1.6.3 Large Tables Spanning Multiple Pages
For large tables that need to span pages:
\begin{longtable}{lrrr}
\caption{Model Results}\\
\toprule
Model & Accuracy & Precision & Recall \\
\midrule
\endhead
% Table contents...
\bottomrule
\end{longtable}8.1.6.4 PDF Compilation Hangs
If compilation seems to hang, it is usually waiting for input at LaTeX’s interactive error prompt. TinyTeX passes -halt-on-error by default so this is rare, but if you are calling LaTeX yourself, or a package has re-enabled the prompt, force it to fail rather than wait:
# In R
tinytex::latexmk('document.tex', engine_args = '-interaction=nonstopmode')Then read document.log — the first ! line in that file is the real error.
8.1.7 LaTeX or Typst?
LaTeX has been the standard for scientific documentation for decades. Quarto and R Markdown default to it when you ask for a PDF, which is why it is worth knowing even if you never write a .tex file by hand — and why the errors above are errors you will eventually meet.
It also carries a real learning curve, and for most of what this book’s readers produce, there is now a second option.
8.1.7.1 Typst
Typst is a newer typesetting system aimed at the same output with less ceremony. Its markup is closer to Markdown than to LaTeX:
// Typst syntax
= Introduction
== Subsection
$x = (a + b) / c$ // Math notation
#figure(
image("plot.png"),
caption: "Sample Plot"
)Compare this to equivalent LaTeX:
% LaTeX syntax
\section{Introduction}
\subsection{Subsection}
$x = \frac{a + b}{c}$
\begin{figure}
\includegraphics{plot.png}
\caption{Sample Plot}
\end{figure}It also compiles in a fraction of the time, which you notice when you’re rendering a document every few minutes, and its error messages name the line and say what’s wrong rather than leaving you to read a log.
8.1.7.2 Choosing Your Path Forward
Use LaTeX when something outside your control expects it: a journal template, a supervisor’s preamble, a package that exists nowhere else. Use Typst when nothing does. That covers most business and internal reporting, where nobody cares what produced the PDF.
You don’t have to choose up front. Quarto has shipped the Typst compiler since version 1.4, so switching engines is a one-line YAML change and your content stays as it is:
---
title: "Sales Report"
format: typst # instead of: pdf
---There is a practical consequence worth stating plainly: format: typst produces a PDF without LaTeX installed at all. If PDF output is all you need, you can skip the TinyTeX installation and everything that follows from it.
8.1.8 Creating Technical Documentation
For more complex projects, specialised documentation tools may be needed:
8.1.8.1 MkDocs: Simple Documentation with Markdown
MkDocs creates a documentation website from Markdown files:
# Install MkDocs
pip install mkdocs
# Create a new project
mkdocs new my-documentation
# Serve the documentation locally
cd my-documentation
mkdocs serveMkDocs is focused on simplicity and readability. It generates a clean, responsive website from your Markdown files, with navigation, search, and themes — enough for project documentation aimed at users or team members, without a heavier system’s configuration burden.
8.1.8.2 Sphinx: Documentation Generated from Code
Sphinx is the documentation tool the Python ecosystem standardised on:
# Install Sphinx
pip install sphinx
# Create a new documentation project
sphinx-quickstart docs
# Build the documentation
cd docs
make htmlSphinx offers advanced features like automatic API documentation generation, cross-referencing, and multiple output formats. It’s the system behind the official documentation for Python itself and many major libraries like NumPy, pandas, and scikit-learn.
Sphinx became the standard for Python documentation largely because it generates reference documentation from docstrings — the descriptive text you write inside a function or class, which Python keeps attached to the object at runtime. You document each function where it lives, and Sphinx extracts and formats those descriptions into a browsable reference that can’t drift out of step with the code.
8.2 Reproducible Reports — Working with Data
When using external data files in Quarto projects, it’s important to understand how to handle file paths properly to ensure reproducibility across different environments.
8.2.1 Common Issues with File Paths
The error 'my_data.csv' does not exist in current working directory is a common issue when transitioning between different editing environments like VS Code and RStudio. This happens because:
- Different IDEs may have different default working directories
- Quarto’s rendering process often sets the working directory to the chapter’s location
- Absolute file paths won’t work when others try to run your code
8.2.2 Project-Relative Paths with the here Package
The here package provides an elegant solution by creating paths relative to your project root:
library(tidyverse)
library(here)
# Load data using project-relative path
data <- read_csv(here("data", "my_data.csv"))
head(data)The here() function automatically detects your project root (usually where your .Rproj file is located) and constructs paths relative to that location. This ensures consistent file access regardless of:
- Which IDE you’re using
- Where the current chapter file is located
- The current working directory during rendering
To implement this approach:
- Create a
datafolder in your project root - Store all your datasets in this folder
- Use
here("data", "filename.csv")to reference them
8.2.3 Alternative: Built-in Datasets
For maximum reproducibility, consider using built-in datasets that come with R packages:
# Load a dataset from a package
data(diamonds, package = "ggplot2")
# Display the first few rows
head(diamonds)A dataset that ships with a package has no path to get wrong, and anyone with the package has it. That makes built-in data the right choice for examples and tutorials, where which data you use rarely matters.
8.2.4 Creating Sample Data Programmatically
Another reproducible approach is to generate sample data within your code:
# Create synthetic data
set.seed(0491) # For reproducibility
synthetic_data <- tibble(
id = 1:20,
value_x = rnorm(20),
value_y = value_x * 2 + rnorm(20, sd = 0.5),
category = sample(LETTERS[1:4], 20, replace = TRUE)
)
# Display the data
head(synthetic_data)This approach works well for illustrative examples and ensures anyone can run your code without any external files.
8.2.5 Remote Data with Caching
For real-world datasets that are too large to include in packages, you can fetch them from reliable URLs:
# URL to a stable dataset (ggplot2's default branch is 'main', not 'master')
url <- "https://raw.githubusercontent.com/tidyverse/ggplot2/main/data-raw/diamonds.csv"
# Download and read the data
remote_data <- readr::read_csv(url)
# Display the data
head(remote_data)The cache: true option tells Quarto to save the results and only re-execute this chunk when the code changes, which prevents unnecessary downloads.
8.2.6 Best Practices for Documentation
Write it as you go. Documentation left until the end is documentation that doesn’t get written, and by then you’ve forgotten why you made half the choices you’re trying to describe.
Explaining a modelling choice in prose is also a reliable way to discover that you can’t justify it, which is worth more than the document.
8.3 Conclusion
This chapter covered the document side of data science output: Markdown and Quarto for literate programming, LaTeX and Typst for publication-quality typesetting, parameterised reports for templating, and data-loading patterns that survive being run on someone else’s machine.
By the end of this chapter you should have:
- One
.qmddocument that renders to HTML, containing both prose and a code chunk whose output appears in the result - The same document rendered to PDF, with TinyTeX installed — or Typst configured instead, if you’d rather not install a LaTeX distribution at all
- A report that loads its data by a path that works on someone else’s machine, using
here()or an equivalent, rather than an absolute path from yours - At least one parameterised render, so you’ve seen the same document produce two different outputs from the same source
What we haven’t covered yet are the charts that live inside these documents and the interactive applications that extend beyond them:
- The Data Visualisation chapter covers the static and lightly-interactive plotting libraries you’ll embed in reports (matplotlib, seaborn, plotly, ggplot2), plus Mermaid for code-based diagrams.
- The Web Development for Data Scientists chapter covers the tools for when “rerun the render” isn’t enough and stakeholders need to poke at the data themselves: Shiny, Dash, Streamlit, and Flask.
Pick whichever matches what your reader actually needs. A rendered document is enough far more often than the request for a dashboard suggests.