Error Handling in R
Error handling is a critical skill for writing robust R code. Whether you are reading files that might not exist, calling APIs that might fail, or working with user input, your code needs to handle unexpected situations gracefully. In this tutorial, you will learn the fundamentals of error handling in R.
Understanding Errors vs Warnings
R distinguishes between errors and warnings. An error stops execution immediately, while a warning signals a potential problem but allows execution to continue.
# This generates a warning but continues
log(-1)
# [1] NaN
# Warning message:
# In log(-1) : NaNs produced
# This would generate an error in a function
divide <- function(a, b) {
if (b == 0) stop("Cannot divide by zero!")
a / b
}
divide(10, 0)
# Error in divide(10, 0) : Cannot divide by zero!
The tryCatch Function
The primary mechanism for error handling in R is tryCatch(). It lets you catch errors and execute alternative code:
result <- tryCatch(
{
# Code to try
dangerous_function()
},
error = function(e) {
# Code to run if error occurs
message("An error occurred: ", e$message)
NA # Return NA on error
},
warning = function(w) {
# Code to run if warning occurs
message("Warning: ", w$message)
NA # Return NA on warning
},
finally = {
# Code that always runs (cleanup)
message("Execution complete")
}
)
Practical Example: Reading Files Safely
A common use case is reading files that might not exist:
read_csv_safe <- function(filepath) {
tryCatch(
{
read.csv(filepath)
message("Successfully read: ", filepath)
},
error = function(e) {
message("Failed to read ", filepath, ": ", e$message)
NULL # Return NULL instead of failing
}
)
}
# Usage
data <- read_csv_safe("data.csv")
data <- read_csv_safe("nonexistent.csv")
# Failed to read nonexistent.csv: cannot open the connection
Handling Multiple Error Types
You can handle different error conditions with separate condition handlers:
safe_divide <- function(a, b) {
tryCatch(
{
if (!is.numeric(a) || !is.numeric(b)) {
stop("Arguments must be numeric")
}
if (b == 0) {
stop("Division by zero")
}
a / b
},
error = function(e) {
message("Error: ", e$message)
NA
},
warning = function(w) {
message("Warning: ", w$message)
NA
}
)
}
safe_divide(10, 2)
# [1] 5
safe_divide(10, 0)
# Error: Division by zero
# [1] NA
safe_divide("a", 2)
# Error: Arguments must be numeric
# [1] NA
The try Function for Inline Handling
For simpler cases, try() wraps an expression and returns an object with status information:
# Using try() for simple error handling
result <- try(dangerous_operation(), silent = TRUE)
if (inherits(result, "try-error")) {
message("Operation failed: ", attr(result, "condition")$message)
} else {
# Use result
}
Using purrr for Functional Error Handling
The tidyverse approach uses purrr’s safely(), possibly(), and quietly() functions:
library(purrr)
# safely() returns a list with result and error
safe_log <- safely(log)
safe_log(10)
# $result
# [1] 2.302585
# $error
# NULL
safe_log(-10)
# $result
# NULL
# $error
# <simpleError in log(-10): NaNs produced>
# possibly() provides a default value for errors
positive_log <- possibly(log, otherwise = NA_real_)
positive_log(10)
# [1] 2.302585
positive_log(-10)
# [1] NA
Creating Custom Error Classes
For more complex applications, create custom error classes:
# Define custom error class
validate_positive <- function(x, name = "value") {
if (!is.numeric(x) || x <= 0) {
stop(simpleError(
paste(name, "must be a positive number, got:", x),
call = sys.call()
))
}
x
}
# Using custom validation
divide_safe <- function(a, b) {
tryCatch(
{
validate_positive(a, "a")
validate_positive(b, "b")
a / b
},
error = function(e) {
message("Validation failed: ", e$message)
NA
}
)
}
divide_safe(10, -5)
# Validation failed: b must be a positive number, got: -5
# [1] NA
Best Practices
- Catch specific errors - Handle specific error conditions rather than catching everything
- Provide meaningful error messages - Help users understand what went wrong
- Clean up with finally - Release resources (close connections, files) in the finally block
- Do not suppress errors silently - At minimum, log or report errors
- Test error handling - Use testthat or similar to verify your error paths work
Summary
Error handling in R uses tryCatch() as the primary mechanism, with try() for simpler cases and purrr’s safely() and possibly() for functional programming contexts. Always provide meaningful error messages, clean up resources, and test your error handling paths.
In the next tutorial, you will apply these concepts as you build more complex R programs that gracefully handle unexpected inputs and conditions.