3  Documenting your code

Code that works today can be a mystery in six months, even to the person who wrote it. Good documentation means that you, your supervisor or a colleague can later understand what you did, why you did it, and run it again to get the same result.

Since the first chapter, you have been working in a Quarto document, with your code in chunks and your notes in between. This chapter shows how to get the most out of it:

3.1 Markdown

The text in a Quarto document is written in Markdown. Markdown is a simple way to format plain text. You type a few special characters, and a program turns them into formatted text. Some examples:

You type You get
# Title a heading of the first level
## Subtitle a heading of the second level
**bold** bold
*italic* italic
`code` code
* item or - item at the start of a line a bullet point
1. item at the start of a line a numbered list
[link text](https://quarto.org) link text

Leave an empty line before a heading and before a list. Otherwise Markdown may not recognise them.

Why use Markdown instead of a word processor?

  • A Markdown file is a plain text file. You can open it with any text editor, on any operating system, and it will still be readable in many years.
  • It is used in many places, for example on GitHub, in Jupyter notebooks and in Quarto. This book itself is written in Markdown.

This tutorial does not cover Markdown in detail, but there is good material online:

3.2 What to write between your chunks

The text between your chunks is where your analysis becomes understandable. A few questions can help you decide what to write:

  • What do I do here, and why? For example: “I remove the plants without a root length measurement, because they cannot be used for the comparison.”
  • What do I see? Describe the result in your own words, even if it seems obvious. For example: “The table has 594 rows, one per plant.”
  • What surprises me? Unexpected values, odd groups, numbers that are much larger or smaller than you thought.
  • What questions do I have? Things you want to check later, or ask your supervisor. Writing them down while you work means you don’t forget them.

Here is an example of how this can look:

## Read the growth data

We read in the measurements of the plant experiment.

```{r}
growth_data <- read.table("data/Growth_Data.txt", sep = "\t", header = TRUE, quote = "")
head(growth_data)
dim(growth_data)
```

The table has one row per plant and 594 plants in total. The columns are
the phosphate level, the bacterial treatment, the experiment, the root
length and the shoot weight.

Questions:

- Some values are `NA`. How many are there, and are they in one group?
- Why are there three experiments? Were they done at different times?

Your notes don’t need to be polished. They are mainly for you and the people you work with. Short, honest notes are much more useful than none.

3.3 Comments in your code

Inside a chunk, you can write comments. Everything on a line after a # is a comment. R ignores it, so you can write anything you like there:

# this whole line is a comment
x <- 5 # a comment can also follow some code

Use the text between chunks for longer notes, and comments for short notes about a single line of code. Good comments explain why you do something, not only what the code does. The code already shows what happens; the reason is often the part you forget.

# not very helpful: says the same as the code
growth_data <- growth_data[growth_data$Rootlength > 0, ]

# more helpful: explains why
# remove plants with root length 0, these seeds did not germinate
growth_data <- growth_data[growth_data$Rootlength > 0, ]
Tip

In RStudio, you can turn several lines into comments at once: select them and press Ctrl+Shift+C (Cmd+Shift+C on Mac). Press it again to remove the # signs.

Note

In an R script (a .R file), comments are the only place for notes. There, it helps to start the script with a few comment lines that say who wrote it, when, and what it is for:

# Analysis of root length in the growth experiment
# Author: your name
# Date: 2026-10-19
# Purpose: test whether the bacterial strains change root length under low phosphorus

In a Quarto document, the header at the top does this job (see below).

3.4 Turn your document into a report

You already know the three parts of a Quarto document from the first chapter: the header, the text and the code chunks. Here we look at the header in more detail and at how to make a report from your document.

3.4.1 The header

The header at the top of the document, between the two lines of ---, holds the settings of your document. It is a good place to say who wrote the document, when, and what it is about:

---
title: "Root length in the growth experiment"
author: "Your name"
date: "2026-10-19"
format: html
---

format: html tells Quarto to make an HTML page, which you can open in any web browser. Other formats are possible, for example pdf or docx (a Word file), but they may need extra software.

3.4.2 Render your document

To create a report, click the Render button at the top of the document. Quarto then runs all code from top to bottom in a fresh R session and puts the text, the code and its results (tables, plots) together. The report is saved next to your .qmd file, here as an HTML file with the same name. RStudio usually also opens it for you.

Tip

Because rendering starts from a fresh R session, it only works if your document contains every step: loading packages, reading the data, and so on. If it renders without errors, you know that your analysis does not secretly depend on something you once typed into the Console.

If rendering fails, Quarto prints an error message in the Background Jobs tab next to the Console. It tells you in which chunk the problem is. Fix the chunk, run it on its own to check, and render again.

3.4.3 Chunk options

You can control what a chunk does and what appears in the report with chunk options. In Quarto, you write them at the top of the chunk, on lines starting with #|:

```{r}
#| echo: false
#| warning: false

head(growth_data)
```

Some useful options:

  • echo: false hides the code, but shows its results in the report. Useful for plots.
  • eval: false shows the code, but does not run it.
  • include: false runs the code, but shows neither the code nor its results. The results can still be used by later chunks.
  • warning: false hides warnings in the report.
  • message: false hides messages, for example the ones some packages print when you load them.
  • fig-cap: "..." adds a caption to a plot.

You find all options on the Quarto website.

3.4.4 Optional: using languages other than R

You do not need this for the tutorial.

Quarto documents can also contain code in other languages, such as bash (the language of the Linux and Mac command line) or Python. You only change the name in the curly brackets at the top of the chunk, for example ```{bash} instead of ```{r}.

This is useful if part of your analysis runs on the command line, for example on a computer cluster, and another part in R. You can document both steps in the same file.

Some examples with bash:

# print something to the screen
echo 'hello world'
hello world
# list the files in our data folder
ls data
Genes_of_interest.txt
Genotype_Meristem.txt
Genotype_Rootlength.txt
Growth_Data.txt
Metabolism_Table_KO_Apr2020.txt
Timecourse.txt
UAP2_Annotation_table_u.txt
ar14_arCOGdef19.txt
make_example_data.R
mapping.txt
mmc5.xlsx
# show the first lines of the growth data
head -n 3 data/Growth_Data.txt
Nutrient    Condition   Experiment  Rootlength  FW_shoot_mg
low_P   mock    ex61    40.4    6.6
low_P   mock    ex61    38  6.8

To run bash chunks, bash must be installed on your computer. On Mac and Linux it already is. On Windows, it is available for example through Git for Windows or WSL. If you don’t have bash, these chunks give an error, but this does not affect your R chunks.

A general introduction into bash and awk is provided in a separate tutorial on GitHub.

3.5 Good practices

These practices are useful in any programming language. You don’t need to follow all of them from day one, but the earlier they become habits, the easier your life will be.

  • Keep each project in its own folder, with subfolders for input data and output files, and use an RStudio project (see Getting started).
  • Fill in the header of your document: who wrote it, when, and what it is about.
  • Load all packages at the top of your document, so you see at once what it needs.
  • Write down what you see and what you wonder about between your chunks, not only the code.
  • Comment your code so that you can still understand it in 5 years. Explain why, not only what.
  • Use descriptive names for objects: short, but clear enough that you know what is inside, for example growth_data instead of df1.
  • Break your code into small steps and check the result of each step, for example with head() and dim(). One line per step is easier to read and to fix than one long line that does everything.
  • Render your document from time to time. This checks that it still runs from top to bottom without errors.
  • Never change your raw data by hand. Read it into R, change it there, and save the result in a new file. Then your document records every change.
  • Save tables, not workspaces. Write the tables you need with write.table() instead of saving the whole workspace. The workspace hides how the objects were made; your document and the tables don’t.
  • If you use random numbers, for example for sampling, set and report the starting point with set.seed(), for example set.seed(42). Then you get the same random numbers each time you run the code.
  • Record the software versions you used. Packages change over time, and the same code can give different results or errors with another version. The function sessionInfo() prints your R version and all loaded packages with their versions. Run it at the end of your document:
sessionInfo()
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default


locale:
[1] LC_COLLATE=English_Netherlands.utf8  LC_CTYPE=English_Netherlands.utf8   
[3] LC_MONETARY=English_Netherlands.utf8 LC_NUMERIC=C                        
[5] LC_TIME=English_Netherlands.utf8    

time zone: Europe/Amsterdam
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
 [1] htmlwidgets_1.6.4 compiler_4.4.2    fastmap_1.2.0     cli_3.6.5        
 [5] tools_4.4.2       htmltools_0.5.8.1 yaml_2.3.11       rmarkdown_2.30   
 [9] knitr_1.50        jsonlite_1.9.0    xfun_0.54         digest_0.6.39    
[13] rlang_1.1.6       evaluate_1.0.5