Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
The R language definition section on Exception Handling describes a very few basics about exceptions in R but is of little use to anyone trying to write robust code that can recover gracefully in the face of errors. In fact, if you do a little searching you will find that quite a few people have read through the ?tryCatch
documentation but come away just as confused as when they started. In this post we’ll try to clarify a few things and describe how R’s error handling functions can be used to write code that functions similarly to Java’s try-catch-finally construct.
List of error handling functions
Without any simple documentation on the subject, the first thing we need is a list of the functions involved in error handling. With this list in hand we can then start up R and type ?function_of_interest
to read associated documentation or function_of_interest
without the ‘()’ to see how the function is implemented. Here is a minimal list of functions that anyone writing error handling code should read up on:
warning(…)
— generates warningsstop(…)
— generates errorssuppressWarnings(expr)
— evaluates expression and ignores any warningstryCatch(…)
— evaluates code and assigns exception handlers
Other functions exist that relate to error handling but the above are enough to get started. (The documentation for these functions will lead to all the other error-related functions for any RTFM enthusiasts.)
R does try-catch-finally differently
In case you hadn’t noticed, R does a lot of things differently from most other programming languages. Java and Python and C and all other languages covered in Wikipedia’s excellent page on Exception handling syntax use language statements to enable try-catch-finally. R
, needing to be different, uses a function.
But the tryCatch()
function actually looks a lot like other languages’ try-catch syntax if you format it properly:
result = tryCatch({ expr }, warning = function(w) { warning-handler-code }, error = function(e) { error-handler-code }, finally = { cleanup-code }
In tryCatch()
there are two ‘conditions’ that can be handled: ‘warnings’ and ‘errors’. The important thing to understand when writing each block of code is the state of execution and the scope. Excerpting relevant text from the ?tryCatch
documentation:
If a condition is signaled while evaluating ‘expr’ then [...] control is transferred to the ‘tryCatch’ call that established the handler [...] and the result returned by the handler is returned as the value of the ‘tryCatch’ call. [...] The ‘finally’ expression is then evaluated in the context in which ‘tryCatch’ was called.
What this means is that ‘expr’ is evaluated a line at a time until a ‘condition’ is encountered and then execution is transferred to the handler with the state in tact. Code can often explain more than words and the example at the end of this post is a standalone R script that explores various features that might be required in a robust error handling system:
- generating warnings and errors from within a function
- setting warning and error handlers with
tryCatch()
- providing alternative return values when a function generates a warning or error
- modifying the text of warning and error messages
- suppressing warning messages
Just copy and paste the script at the end, save it and make it executable. Then try it out with the following commands:
$ chmod +x tryCatch.R $ ./tryCatch.R 1 $ ./tryCatch.R 0 $ ./tryCatch.R a $ ./tryCatch.R $ ./tryCatch.R warning $ ./tryCatch.R error $ ./tryCatch.R suppress-warnings
Pay special attention to what happens with ‘suppress-warnings’.
The are a couple of take home messages that result from this experimentation:
tryCatch()
isn’t that hard to use (Once you know how!)warning()
andstop()
messages are accessible to the condition handlers.- Do appropriate type conversion before passing arguments to functions.
- Ideally, the
tryCatch()
expression should be a single function.
Here is the tryCatch.R
example script. Happy error handling!
#!/usr/bin/env Rscript # tryCatch.R -- experiments with tryCatch # Get any arguments arguments <- commandArgs(trailingOnly=TRUE) a <- arguments[1] # Define a division function that can issue warnings and errors myDivide <- function(d, a) { if (a == 'warning') { return_value <- 'myDivide warning result' warning("myDivide warning message") } else if (a == 'error') { return_value <- 'myDivide error result' stop("myDivide error message") } else { return_value = d / as.numeric(a) } return(return_value) } # Evalute the desired series of expressions inside of tryCatch result <- tryCatch({ b <- 2 c <- b^2 d <- c+2 if (a == 'suppress-warnings') { e <- suppressWarnings(myDivide(d,a)) } else { e <- myDivide(d,a) # 6/a } f <- e + 100 }, warning = function(war) { # warning handler picks up where error was generated print(paste("MY_WARNING: ",war)) b <- "changing 'b' inside the warning handler has no effect" e <- myDivide(d,0.1) # =60 f <- e + 100 return(f) }, error = function(err) { # error handler picks up where error was generated print(paste("MY_ERROR: ",err)) b <- "changing 'b' inside the error handler has no effect" e <- myDivide(d,0.01) # =600 f <- e + 100 return(f) }, finally = { print(paste("a =",a)) print(paste("b =",b)) print(paste("c =",c)) print(paste("d =",d)) # NOTE: Finally is evaluated in the context of of the inital # NOTE: tryCatch block and 'e' will not exist if a warning # NOTE: or error occurred. #print(paste("e =",e)) }) # END tryCatch print(paste("result =",result))
A previous version of this article originally appeared at WorkingwithData.
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.