R Error Handling — tryCatch and Debugging

R BasicsError HandlingFree Lesson

Advertisement

R Error Handling — tryCatch and Debugging

Learning Objectives

By the end of this tutorial, you will be able to:

  • Handle errors gracefully with tryCatch() and try()
  • Catch and manage warnings
  • Create custom error conditions
  • Use debugging tools like browser(), debug(), and traceback()
  • Write robust functions that handle edge cases

Error Handling Basics

try() — Simple Error Handling

# try() returns error message instead of stopping
result <- try(log("a"))
# Error in log("a") : non-numeric argument to mathematical function
class(result)   # [1] "try-error"

# Check for error
if (inherits(result, "try-error")) {
  cat("An error occurred\n")
}

# With silent = TRUE
result <- try(log("a"), silent = TRUE)

tryCatch() — Advanced Error Handling

# tryCatch with different handlers
result <- tryCatch(
  {
    log("a")
  },
  error = function(e) {
    cat("Error:", conditionMessage(e), "\n")
    NA
  },
  warning = function(w) {
    cat("Warning:", conditionMessage(w), "\n")
    NULL
  }
)
# Error: non-numeric argument to mathematical function

# Handle specific error types
result <- tryCatch(
  {
    x <- 1
    y <- "2"
    x + y
  },
  error = function(e) {
    if (grepl("non-numeric", conditionMessage(e))) {
      cat("Type error caught\n")
      NA
    } else {
      stop(e)  # Re-throw unknown errors
    }
  }
)

Warning Handling

# Catch warnings
result <- withCallingHandlers(
  {
    x <- log(-1)  # Produces warning
    x
  },
  warning = function(w) {
    cat("Warning caught:", conditionMessage(w), "\n")
    invokeRestart("muffleWarning")  # Suppress warning
  }
)

# Suppress warnings
result <- suppressWarnings(log(-1))

# withCallingHandlers vs tryCatch
# withCallingHandlers: continues after handler
# tryCatch: exits the expression after handler

Custom Error Conditions

# Create custom error
validate_age <- function(age) {
  if (!is.numeric(age)) {
    stop("Age must be numeric", call. = FALSE)
  }
  if (age < 0 || age > 150) {
    stop(paste("Age must be between 0 and 150, got:", age), call. = FALSE)
  }
  age
}

# Custom error class
invalid_input <- function(message) {
  structure(
    class = c("invalid_input", "error", "condition"),
    list(message = message)
  )
}

safe_divide <- function(a, b) {
  if (b == 0) {
    stop(invalid_input("Cannot divide by zero"))
  }
  a / b
}

# Handle custom errors
tryCatch(
  safe_divide(10, 0),
  invalid_input = function(e) {
    cat("Invalid input:", e$message, "\n")
    NA
  }
)

Debugging Tools

browser() — Interactive Debugging

my_function <- function(x) {
  browser()  # Execution stops here
  y <- x^2
  z <- y + 1
  return(z)
}

my_function(5)
# Called from: my_function(5)
# Browse[1]>
# Type n (next), c (continue), q (quit), s (step into)

debug() and undebug()

# Debug a function
debug(my_function)
my_function(5)  # Enters debug mode

# Step through
undebug(my_function)

# Debug once
debugonce(my_function)

traceback() and options(error = traceback)

# Show call stack after error
f <- function(x) g(x)
g <- function(x) h(x)
h <- function(x) x + "error"

tryCatch(f(1), error = function(e) traceback())

# Auto-show traceback on error
options(error = traceback)

Common Error Patterns

# Validate input
safe_mean <- function(x, na.rm = FALSE) {
  if (!is.numeric(x)) {
    stop("x must be numeric")
  }
  if (length(x) == 0) {
    stop("x must not be empty")
  }
  mean(x, na.rm = na.rm)
}

# Handle missing values
safe_log <- function(x) {
  if (any(x <= 0)) {
    warning("Non-positive values replaced with NA")
    x[x <= 0] <- NA
  }
  log(x)
}

# Retry pattern
retry <- function(expr, max_attempts = 3) {
  for (i in 1:max_attempts) {
    result <- tryCatch(
      expr,
      error = function(e) NULL
    )
    if (!is.null(result)) return(result)
    cat("Attempt", i, "failed, retrying...\n")
  }
  stop("All attempts failed")
}

Practice Exercises

Exercise 1: Safe Calculator

Write a calculator function that handles division by zero, non-numeric inputs, and returns meaningful error messages.

Solution

safe_calculator <- function(a, b, op) {
  if (!is.numeric(a) || !is.numeric(b)) {
    stop("Inputs must be numeric")
  }

  switch(op,
    add = a + b,
    subtract = a - b,
    multiply = a * b,
    divide = {
      if (b == 0) {
        stop("Cannot divide by zero")
      }
      a / b
    },
    stop(paste("Unknown operation:", op))
  )
}

safe_calculator(10, 5, "divide")     # [1] 2
safe_calculator(10, 0, "divide")     # Error: Cannot divide by zero
safe_calculator("a", 5, "add")       # Error: Inputs must be numeric

Key Takeaways

  • try() is simple — returns try-error on failure
  • tryCatch() is flexible — separate handlers for errors, warnings, messages
  • withCallingHandlers() handles warnings without stopping
  • Custom conditions let you create domain-specific errors
  • browser() pauses execution for interactive debugging
  • debug() steps through a function line by line
  • traceback() shows the call stack after an error
  • Always validate inputs at the start of functions

Next: Learn about R Packages — extending R's functionality.

Advertisement

Need Expert R Programming Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement