Debugging R Code
Bugs happen. Even experienced R programmers encounter errors that aren’t obvious from the error message alone. R provides several built-in tools to help you trace through code, inspect variables, and figure out what went wrong. This guide covers the essential debugging functions you’ll use daily.
Understanding Error Messages
When R throws an error, it shows a message but not always the full picture. The first step is getting the call stack.
# A function with a bug
calculate_stats <- function(data) {
mean_value <- mean(data$column)
sd_value <- sd(data$column)
return(list(mean = mean_value, sd = sd_value))
}
# This will fail
calculate_stats(mtcars)
# Error in mean.default(data$column) :
# 'x' must be numeric
The error tells you something is wrong with the column, but not how you got there.
Using traceback()
The traceback() function prints the call stack that led to the error. Call it immediately after an error occurs:
calculate_stats(mtcars)
# Error in mean.default(data$column) : 'x' must be numeric
traceback()
# 4: mean.default(data$column) at #2 : calculate_stats.R#2
# 3: sd.default(data$column) at #3 : calculate_stats.R#3
# 2: calculate_stats(mtcars)
# 1: eval(expr, envir, enclos)
The call stack shows the path: your function called mean() and sd(), both failed. Now you know exactly where to look.
Using browser()
The browser() function pauses execution and drops you into an interactive debugging mode. This is useful when you need to inspect the state of your code at a specific point:
process_data <- function(df) {
browser() # Execution stops here
result <- df$x + df$y
return(result)
}
process_data(data.frame(x = 1:5, y = 6:10))
# Called from: process_data(data.frame(x = 1:5, y = 6:10))
# Browse[1]>
Once inside browser mode, you can:
- Type variable names to print their values
- Use
nto execute the next line - Use
cto continue to the next breakpoint - Use
Qto exit the browser
This gives you X-ray vision into what’s actually happening inside your function.
Using debug() and undebug()
The debug() function marks a function for debugging mode. Every time that function is called, R enters the browser automatically:
debug(calculate_stats)
calculate_stats(mtcars)
# Entering debugger for calculate_stats at r-test.R#1:2
# Browse[1]>
This is powerful for functions you suspect have issues. You can step through the entire function, watching variables change with each line.
When you’re done debugging, remove the flag with undebug():
undebug(calculate_stats)
You can also check if a function is currently being debugged:
isdebugged(calculate_stats)
# [1] TRUE
Using options(error=recover)
Setting options(error=recover) changes R’s behavior when an error occurs. Instead of stopping, R drops you into a browser-like interface with access to all frames in the call stack:
options(error = recover)
calculate_stats(mtcars)
# Error in mean.default(data$column) : 'x' must be numeric
# Enter a frame number, or 0 to exit :
# Selection: 1
# Browse[1 of 4]>
You can examine variables in any frame. This is particularly useful when errors occur deep in nested function calls. Type 0 to exit the debugger.
To return to normal error behavior:
options(error = NULL)
Using warning() and message()
Not all problems are errors. Sometimes you want to inform users about something without stopping execution. R has two functions for this: warning() and message().
calculate_mean <- function(x) {
if (!is.numeric(x)) {
warning("Converting x to numeric")
x <- as.numeric(x)
}
mean(x)
}
calculate_mean(c(1, 2, 3))
# [1] 2
calculate_mean(c("1", "2", "3"))
# Warning: Converting x to numeric
# [1] 2
The difference: warnings can be suppressed with suppressWarnings(), while messages can be suppressed with suppressMessages(). Use warnings for unexpected but recoverable situations, messages for informational output.
Finding the Source of Errors
When error messages aren’t clear, work backwards:
- Run
traceback()immediately after an error - Add
browser()before where you think the problem is - Use
debug()to step through the entire function - Check the data types of inputs—R is strict about this
- Look for typos in variable names—R won’t catch these automatically
The key is reducing the search space. Divide your code in half, add a browser statement in the middle, and determine which half contains the bug. Repeat until you isolate the exact line.