R Error Handling

Errors, warnings, and messages are R's three ways of signaling problems. Without error handling, a single failure crashes your entire script. With proper error handling, you catch failures gracefully, log what went wrong, and let the rest of your code continue running.

R's Three Signal Types

Type       Severity     Behavior              Function
──────────────────────────────────────────────────────────────────
Error      Fatal        Stops execution       stop("message")
Warning    Non-fatal    Continues, alerts     warning("message")
Message    Informational Just prints          message("message")

tryCatch() — Catch and Handle Errors

result <- tryCatch({
  log(-1)        # produces warning + NaN
  "success"
}, warning = function(w) {
  cat("Warning caught:", conditionMessage(w), "\n")
  "handled warning"
}, error = function(e) {
  cat("Error caught:", conditionMessage(e), "\n")
  NA
}, finally = {
  cat("This always runs\n")
})

print(result)
tryCatch flow:
  Run expression
      │
      ├── Error? ──► error handler ──► return its value
      ├── Warning? ─► warning handler → return its value
      ├── Message? ─► message handler → return its value
      └── No signal → return expression value
      
  finally block always runs (cleanup code)

tryCatch() in a Loop

# Process a list — skip failures, continue with rest
files <- c("data1.csv", "missing.csv", "data2.csv")
results <- list()

for (f in files) {
  results[[f]] <- tryCatch({
    read.csv(f)
  }, error = function(e) {
    cat("Skipped", f, ":", conditionMessage(e), "\n")
    NULL
  })
}

# Count successful loads
success <- sum(!sapply(results, is.null))
cat("Loaded:", success, "of", length(files), "files\n")

withCallingHandlers() — Handle Without Stopping

# Unlike tryCatch, does not stop execution after handling
withCallingHandlers({
  log(-1)            # warning
  log(10)            # fine
  sqrt(-4)           # NaN warning
}, warning = function(w) {
  cat("Warning:", conditionMessage(w), "\n")
  invokeRestart("muffleWarning")   # suppress the warning printout
})

Generating Your Own Conditions

safe_sqrt <- function(x) {
  if (!is.numeric(x)) stop("Input must be numeric")
  if (x < 0) warning("Negative input: returning NaN")
  if (x == 0) message("Input is zero — result will be zero")
  sqrt(abs(x))
}

tryCatch(safe_sqrt("hello"), error=function(e) cat("ERROR:", conditionMessage(e)))
tryCatch(safe_sqrt(-9),      warning=function(w) cat("WARN:", conditionMessage(w)))

try() — Simpler Alternative

# try() is simpler than tryCatch for basic error catching
result <- try(read.csv("nonexistent.csv"), silent=TRUE)

if (inherits(result, "try-error")) {
  cat("File not found — using default data\n")
  result <- data.frame(x=1:5, y=1:5)
}

safely() from purrr — Functional Error Handling

library(purrr)

safe_log <- safely(log)

safe_log(10)       # $result: 2.30  $error: NULL
safe_log(-1)       # $result: NULL  $error: <simpleWarning...>
safe_log("abc")    # $result: NULL  $error: <simpleError...>

# Apply safely to a list
results <- map(list(10, -1, "abc", 100), safely(log))
ok_vals  <- keep(results, ~is.null(.x$error))
cat("Successful:", length(ok_vals), "\n")

Logging Errors to a File

log_error <- function(msg, file="error_log.txt") {
  timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
  write(paste(timestamp, "-", msg), file=file, append=TRUE)
}

tryCatch({
  read.csv("bad_file.csv")
}, error = function(e) {
  log_error(conditionMessage(e))
  cat("Error logged. Analysis skipped.\n")
})

tryCatch vs withCallingHandlers

tryCatch:                        withCallingHandlers:
────────────────────────────     ──────────────────────────────────────
Exits expression on catch        Resumes execution after handling
Returns handler value            Continues from point of signal
Good for "fail and recover"      Good for "log but continue"

Error handling transforms fragile scripts into robust pipelines. Any script that reads files, calls APIs, or processes user-supplied data should wrap risky operations in tryCatch. The few extra lines of error handling code prevent hours of debugging when something inevitably goes wrong in production.

Leave a Comment

Your email address will not be published. Required fields are marked *