Prof. Marc Los Huertos • Pomona College • Environmental Analysis
Error Anatomy Errors vs Warnings vs Messages Debugging Strategy Error Catalogue Warning Catalogue Errors in RMarkdown Debugging Tools Getting Help
traceback(), browser(), and
options(warn = 2) for deeper debugging.
What this module covers. Every R user spends a substantial fraction of their time reading error messages. Most errors fall into a small set of recurring categories — and once you can recognize them, fixing them takes seconds rather than hours. This module builds two things: a mental model for reading any error, and a practical catalogue of the errors you will encounter most often in EA030 analyses.
Before looking at specific errors, it helps to understand what an error message is actually telling you. R error messages have a consistent structure — but they use technical vocabulary that can make them feel more opaque than they are.
# Deliberately trigger a common error
my_data <- data.frame(site = c("A", "B", "C"), temp = c(12.1, 14.5, 11.8))
mean(my_data$temperture) # typo: "temperture" instead of "temp"
## [1] NA
Error in mean(my_data$temperture) : argument “x” is missing, with no default
Every error message has at most three parts:
1. Error in [function call]: — tells
you which function call failed.
mean(my_data$temperture) is the immediate call, but the
root cause may be several steps earlier.
2. The message itself — describes what went
wrong. The vocabulary is terse and sometimes misleading (this
message says “argument missing” but the real problem is the column
doesn’t exist — $temperture returns NULL, and
mean(NULL) has no x argument).
3. A backtrace (not always shown) — a chain of
function calls that led to the failure. In RMarkdown knit output you
sometimes see Backtrace: followed by numbered frames. Read
from the bottom up — the bottom frame is the deepest point of
failure.
📌 The error message describes a symptom, not always the
cause. argument "x" is missing means R received
NULL where it expected a vector. The cause is the typo
$temperture. Always ask: “what value was passed to this
function, and where did that value come from?”
# A deeper error: function calls inside a pipeline
library(dplyr)
wq <- data.frame(site = c("A","B"), do_mgl = c(7.2, 8.1))
wq |>
group_by(site) |>
summarise(mean_do = mean(DO_mgl)) # wrong column name
## Error in `summarise()`:
## ℹ In argument: `mean_do = mean(DO_mgl)`.
## ℹ In group 1: `site = "A"`.
## Caused by error:
## ! object 'DO_mgl' not found
Error in summarise():
ℹ In argument:
mean_do = mean(DO_mgl).
Caused by error in
mean():
! object ‘DO_mgl’ not found
Modern dplyr (>= 1.1) formats errors with nested
Caused by lines — the innermost Caused by is
the actual problem. Here the chain is: summarise() called
mean(), which tried to find DO_mgl, which does
not exist (the column is named do_mgl).
R produces three distinct levels of output that are easy to confuse.
Error in read.csv(“data/water_quality.csv”) : cannot open file ‘data/water_quality.csv’: No such file or directory
An error halts execution immediately. No output is produced. Nothing after the error line runs. You must fix the error before continuing.
Warning message: In log(x) : NaNs produced
A warning means R completed the operation but encountered something suspicious. The result is returned — but it may be wrong. Warnings deserve the same attention as errors. Silent incorrect results caused by ignored warnings are among the hardest bugs to find.
# This runs without error but produces silent NaN values
x <- c(5, 2, -1, 8, -3, 4)
log_x <- log(x) # Warning: NaNs produced for negative values
## Warning in log(x): NaNs produced
log_x # The NaNs are in your data now
## [1] 1.6094379 0.6931472 NaN 2.0794415 NaN 1.3862944
⚠️ warning = FALSE in knitr hides warnings from
the rendered document — it does NOT suppress the underlying
problem. Many module templates include
warning = FALSE in the setup chunk so output is cleaner.
This is fine once a module is working, but while you are developing
code, always run with warning = TRUE or check
warnings() after execution.
── Attaching core tidyverse packages
──────────── tidyverse 2.0.0 ── ✔ ggplot2
3.5.1 ✔ purrr 1.0.2
✔ tibble 3.2.1 ✔ dplyr 1.1.4
✔ tidyr 1.3.0 ✔
stringr 1.5.1
A message is purely informational — R is telling you
something happened (a package loaded, a file was written, a model
converged). Messages do not indicate problems. Suppress them in final
reports with message = FALSE in the chunk header.
| Type | Execution | Result returned? | Action required? |
|---|---|---|---|
| Error | Stops | No | Yes — always |
| Warning | Continues | Yes, possibly wrong | Usually yes |
| Message | Continues | Yes, correct | No |
When you encounter an unfamiliar error, resist the urge to immediately Google the message. Work through these steps first — you will solve 80% of errors in under two minutes.
Caused by or In argument lines that follow.
Scroll up in the console — the error may be preceded by useful context.
Error in [X]: — X is the function that crashed. Find that
call in your code. Is it spelled correctly? Are you calling a function
that is not loaded?
class(), str(), head(),
dim(), and is.na() on the inputs.
Here is a realistic EA030 scenario.
# You are trying to compute monthly means from a CSV export
# that has the date column stored as character
sensor_df <- data.frame(
timestamp = c("2024-01-15", "2024-01-16", "not-a-date",
"2024-02-03", "2024-02-04"),
temp_C = c(12.1, 13.4, 11.8, 9.2, 10.5)
)
library(lubridate)
library(dplyr)
sensor_df |>
mutate(month = month(ymd(timestamp))) |>
group_by(month) |>
summarise(mean_temp = mean(temp_C))
## # A tibble: 3 × 2
## month mean_temp
## <dbl> <dbl>
## 1 1 12.8
## 2 2 9.85
## 3 NA 11.8
Step 1 — read the full output. The code ran without
error, but there is likely a warning (run with
warning = TRUE).
Step 2 — find the failing call. The warning would
say 1 failed to parse in ymd(). The timestamp
"not-a-date" could not be parsed.
Step 3 — inspect the inputs. Run
ymd(sensor_df$timestamp) alone and inspect the output — you
will see an NA in position 3.
Step 4 — simplify. The problem is one bad string in
your data column. Check with
sensor_df$timestamp[is.na(ymd(sensor_df$timestamp))] to
find and fix it.
# Fix: identify the bad row before computing
bad_rows <- which(is.na(ymd(sensor_df$timestamp)))
cat("Rows with unparseable timestamps:", bad_rows, "\n")
## Rows with unparseable timestamps: 3
cat("Values: ", sensor_df$timestamp[bad_rows], "\n")
## Values: not-a-date
# Then either fix the data or filter it out
sensor_clean <- sensor_df[-bad_rows, ]
sensor_clean |>
mutate(month = month(ymd(timestamp))) |>
group_by(month) |>
summarise(mean_temp = mean(temp_C))
## # A tibble: 2 × 2
## month mean_temp
## <dbl> <dbl>
## 1 1 12.8
## 2 2 9.85
The following errors account for the vast majority of what you will encounter in EA030. Each entry shows the exact message R produces, the most common cause, and the fix.
Error object ‘x’ not found ●●●●● most common
What it means. R looked for an object named
x in the current environment and could not find it. The
object either was never created, was created in a different session, or
its name is misspelled.
Most common causes.
# Cause 1: typo in object name
temperature_data <- c(12, 14, 11)
mean(temperture_data) # Error: object 'temperture_data' not found
# Cause 2: chunk ran out of order in RMarkdown — object from chunk 5
# is referenced in chunk 3, which runs first during knit
# Fix: re-order your chunks so dependencies flow top to bottom
# Cause 3: object created inside an if() or for() block with wrong scope
for (i in 1:5) { result_i <- i^2 }
result_i # Works — last iteration's value is in global env
# But if the for loop never ran (e.g., empty vector), result_i won't exist
# Cause 4: package function used before library() call
mutate(df, x = x * 2) # Error if dplyr is not loaded
Fix. Run ls() to list objects in your
current environment. Check spelling. In RMarkdown, ensure the chunk that
creates the object runs before the chunk that uses
it.
Error could not find function “x” ●●●●○ very common
What it means. R could not find a function named
x. Unlike “object not found” (which applies to data), this
applies to functions — it almost always means a package is not
loaded.
Most common causes.
# Cause 1: forgot library()
ggplot(df, aes(x = site, y = do_mgl)) + geom_col()
# Error: could not find function "ggplot"
# Fix:
library(ggplot2) # or library(tidyverse)
# Cause 2: typo in function name
raed_csv("data/wq.csv") # should be read_csv
filter(df, siet == "Upland") # filter is fine, but check the column name typo too
# Cause 3: package installed but namespace not loaded
vegan::rarefy(comm, sample = 50) # This works without library(vegan)
rarefy(comm, sample = 50) # Error — function not in search path
Fix. Add library(packagename) near the
top of your script, before any code that uses the package. Check
spelling with ?function_name — if the help page opens, the
function exists.
Error cannot open file ‘…’: No such file or directory ●●●●○ very common
What it means. R tried to read a file and could not find it at the path you specified.
Most common causes.
# Cause 1: wrong working directory
# R is looking in the wrong folder
read.csv("data/water_quality.csv")
# Fix: check and set your working directory
getwd()
setwd("/correct/path/to/project")
# Better: use RStudio Projects so working directory is always the project root
# Cause 2: path is relative to the wrong location
# In RMarkdown, the working directory during knit is the .Rmd file's location,
# not your console working directory
read.csv("../data/water_quality.csv") # go up one level, then into data/
# Cause 3: typo in filename (including wrong case — R is case-sensitive on Linux/Mac)
read.csv("data/Water_Quality.csv") # file is actually water_quality.csv
Fix. Run list.files("data/") to see
exactly what is in the folder. Use
file.exists("data/water_quality.csv") to test a specific
path before reading.
# Defensive pattern
path <- "data/water_quality.csv"
if (!file.exists(path)) stop("File not found: ", path)
df <- read.csv(path)
Error object of type ‘closure’ is not subsettable ●●●○○ common
What it means. You tried to use [ or
$ on a function rather than a data frame or
vector. A “closure” is R’s technical term for a function.
Most common cause. A variable name shadows a
function name — you used data, df,
filter, mean, or another common name for your
data object and for a function, and R found the function
first.
# Cause: common variable names that shadow functions
data <- read.csv("myfile.csv") # 'data' is also a base R function
data$site # Error: object of type 'closure' is not subsettable
# Fix: use a more specific variable name
wq_data <- read.csv("myfile.csv")
wq_data$site # works fine
# Other names to avoid as variable names:
# c, t, T, F, mean, sum, min, max, df, data, matrix, list, table
Fix. Rename your variable to something that does not
clash with a built-in function. Run is.function(data) — if
it returns TRUE, data is being interpreted as
the function.
Error replacement has X rows, data has Y rows ●●●○○ common
What it means. You tried to assign a vector to a data frame column, but the vector length does not match the number of rows.
wq <- data.frame(site = c("A","B","C","D"), do_mgl = c(7,8,6,9))
# Cause 1: vector is the wrong length
wq$log_do <- log(c(7, 8)) # Error: replacement has 2 rows, data has 4
# Cause 2: filter() or subset() happened upstream and changed nrow
wq_sub <- wq[wq$do_mgl > 7, ] # now 2 rows
wq$flag <- wq_sub$do_mgl > 7 # wq still has 4 rows — mismatch
# Fix: always operate on the same object
wq$log_do <- log(wq$do_mgl) # same-length operation
wq$flag <- wq$do_mgl > 7 # correct: compares within the same df
Fix. Check length() of the vector and
nrow() of the data frame before assigning. When in doubt,
use mutate() inside a dplyr pipeline — it
always operates on the rows of the piped object.
Error non-numeric argument to binary operator ●●●○○ common
What it means. You tried to do arithmetic
(+, -, *, /,
^) on something that is not a number — most often a
character string or a factor.
wq <- data.frame(
site = c("A","B","C"),
temp = c("12.1", "14.5", "11.8") # stored as character, not numeric!
)
mean(wq$temp) # returns NA with a warning
wq$temp_scaled <- wq$temp * 0.1 # Error: non-numeric argument to binary operator
Diagnosis and fix.
class(wq$temp) # "character" — that's the problem
str(wq) # shows all column types at once
# Fix 1: convert the column
wq$temp <- as.numeric(wq$temp)
# Fix 2: fix at import (readr does this automatically for properly formatted CSVs)
library(readr)
wq <- read_csv("wq.csv") # detects column types automatically
# Fix 3: check for stray characters that prevent coercion
# If as.numeric() produces NAs, the column contains non-numeric strings
wq$temp[is.na(as.numeric(wq$temp))] # shows the problematic values
Error undefined columns selected ●●○○○ moderate
What it means. You used [ with a column
name or number that does not exist in the data frame.
df <- data.frame(site = "A", temp_C = 12, do_mgl = 8)
df[, "temperature"] # Error: undefined columns selected — column is "temp_C"
df[, 5] # Error: there is no 5th column
# With $, the behavior is different — silently returns NULL
df$temperature # NULL (no error!)
Fix. Use names(df) or
colnames(df) to check exact column names. The
$ operator’s silent NULL return for missing
columns is more dangerous than this error, because it
propagates invisibly through your pipeline.
# Defensive check
col <- "temperature"
if (!col %in% names(df)) stop("Column '", col, "' not found in data frame")
Error arguments imply differing number of rows: X, Y ●●○○○ moderate
What it means. You tried to create a
data.frame() from vectors of different lengths, and none of
them is a multiple of the others (so recycling cannot resolve it).
data.frame(
site = c("A", "B", "C"),
temp = c(12.1, 14.5), # only 2 values — Error
do_mg = c(7.2, 8.1, 6.9)
)
Fix. All vectors passed to data.frame()
must have the same length. Check length() on each vector
before constructing the frame. The error message tells you the
conflicting lengths.
Error $ operator is invalid for atomic vectors ●●○○○ moderate
What it means. You used $ on a plain
vector (numeric, character, or logical), not a list or data frame.
$ only works on named list-like objects.
# Cause 1: trying to use $ on a vector returned by a function
result <- c(mean = 12.4, sd = 2.1) # named numeric vector, not a list
result$mean # Error: $ operator is invalid for atomic vectors
# Fix: use [[ ]] or named indexing
result[["mean"]] # 12.4
result["mean"] # returns named vector
# Cause 2: function returns a vector, not a data frame
summary_values <- tapply(wq$temp, wq$site, mean)
summary_values$A # Error: tapply returns an array, not a data frame
summary_values["A"] # Correct
Error package ‘X’ was built under R version Y.Z Warning ●○○○○ occasional
What it means. The package was compiled for a newer (or older) version of R than you are running. This is a warning, not an error — the package usually still loads and works. It becomes a problem if the package uses features that do not exist in your R version.
Fix. Update R and/or the package. Run
update.packages() to update all installed packages. If you
need a specific older version for reproducibility, use
renv.
Error there is no package called ‘X’ ●●○○○ moderate
What it means. You called library(X)
but package X is not installed on this machine.
library(vegan)
# Error in library(vegan) : there is no package called 'vegan'
Fix.
install.packages("vegan") # installs from CRAN
library(vegan) # then load it
# For GitHub-only packages:
# install.packages("remotes")
# remotes::install_github("username/packagename")
In RMarkdown, include install lines commented out with a note:
# Run this line once to install, then comment it out:
# install.packages("vegan")
library(vegan)
Warnings are more insidious than errors because R continues running and returns a result — but the result may be silently wrong. These are the most important warnings to understand.
Warning NAs introduced by coercion ●●●●○ very common
What it means. as.numeric() (or
as.integer()) could not convert one or more values — they
became NA. This almost always means your column contains
non-numeric strings mixed in with numbers.
x <- c("12.1", "14.5", "N/A", "11.8", "--", "10.2")
as.numeric(x)
# [1] 12.1 14.5 NA 11.8 NA 10.2
# Warning: NAs introduced by coercion
This is how you clean sensor data with flags. Find and inspect the non-numeric values, decide whether to recode them, then convert.
# Find which values will not convert
x[is.na(suppressWarnings(as.numeric(x)))]
# [1] "N/A" "--"
# Recode to NA explicitly, then convert
x_clean <- ifelse(x %in% c("N/A", "--", ""), NA_character_, x)
as.numeric(x_clean) # no warning; NAs are intentional
Warning In log(x) : NaNs produced ●●●○○ common
What it means. You took the log (or sqrt) of a
negative number or zero. log(0) returns -Inf;
log(-1) returns NaN (Not a Number). Both
propagate silently through arithmetic.
conc <- c(0.5, 1.2, 0.0, -0.1, 2.3) # zero and negative from a sensor glitch
log(conc)
# [1] -0.6931 0.1823 -Inf NaN 0.8329
# Warning message: In log(conc) : NaNs produced
Fix. Investigate why negatives or zeros exist. Sensor values of 0 or below the detection limit need special handling (see the Non-Detects module). Filter or recode before transforming.
conc_clean <- conc[conc > 0] # remove zeros and negatives
log(conc_clean) # no warning
Warning longer object length is not a multiple of shorter object length ●●●○○ common
What it means. You performed a vectorized operation on two vectors of different lengths, and R recycled the shorter vector to match the longer one — but the lengths are not evenly divisible. The recycling probably produced wrong results.
a <- c(1, 2, 3, 4, 5, 6, 7)
b <- c(10, 20, 30)
a + b # Warning: longer object length is not a multiple of shorter
# [1] 11 22 33 14 25 36 17
# R recycled b as 10, 20, 30, 10, 20, 30, 10 — almost certainly not intended
This warning signals a logic error, not a data glitch. Check that your vectors are the lengths you intend.
Warning Removed X rows containing missing values (geom_point) ●●●○○ common
What it means. ggplot2 encountered NA
values in the columns mapped to x or y aesthetics. It silently dropped
those rows from the plot. This is often expected behavior — but always
ask whether the NAs are intentional or indicate a parsing error
upstream.
# Diagnosis: how many NAs are there and where?
sum(is.na(df$do_mgl))
which(is.na(df$do_mgl)) # which rows
# If the NAs came from a failed date parse, for example,
# fix the parse rather than just suppressing the ggplot warning
Warning 1 failed to parse. / X failed to parse. ●●●●○ very common
What it means. lubridate’s ymd(),
mdy(), etc. could not parse one or more values — they
became NA. This is the single most important warning in
time-series analysis because it means you have lost data points
silently.
dates <- c("2024-01-15", "2024-02-20", "20240330", "bad date")
ymd(dates)
# [1] "2024-01-15" "2024-02-20" "2024-03-30" NA
# Warning message: 1 failed to parse.
Fix. Find and inspect the failed values before proceeding.
parsed <- ymd(dates, quiet = TRUE)
dates[is.na(parsed)] # "bad date"
# Fix, recode, or document the bad value, then re-parse
RMarkdown introduces a second layer of errors on top of R errors. A
document that runs fine in the console may fail during
knit.
When you knit, RMarkdown runs every chunk from top to bottom in a fresh R session. Objects created interactively in your console do not exist during knit.
Error in mean(pm_data$temp) : object 'pm_data' not found
This means pm_data was created in a chunk that runs
after the chunk that uses it, or it was created interactively
but never put in a chunk. Fix: always create objects before
using them. Read data in an early chunk.
eval=FALSE trap
``` r
pm_data <- read_csv("data/pm25.csv") # this chunk does NOT run during knit
ggplot(pm_data, ...) # Error: object 'pm_data' not found
## Error in `ggplot()`:
## ! could not find function "ggplot"
If eval=FALSE was added to a data-loading chunk to
“skip” a slow step, the object is never created during knit. Remove
eval=FALSE or use cache=TRUE instead.
💡 Use error = TRUE in the setup chunk while
developing. Setting
knitr::opts_chunk$set(error = TRUE) allows the document to
knit even when chunks produce errors — the error message is printed
inline rather than halting the knit. This lets you see all errors in one
pass. Remove it (or set error = FALSE) once your document
is working correctly.
# In your setup chunk during development:
knitr::opts_chunk$set(
echo = TRUE,
error = TRUE, # show errors inline instead of halting
warning = TRUE, # show warnings so you can find them
message = FALSE
)
During knit, the working directory is the directory
containing the .Rmd file, not your RStudio working
directory. Any file paths that work in the console may fail during
knit.
# Console: works (working directory is project root)
read.csv("data/wq.csv")
# Knit: fails if Rmd is in project root (same, ok) but fails if Rmd is in a subfolder
# Fix: use here::here() which always resolves from the project root
library(here)
read.csv(here("data", "wq.csv")) # works regardless of Rmd location
For errors that do not resolve quickly with the five-step strategy, R provides several tools for deeper investigation.
traceback() — see the call stack after an errorAfter an error occurs interactively, run traceback()
immediately (before running any other command). It prints the full chain
of function calls that led to the error, from the outermost call down to
the line that failed.
# After getting an error, immediately run:
traceback()
# Output (read from bottom to top):
# 3: stop("argument must be numeric")
# 2: mean.default(my_data$tempC)
# 1: mean(my_data$tempC)
The bottom frame is where R stopped. Work upward to understand what called what.
options(warn = 2) — convert warnings to errorsWarnings are easy to miss. Setting options(warn = 2)
makes every warning behave as an error, with a full traceback. This is
the fastest way to find where a warning originates.
options(warn = 2) # warnings become errors with traceback
log(c(1, -1, 2)) # now produces a full traceback to the negative value
options(warn = 0) # reset to normal behavior afterward
browser() — pause execution inside a functionInsert browser() anywhere in your code (including inside
a custom function) and execution will pause at that point, dropping you
into an interactive browser where you can inspect all local
variables.
my_fn <- function(x) {
x_clean <- x[x > 0]
browser() # execution pauses here; type variable names to inspect
log(x_clean)
}
my_fn(c(1, -1, 2))
# Called from: my_fn(c(1, -1, 2))
# Browse[1]> x_clean # inspect the variable
# [1] 1 2
# Browse[1]> Q # quit the browser
print() / cat() — the simplest
debuggerFor loops and apply calls, inserting print() or
cat() statements is often faster than any formal debugging
tool.
for (i in seq_along(site_list)) {
cat("Processing site:", site_list[i], "\n") # confirms which iteration failed
result[[i]] <- analyse_site(site_data[[i]])
}
?mean # help page for the mean() function
??rarefaction # search all help pages for the word "rarefaction"
help(package = "vegan") # all functions in a package
vignette("diversity-vegan") # package vignette with worked examples
When you need to ask for help — from a classmate, instructor, or Stack Overflow — a reprex (minimal reproducible example) is the most important skill. A good reprex:
# Bad: "my code doesn't work" + 200 lines of code + a file path no one can access
# Good reprex:
library(dplyr)
df <- data.frame(site = c("A","B"), do_mgl = c(7.2, 8.1))
df |> summarise(mean_do = mean(DO_mgl))
# Error: object 'DO_mgl' not found
# I expect this to work but 'DO_mgl' is not found even though I can see it in df
The reprex package (part of the tidyverse) can format
your reprex automatically for pasting into GitHub or Stack Overflow:
# install.packages("reprex")
library(reprex)
reprex({
df <- data.frame(site = c("A","B"), do_mgl = c(7.2, 8.1))
df |> dplyr::summarise(mean_do = mean(DO_mgl))
})
💡 The act of writing a reprex often solves the problem. Stripping your code down to the minimal example that reproduces the error forces you to understand exactly what is failing and why. It is very common to solve your own bug while writing the reprex.
Error stops execution;
Warning continues with possibly-wrong results;
Message is informational only.
library()),
cannot open file (wrong working directory or path).
here::here() for file paths;
error = TRUE during development.
traceback() after an error;
options(warn=2) to trace warnings; browser()
to inspect inside functions.
Error: object 'wq_clean' not found during knit, but the
same code works fine when you run chunks interactively. What is the most
likely cause, and how would you fix it?
mutate() call runs without error and produces a new
column, but when you check the data, roughly 15% of values in the new
column are NA where you expected numbers. No error or
warning appeared. What knitr setting may have hidden a
warning, and how would you find the source of the NAs?
Error in data.frame(...) : arguments imply differing number of rows: 48, 50.
Walk them through the five-step debugging strategy: what specific checks
would you run at step 3?
Error in group_by(site) : could not find function "group_by".
options(warn = 0),
options(warn = 1), and options(warn = 2)? When
would you use each?