12.5 Error Handling and Debugging

When writing R code, you will inevitably encounter errors, warnings, and unexpected behavior. In this section, you will learn how to handle these situations gracefully and debug your code effectively.

12.5.1 Errors, Warnings, and Messages

R has three types of signals that communicate different levels of severity:

  • Errors (produced by stop()): The code cannot continue. Execution halts immediately.
  • Warnings (produced by warning()): Something unexpected happened, but the code can still continue.
  • Messages (produced by message()): Informational output that does not indicate a problem.
log(-1)        # produces a warning
#> [1] NaN
log("abc")     # produces an error
#> Error in `log()`:
#> ! non-numeric argument to mathematical function
message("This is an informational message.")
#> This is an informational message.

12.5.2 Generating Custom Errors and Warnings

When writing your own functions, you should signal problems clearly to the user.

safe_divide <- function(x, y) {
  if (y == 0) {
    stop("Division by zero is not allowed.")
  }
  if (x == 0) {
    warning("Numerator is zero; result will be zero.")
  }
  x / y
}
safe_divide(10, 2)
#> [1] 5
safe_divide(0, 5)
#> [1] 0
safe_divide(10, 0)
#> Error in `safe_divide()`:
#> ! Division by zero is not allowed.

12.5.3 Handling Errors with try() and tryCatch()

12.5.3.1 try(): Keep Going After an Error

The try() function wraps an expression so that if it produces an error, execution continues instead of stopping.

result <- try(log("abc"), silent = TRUE)
class(result)
#> [1] "try-error"

When silent = TRUE, the error message is suppressed. You can check whether an error occurred using inherits(result, "try-error").

if (inherits(result, "try-error")) {
  cat("An error occurred, using a default value instead.\n")
  result <- NA
}
#> An error occurred, using a default value instead.
result
#> [1] NA

12.5.3.2 tryCatch(): Fine-Grained Error Handling

The tryCatch() function provides more control by letting you specify handlers for different types of conditions.

safe_log <- function(x) {
  tryCatch(
    log(x),
    warning = function(w) {
      message("Warning caught: ", conditionMessage(w))
      NA
    },
    error = function(e) {
      message("Error caught: ", conditionMessage(e))
      NA
    }
  )
}
safe_log(10)
#> [1] 2.302585
safe_log(-1)
#> Warning caught: NaNs produced
#> [1] NA
safe_log("abc")
#> Error caught: non-numeric argument to mathematical function
#> [1] NA

A practical use case is applying a function to a list of inputs where some might cause errors:

inputs <- list(4, -1, "hello", 100)
results <- sapply(inputs, safe_log)
#> Warning caught: NaNs produced
#> Error caught: non-numeric argument to mathematical function
results
#> [1] 1.386294       NA       NA 4.605170

12.5.4 Debugging Tools

When your code produces unexpected results but no error, you need to investigate. R provides several debugging tools.

12.5.4.1 traceback(): Find Where an Error Occurred

After an error, calling traceback() shows the sequence of function calls that led to the error.

f <- function(x) g(x)
g <- function(x) h(x)
h <- function(x) log(x)
f("abc")
traceback()

12.5.4.2 browser(): Pause and Inspect

Inserting browser() inside a function pauses execution at that point and opens an interactive debugging session where you can inspect variables.

my_function <- function(x) {
  y <- x^2
  browser()  # execution pauses here
  z <- y + 1
  z
}
my_function(5)

In the debugging session, you can type variable names to see their values, n to step to the next line, c to continue, or Q to quit.

12.5.4.3 debug() and undebug()

You can also set a function to be debugged without modifying its source code:

debug(my_function)
my_function(5)      # will enter browser mode automatically
undebug(my_function) # stop debugging

12.5.5 Exercises

  1. Write a function safe_sqrt(x) that uses tryCatch() to return NA when the input is negative (instead of producing a warning) and when the input is not numeric (instead of producing an error). Test it with safe_sqrt(9), safe_sqrt(-4), and safe_sqrt("hello").

  2. Given a list of file paths files <- c("data/file1.csv", "data/file2.csv", "data/nonexistent.csv"), write code using try() inside a for loop that attempts to read each file with read.csv() and prints a message for files that cannot be read. (You do not need actual files; just observe the error handling.)

  3. Write a function divide_all(x, y) that divides each element of vector x by the corresponding element of vector y. Use tryCatch() to handle cases where y contains zeros, returning Inf for those positions instead of an error.

  4. Use browser() inside a simple function to practice stepping through code. Write a function that computes the cumulative sum of a vector using a for loop, and use browser() to inspect the intermediate values.


Buy Me A Coffee