Easier Error Handling in R with try()

[This article was first published on R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In a previous post, we looked at error handling in R with the tryCatch() function and how this could be used to write Java style try-catch-finally blocks. This time we’ll look at what can be done with the try() function and how we can easily process warning and error messages to take appropriate action when something goes wrong.

The try() function is really just a simplified interface to tryCatch(). To see how try() calls tryCatch() you can examine the guts of the try() function by typing try without parens at the R prompt but you may not like what you see. For those of us outside the R core development team, this is not a good place to start. The documentation seen with ?try is much better and has a useful example showing how try() can be used to generate simulated results, ignoring those that generated errors. But this documentation doesn’t address the kind of error handling one needs for “application logic” where different actions are taken depending on the kind of error issued.

For this post, we will more generally explore how try() can be used and how warning and error messages can be processed using geterrmessage() and grepl(). The important things to remember about these functions are:

  • try() only ignores warnings, not errors
  • options(warn = 2) turns warnings into errors
  • geterrmessage() returns the character string associated with the last error
  • grepl(pattern, string) returns TRUE if pattern is found within string, FALSE otherwise

With just these functions we have everything we need to write very simple constructs that can evaluate a function and handle both errors and warnings. The test script at the end of this post demonstrates how messages and errors can be generated within a function and then trapped and processed by a calling function, potentially generating new errors that could be passed upstream.

Ideally, a function like myFunc() would validate incoming parameters with e.g. is.numeric() before attempting to use them but with more complicated objects like data frames this is not always possible. Also note that pattern matching on error strings depends on the stability of the error string so be careful! Nevertheless, this approach allows for quick development and expansion of application logic that can gracefully handle errors.

Just copy and paste the script at the end, make it executable and try it out with the these shell commands:

$ chmod +x try.R
$ ./try.R 2
$ ./try.R 1
$ ./try.R 0
$ ./try.R a
$ ./try.R
$ ./try.R warning
$ ./try.R error-A
$ ./try.R error-B

And here is the script.

#!/usr/bin/env Rscript
# try.R -- experiments with try

# Get any arguments
arguments <- commandArgs(trailingOnly = TRUE)
a <- arguments[1]

# Define a function that can issue custom warnings and errors
# Use '.call = FALSE' to remove the function call from the message
myFunc <- function(a) {
  if (a == 'warning') {
    return_value <- 'warning return'
    warning("custom warning message", call. = FALSE)
  } else if (a == 'error-A') {
    return_value <- 'error return'
    stop("custom error message A", call. = FALSE)
  } else if (a == 'error-B') {
    return_value <- 'error return'
    stop("custom error message B", call. = FALSE)
  } else {
    return_value = log(a)
  }
  return(return_value)
}

# Turn warnings into errors so they can be trapped
options(warn = 2)

# Put one or more lines of R code inside {...} to be
# evaluated together.
result <- try({
    myFunc(a)
  }, silent = TRUE)

# Process any error messages
if (class(result) == "try-error") {
  # Ignore warnings while processing errors
  options(warn = -1)

  # If this script were a function, warning() and stop()
  # could be called to pass errors upstream
  msg <- geterrmessage()
  if (grepl("missing value", msg)) {
    result <- paste("USER ERROR: Did you supply an argument?")
  } else if (grepl("non-numeric argument",msg)) {
    # Oops, forgot to convert argument to numeric
    a <- as.numeric(a)
    if (is.na(a)) {
      result <- "Argument is non-numeric"
    } else {
      result <- myFunc(a)
    }
  } else if (grepl("custom warning message", msg)) {
    result <- "Took action for warning message"
  } else if (grepl("custom error message A", msg)) {
    result <- "Took action for error message A"
  } else if (grepl("custom error message B", msg)) {
    result <- "Could call stop() to pass an error upstream."
  }

  # Restore default warning reporting
  options(warn = 0)
}

print(result)

A previous version of this article originally appeared at WorkingwithData.

To leave a comment for the author, please follow the link and comment on their blog: R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)