RMarkdown and knitr

Marc Los Huertos

EA030 · Compiled 2026-04-14

What is Rmd? Anatomy YAML Header Markdown Prose Code Chunks Chunk Options Inline R The Setup Chunk Images Citations Rendering Troubleshooting

What this module covers. An R Markdown file (.Rmd) weaves prose, code, and output into a single reproducible document. When you click Knit, knitr runs every chunk, captures its output, and pandoc converts everything to HTML, PDF, or Word. This module walks through every part of an Rmd file from scratch — YAML headers, markdown syntax, chunk options, inline R, and the rendering pipeline — so you understand not just what to write but why it works.


1. What Is R Markdown?

A plain .R script runs code and prints results to the Console. A .Rmd file does the same thing, but also lets you write formatted prose around that code — headings, paragraphs, bullet lists, equations — and produce a polished output document that a reader who has never opened RStudio can read.

The three technologies involved are:

Layer Tool Role
Text markup Markdown Headings, bold, lists, links, equations
Code execution knitr Runs R chunks, captures output and plots
Document conversion pandoc Converts the combined result to HTML/PDF/Word

You never call pandoc or knitr directly. Clicking Knit (or running rmarkdown::render()) triggers all three in sequence.

💡 Create your first Rmd now In RStudio: File → New File → R Markdown…. Choose HTML output. RStudio generates a template with all three sections already present — read it, then delete everything below the setup chunk and start fresh.


2. The Anatomy of an Rmd File

Every Rmd file has exactly three kinds of content, always in this order at the top:

YAML Header
--- title: “My Report” author: “Marc Los Huertos” output: html_document ---
Setup Chunk
{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE, warning = FALSE)
Code Chunk
{r my-analysis} x <- c(1, 4, 9, 16, 25) mean(x)
Prose

Results


The mean value was 11. We collected data at five sites.

These three elements — YAML, chunks, and prose — can appear in any order after the header, interleaved as many times as needed. The YAML block must be the very first thing in the file.


3. The YAML Header

The YAML header controls the document’s metadata and output format. It is delimited by triple-dashes (---) and must appear at the very top of the file, before any other content.

Minimal header

---
title: "Air Quality Analysis"
author: "Marc Los Huertos"
date: "2024-03-15"
output: html_document
---

Dynamic date

Hard-coding the date means it goes stale. Use inline R instead so the date updates every time you knit:

date: "Compiled `r format(Sys.Date(), '%Y-%m-%d')`"

Output formats

The output: field determines what pandoc produces. The most common options:

output value Produces Requires
html_document Self-contained HTML file nothing extra
pdf_document PDF via LaTeX a LaTeX installation (TinyTeX)
word_document .docx Word file nothing extra
github_document Markdown for GitHub nothing extra

HTML document options

output:
  html_document:
    toc: true              # table of contents
    toc_float: true        # floating sidebar TOC
    toc_depth: 3           # include h1, h2, h3 headings
    number_sections: true  # auto-number headings
    theme: flatly          # Bootswatch CSS theme
    highlight: tango       # syntax highlighting style
    self_contained: true   # embed all CSS/JS — one portable file
    df_print: paged        # paginated data frames
    code_folding: hide     # show "Code" buttons, hide by default

⚠️ YAML is whitespace-sensitive Indentation in YAML is exactly two spaces — not tabs, not four spaces. A misaligned line causes a cryptic parse error and the document will not knit. If you see Error in yaml::yaml.load(), the problem is almost always indentation or a missing colon.

The custom knit: function

The modules in this course use a custom knit: field to redirect the output HTML to a specific folder. This is what lets the knit button send the file directly to the docs/ folder without any manual copying:

knit: (function(inputFile, encoding) {
    rmarkdown::render(
      inputFile,
      encoding = encoding,
      output_file = file.path(dirname(inputFile),
                              "../docs/RGuides/02_RMarkdown_knitr.html")
    )
  })

Without this, clicking Knit places the HTML in the same folder as the Rmd. The file.path(dirname(inputFile), "../docs/...") pattern navigates relative to the Rmd’s location — so the path works on any machine regardless of the absolute path to the project.

📁 dirname() and relative paths dirname(inputFile) returns the folder the Rmd lives in. “../docs/RGuides/…” then navigates one level up (..) and into docs/RGuides/. This means the Rmd and the docs folder must be siblings — both children of the same project root — for the path to resolve correctly.


4. Markdown Prose

Between and around chunks, you write plain text with lightweight markdown formatting. Pandoc converts this to HTML or whatever output format you chose.

Headings

# Level 1 heading   (one #)
## Level 2 heading  (two ##)
### Level 3 heading (three ###)

Headings also create anchor links. Adding {#my-id} after the heading text lets you link to it:

## Results {#results}

See the [Results section](#results) for details.

Block quotes and horizontal rules

> A blockquote — useful for key questions or callouts.

&#45;&#45;&#45;    (three dashes = horizontal rule)

Tables

| Column A | Column B | Column C |
|----------|----------|----------|
| value 1  | value 2  | value 3  |
| value 4  | value 5  | value 6  |

The colons in the separator row control alignment:

| Left     | Center   | Right    |
|:---------|:--------:|---------:|
| text     | text     | text     |

💡 For data frames, use knitr::kable() instead Never type a markdown table by hand for data. Use knitr::kable(df) or kableExtra — it generates the table automatically from your data so it updates when the data changes.

Equations

R Markdown supports LaTeX math using $ for inline and $$ for display:

The mean is $\bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i$.

$$
\text{PM}_{2.5} = \beta_0 + \beta_1 T + \varepsilon
$$

Renders as:

The mean is \(\bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i\).


5. Code Chunks

A code chunk is a fenced block that knitr executes. The output — printed values, tables, plots — is inserted into the document immediately below the chunk.

Chunk anatomy

Every chunk has the same structure:

```{r chunk-label, option1 = value, option2 = value}
# your R code here
x <- 1 + 1
x
```

Inserting a chunk

Three ways:

  1. Keyboard shortcut: Ctrl+Alt+I (Windows/Linux) or Cmd+Option+I (Mac)
  2. Toolbar: the green Insert button → R
  3. Typing: type the backtick fence manually

What gets printed

Whatever R would print to the Console also appears in the document:

# Expressions that print their result automatically
42
## [1] 42
c(1, 2, 3)
## [1] 1 2 3
summary(c(2.1, 3.4, 2.8, 4.1, 3.9))
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    2.10    2.80    3.40    3.26    3.90    4.10

Assignments do not print unless you explicitly print the object or wrap in parentheses:

x <- 10      # silent — nothing appears below this line
x            # now it prints
## [1] 10
(y <- 20)    # wrapping assignment in () also prints
## [1] 20

Plots are output too

Any plot created inside a chunk is captured and embedded in the document:

distance_m  <- c(10, 25, 50, 100, 200, 400)
pm25_ug_m3  <- c(38, 29, 21, 15,  11,   8)

plot(distance_m, pm25_ug_m3,
     xlab = "Distance from highway (m)",
     ylab = expression(PM[2.5] ~ (µg/m^3)),
     pch  = 19, col = "#4a7c5e",
     main = "PM2.5 vs Distance from Source")
lines(distance_m, pm25_ug_m3, col = "#4a7c5e", lwd = 1.5)
Particulate matter decreases with distance from the highway.

Particulate matter decreases with distance from the highway.


6. Chunk Options

Chunk options control what knitr does with each chunk: whether to show the code, whether to run it, how large to make the figure. They go inside the {r label, ...} header, separated by commas.

The essential options

echo
TRUE / FALSE
Show the source code in the output? FALSE hides code but still runs it and shows output.
eval
TRUE / FALSE
Run the chunk? FALSE shows code as a formatted block but does not execute it.
include
TRUE / FALSE
Include anything in the output? FALSE runs the chunk silently — code and output both hidden. Use for setup chunks.
message
TRUE / FALSE
Show message() output? Set FALSE to suppress library loading messages.
warning
TRUE / FALSE
Show warnings? Set FALSE to suppress, but check warnings interactively first — never hide problems you haven’t understood.
error
TRUE / FALSE
Default FALSE means an error stops the knit. Set TRUE to show the error in the output and continue knitting — useful in teaching documents.
fig.width
number (inches)
Width of the output figure in inches. Default 7. Combine with fig.height to control aspect ratio.
fig.height
number (inches)
Height of the output figure in inches. Default 5.
fig.cap
character string
Figure caption. Appears below the plot. Supports inline R for dynamic captions.
out.width
“100%”, “70%”, “400px”
Width of the figure in the HTML output. Use “100%” to fill the column, or a percentage to shrink it.
cache
TRUE / FALSE
Save chunk results and skip re-running if code hasn’t changed. Useful for slow model fits. Use with care — can cause stale results.
results
‘markup’, ‘hide’, ‘asis’, ‘hold’
‘hide’ suppresses printed output. ‘asis’ passes raw HTML/LaTeX through (for kable). ‘hold’ collects all output and prints at the end of the chunk.

Common combinations

Rather than listing chunk headers in a code block (which knitr would try to parse), here is a reference table of the most useful combinations:

Goal Options to set
Show code and output (default) nothing — this is the default
Show output only, hide code echo = FALSE
Show code only, don’t run eval = FALSE
Run silently, show nothing include = FALSE
Show plot only, hide code and messages echo = FALSE, message = FALSE
Show error in output without stopping knit error = TRUE
Show figure with caption at a specific size fig.cap = "...", fig.width = 6, fig.height = 4

Each option goes inside the chunk header after the label, separated by commas:

# Example: a results table — output only, no code shown
# ```{r results-table, echo=FALSE}

# Example: install instructions — show code but never run
# ```{r install-packages, eval=FALSE}

# Example: setup — run silently, nothing in output
# ```{r setup, include=FALSE}

Seeing options in action

The next three chunks are identical code but with different options — compare what appears in the rendered output:

# echo=TRUE (default) — you see this code AND the output below
sqrt(144)
## [1] 12
## [1] 13
# results='hide' — code appears, output is suppressed
sqrt(196)

fig.show — controlling multi-plot chunks

When a chunk produces more than one plot, fig.show controls whether they share a figure environment or each get their own:

```{r two-plots, fig.show='hold', fig.cap="Both plots side by side."}
# hold — both plots collected and placed together in one figure
hist(rnorm(200))
hist(rexp(200))
```


``` r
# asis (default) — each plot placed immediately where it is produced
hist(rnorm(200))
```

First plot.

First plot.

``` r hist(rexp(200)) ```
Second plot.

Second plot.

Note the fig.cap as a character vector — when fig.show = 'asis' produces multiple figures from one chunk, supply a caption per plot:

{r figcap-vector-demo, fig.show='hold', fig.width=3.5, fig.height=3, fig.cap=c("PM2.5 — roughly normal.", "PM10 — right-skewed.")} set.seed(1) hist(rnorm(300, mean = 12, sd = 4), main = "PM2.5", xlab = "µg/m³", col = "#4a7c5e", border = NA) hist(rexp(300, rate = 0.08), main = "PM10", xlab = "µg/m³", col = "#c8832a", border = NA)

ref.label — reusing a chunk without repeating code

ref.label runs a previous chunk again without copying its code. This is useful when you want to show code in one place and output in another, or produce the same plot at two different sizes:

```{r my-plot, echo=TRUE, fig.show='hide'}
# Define and show the code here — suppress the figure with fig.show='hide'
plot(1:10, (1:10)^2, pch = 19, col = "#4a7c5e",
     xlab = "x", ylab = expression(x^2))
```

Some prose explaining the plot...


results = 'asis' — passing raw HTML to the output

The default results = 'markup' wraps printed output in a code block. results = 'asis' passes the output directly into the document as raw HTML or markdown — required any time you generate tables or headings programmatically:

# knitr::kable() generates an HTML table — needs results='asis' to render
# Without it, you'd see raw HTML tags as plain text
knitr::kable(
  data.frame(
    Site    = c("Kern", "Mojave", "Coast"),
    PM2.5   = c(12.1, 8.4, 18.0),
    n       = c(5L, 4L, 6L)
  ),
  caption = "Summary table generated inside a chunk."
)
Summary table generated inside a chunk.
Site PM2.5 n
Kern 12.1 5
Mojave 8.4 4
Coast 18.0 6

⚠️ kable() inside a for loop requires results=‘asis’ If you loop over groups and call knitr::kable() inside the loop, you must set results=‘asis’ on that chunk or the tables will appear as raw HTML text. The same applies to any function that emits raw HTML — kableExtra, gt, DT::datatable().


7. Inline R Code

Inline R lets you embed a computed value directly in a sentence, so the number updates automatically every time you knit. The syntax is a single backtick, the letter r, a space, an R expression, and a closing backtick:

The mean PM2.5 was `r round(mean(pm25_week), 1)` µg/m³ across the study period.

Anything that evaluates to a single printable value works: arithmetic, function calls, object lookups, format() calls for formatting.

The source text below contains inline R — look at the rendered output:

Source:

We monitored PM2.5 at 7 sites between
March 04 and March 10, 2024.
The weekly mean was 14.3 µg/m³
(median: 9.7 µg/m³;
max: 42.3 µg/m³ on Thursday).

Renders as:

We monitored PM2.5 at 7 sites between March 04 and March 10, 2024. The weekly mean was 14.3 µg/m³ (median: 9.7 µg/m³; max: 42.3 µg/m³ on Thursday).

🔗 Why inline R matters for reproducibility If you type a number directly into prose — “the mean was 14.3 µg/m³” — and later discover a data error and rerun the analysis, the number in the prose stays wrong while your table updates. Inline R ensures the prose number is always derived from the same computation as everything else.

Formatting numbers for prose

x <- 12345.6789

# Too many decimals for prose
x
## [1] 12345.68
# Rounded
round(x, 2)
## [1] 12345.68
# Formatted with thousands separator
format(x, big.mark = ",", nsmall = 2)
## [1] "12,345.68"
# Scientific notation suppressed
format(1.23e-5, scientific = FALSE, digits = 4)
## [1] "0.0000123"
# Percentage
paste0(round(0.734 * 100, 1), "%")
## [1] "73.4%"

⚠️ Keep inline R expressions short Inline R is great for round(mean(x), 1). It is not meant for multi-step calculations. Compute your value in a chunk first, store it in a named object, then reference that object inline. Debugging a broken inline expression is much harder than debugging a broken chunk.


8. The Setup Chunk

Every Rmd should begin with a setup chunk immediately after the YAML header. It does three things: sets global chunk options, loads all packages, and optionally loads data.

```{r setup, include=FALSE}
# 1. Global chunk options — apply to every chunk unless overridden locally
knitr::opts_chunk$set(
  echo    = TRUE,     # show code by default
  warning = FALSE,    # suppress warnings globally
  message = FALSE,    # suppress package-load messages
  fig.width  = 7,     # default figure width (inches)
  fig.height = 4      # default figure height (inches)
)

# 2. Load all packages here — never scatter library() calls through the doc
library(tidyverse)
library(lubridate)
library(knitr)
library(kableExtra)

# 3. Optionally load data here so it is available to all later chunks
# df <- read_csv("data/field_data.csv")
```

The label setup is special — knitr always runs this chunk first, regardless of where it appears in the file. The option include=FALSE means none of this appears in the output: no code, no messages, no output.

Why load all packages at the top?

If library(ggplot2) is inside chunk 7 and the document fails during knit at chunk 5, you may spend time wondering why ggplot2 “isn’t loaded” — when the real error is earlier and ggplot2 never got a chance to load. A single setup chunk makes the dependency list visible and ensures it runs first.

Global vs local options

A local option on a single chunk overrides the global default from opts_chunk$set(). Use this to make a specific chunk behave differently without changing all chunks:


9. Images

There are two ways to embed an image: the plain markdown syntax for simple cases, and knitr::include_graphics() for anything that needs chunk options (sizing, captions, cross-references).

Markdown syntax

![Alt text describing the image](path/to/image.png)

![Caption shown below the image](figures/site_map.png){width=70%}

The path is relative to the Rmd file’s location. {width=70%} is a pandoc attribute that scales the image in HTML output. This approach is fine for decorative images but gives you no chunk-level control.

knitr::include_graphics() — the preferred approach

```{r site-map, echo=FALSE, fig.cap="Sensor deployment locations, Spring 2024.",
    out.width="80%", fig.align="center"}
knitr::include_graphics("figures/site_map.png")
```

Use out.width (not fig.width) for images loaded from disk — fig.width controls the size of generated R plots; out.width controls the display size of any figure in the output HTML.

Finding and specifying the path

The path is always relative to the Rmd file. If your project looks like:

project/
  reports/
    my_analysis.Rmd      ← Rmd lives here
  figures/
    site_map.png
  data/
    field_data.csv

Then the correct path inside the Rmd is "../figures/site_map.png" — one level up (..), then into figures/. The same logic applies to data files.

# Rmd is in reports/; figure is in figures/ at the project root
knitr::include_graphics("../figures/site_map.png")

# If figures/ is a subfolder of the same folder as the Rmd:
knitr::include_graphics("figures/site_map.png")

Supported formats

knitr::include_graphics() accepts .png, .jpg/.jpeg, .gif, and .svg for HTML output. For PDF output, use .png or .pdf — .gif and .svg are not supported by LaTeX.

💡 Screenshots and field photos For screenshots (e.g. of RStudio, an instrument interface, a map): save as .png (not .jpg — JPEG compression blurs text). Aim for 150–200 dpi at the intended display size. For field photos where file size matters, .jpg is fine.


10. Citations and Bibliography

R Markdown uses pandoc-citeproc to manage citations. You provide a .bib file (BibTeX format) and reference entries inline with @key syntax. Pandoc assembles the bibliography automatically at the end of the document.

Step 1 — Create a .bib file

A .bib file is a plain-text file containing reference entries. Each entry has a type (@article, @book, etc.), a cite key you invent, and fields:

@article{smith2022pm25,
  author  = {Smith, Jane and Jones, Robert},
  title   = {Particulate Matter Trends in Southern California},
  journal = {Environmental Science \& Technology},
  year    = {2022},
  volume  = {56},
  number  = {4},
  pages   = {2341--2350},
  doi     = {10.1021/acs.est.1c07890}
}

@book{wickham2023r4ds,
  author    = {Wickham, Hadley and Cetinkaya-Rundel, Mine and Grolemund, Garrett},
  title     = {R for Data Science},
  edition   = {2},
  year      = {2023},
  publisher = {O'Reilly Media},
  url       = {https://r4ds.hadley.nz}
}

@manual{r-base,
  title        = {R: A Language and Environment for Statistical Computing},
  author       = {{R Core Team}},
  organization = {R Foundation for Statistical Computing},
  address      = {Vienna, Austria},
  year         = {2024},
  url          = {https://www.R-project.org/}
}

Save this as references.bib in the same folder as your Rmd (or a bib/ subfolder — adjust the path in YAML accordingly).

💡 Getting BibTeX entries without typing them Most journal websites have a “Cite” or “Export citation” button — choose BibTeX format. Google Scholar has a quotation-mark icon → BibTeX. Zotero and Mendeley both export .bib files directly. The only thing you need to edit is the cite key (the first field after the opening brace) — make it memorable: smith2022pm25, not smith2022a.

Step 2 — Add bibliography: to the YAML header

---
title: "Air Quality Analysis"
author: "Marc Los Huertos"
output: html_document
bibliography: references.bib
csl: apa.csl               # optional — citation style (APA, MLA, etc.)
link-citations: true       # make in-text citations clickable links
---

Without csl:, pandoc uses its built-in Chicago author-date style. Download .csl files for other styles from the Zotero Style Repository.

Step 3 — Cite in your prose

Particulate matter exposure is linked to cardiovascular disease [@smith2022pm25].

Smith and Jones [-@smith2022pm25] found that PM2.5 declined 40% over two decades.

For an introduction to R, see @wickham2023r4ds [p. 42].

Multiple citations: [@smith2022pm25; @r-base].
Syntax Renders as
[@key] (Smith and Jones 2022)
[-@key] (2022) — suppress author name
@key Smith and Jones (2022) — narrative citation
[@key, p. 42] (Smith and Jones 2022, p. 42)
[@key1; @key2] (Smith 2022; Jones 2021)

Step 4 — The bibliography appears automatically

Pandoc places the formatted reference list at the end of the document by default. To control placement — for example, before a footer — insert a heading with the {#refs} div exactly where you want it:

## References

::: {#refs}
:::

&#45;&#45;&#45;
*Compiled on &#96;r Sys.Date()&#96;.*

Citing R packages

It is good practice to cite the R packages you use. knitr::write_bib() generates BibTeX entries for any installed packages automatically:

# Run once to generate/update the bib file for the packages you use
knitr::write_bib(
  c("base", "rmarkdown", "knitr", "tidyverse", "lubridate", "ggplot2"),
  file = "references.bib"
)

Then cite as normal: [@R-ggplot2], [@R-tidyverse]. The R- prefix is the key format write_bib() produces.

📚 nocite — bibliography entry with no in-text citation To list a source in the bibliography without citing it in the prose — a “Further reading” entry — add a nocite field to the YAML header:

nocite: |
  @wickham2023r4ds, @r-base

These appear in the reference list even though no @key appears in the body text.


11. Rendering the Document

The Knit button

The simplest way to render is the Knit button in the RStudio toolbar (the yarn-and-needle icon). It calls rmarkdown::render() on the current file, runs all chunks in a fresh R session, and opens the output in the Viewer pane.

From the Console

# Render to the default output format specified in YAML
rmarkdown::render("reports/my_analysis.Rmd")

# Render to a specific format regardless of YAML
rmarkdown::render("reports/my_analysis.Rmd",
                  output_format = "pdf_document")

# Render and save to a specific path
rmarkdown::render("reports/my_analysis.Rmd",
                  output_file  = "outputs/my_analysis.html")

What “fresh R session” means — and why it matters

When you click Knit, RStudio starts a new, empty R session to run your document. It does not use the objects already in your Environment pane. This is the most common source of “it works in the Console but fails to knit” confusion:

⚠️ Your Environment is not your document’s environment If you ran df <- read_csv(…) in the Console and forgot to put it in a chunk, df exists in your environment but not in the knit session. The document will error with object ‘df’ not found. The fix: every object the document needs must be created by a chunk inside the document.

Rendering order

knitr executes chunks strictly top to bottom. An object created in chunk 5 is not available in chunk 3, even if you created it in the Console first. If you get object not found, check whether the object is created before it is used.

# This chunk creates the object
site_data <- tibble::tibble(
  site    = c("Kern", "Mojave", "Coast"),
  pm25    = c(12.1, 8.4, 18.0)
)
# This chunk uses it — works because it comes AFTER the chunk that creates it
site_data
## # A tibble: 3 × 2
##   site    pm25
##   <chr>  <dbl>
## 1 Kern    12.1
## 2 Mojave   8.4
## 3 Coast   18

Render vs Source vs Run All

Action R session Chunks run Use for
Knit Fresh, empty All, top to bottom Final output, submission
Run All (Ctrl+Alt+R) Current session All, top to bottom Development — your objects are available
Run chunk (Ctrl+Shift+Enter) Current session Just this chunk Iterative development
Source on a .R file Current session — Running a plain R script

💡 Development workflow While building a document, use Run chunk to iterate quickly (you have access to your environment). Before submitting or sharing, always do a full Knit from scratch to confirm the document is truly self-contained.

Knit errors — reading the error output

When a knit fails, RStudio shows the error in the R Markdown tab. The format is always:

Quitting from R02_RMarkdown_knitr.Rmd:45-52 [chunk-label]
Error in ... : error message here

The numbers 45-52 are the line numbers where the failing chunk starts and ends. Go to that line in your Rmd to find the problem. The [chunk-label] confirms which chunk it was — another reason to always label your chunks.


12. Troubleshooting Common Problems

Checklist for “it runs but won’t knit”

# 1. Is every object the document needs created INSIDE a chunk?
#    (Not just in your Console environment)

# 2. Are all packages loaded with library() inside a chunk?
#    (library() calls in the Console do nothing for the knit session)

# 3. Is every file path correct relative to the Rmd's location?
#    (The knit session's working directory is the folder the Rmd lives in)
getwd()   # run in Console to see where your Rmd's working directory is

# 4. Are all chunk labels unique?
#    (Duplicate labels cause an immediate error before any code runs)

# 5. Does the chunk header use commas between options, not semicolons?
#    CORRECT:  ```{r my-chunk, echo=FALSE, fig.width=6}
#    WRONG:    ```{r my-chunk; echo=FALSE; fig.width=6}

Working directory in the knit session

The knit session’s working directory is the folder the Rmd lives in — not your RStudio project root, and not wherever you last called setwd(). This means all relative file paths in read_csv(), source(), etc. should be relative to the Rmd file, not to the project root.

# If your Rmd is at:  project/reports/my_analysis.Rmd
# And your data is at: project/data/field_data.csv
# Then the correct relative path in the Rmd is:
df <- read_csv("../data/field_data.csv")   # one level up, then into data/

# NOT:
df <- read_csv("data/field_data.csv")      # wrong — that looks for
                                           # project/reports/data/ which doesn't exist

YAML parse errors

Error in yaml::yaml.load(string, ...) :
  Scanner error: ...

This means the YAML header is malformed. Common causes:

Chunk delimiter errors

Error: attempt to use zero-length variable name

or

Unterminated chunk

These usually mean a chunk fence is wrong:

CORRECT opening:  ```{r my-label}
WRONG — missing r: ```{my-label}
WRONG — extra space: ``` {r my-label}

CORRECT closing:  ```
WRONG — has text:  ``` r
WRONG — two ticks: ``

Package not available

Error in library(ggplot2) : there is no package called 'ggplot2'

The knit session installs nothing automatically. Install missing packages in the Console with install.packages("ggplot2"), then knit again. Never put install.packages() in a chunk that will be evaluated during knit — it re-installs the package every time.


🔑 RMarkdown and knitr — key takeaways

  • Three parts, one file. YAML header (metadata and format), prose (markdown), and code chunks (R). They interleave freely; the YAML block must come first.
  • Knit runs a fresh session. Every object and package must be created inside the document. Objects in your Environment pane do not exist during knit.
  • Chunks run top to bottom. An object created in chunk 7 cannot be used in chunk 3. Read errors as line numbers and chunk labels.
  • The setup chunk is special. Label it setup, use include=FALSE, load all packages and set global options here. knitr always runs it first.
  • Local options override global ones. Set sensible defaults in opts_chunk$set() and override per-chunk only when needed.
  • fig.show=‘hold’ collects plots; ‘asis’ places each immediately. Use a character vector for fig.cap when one chunk produces multiple figures. Use ref.label= to reuse a chunk without repeating code.
  • results=‘asis’ is required for programmatic tables. Any function that emits raw HTML — knitr::kable(), kableExtra, gt — needs this option or its output appears as raw text.
  • Inline R keeps prose in sync. Write 1.23457^{4} in prose instead of typing a number. The value updates automatically when data changes.
  • Use knitr::include_graphics() for images. It respects chunk options (out.width, fig.cap, fig.align). Paths are relative to the Rmd file, not the project root.
  • Citations need a .bib file and a YAML bibliography: field. Cite with [@key] in prose; use knitr::write_bib() to generate entries for R packages automatically.
  • YAML is whitespace-sensitive. Two spaces for indentation, no tabs, colons after every key. Quote any value that contains special characters.
  • Working directory = Rmd folder. All relative paths in read_csv(), knitr::include_graphics(), and bibliography: are relative to the Rmd file’s location.

What’s next?