Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I save warnings and errors as output from a function?

I'm using lapply to run a complex function on a large number of items, and I'd like to save the output from each item (if any) together with any warnings/errors that were produced so that I can tell which item produced which warning/error.

I found a way to catch warnings using withCallingHandlers (described here). However, I need to catch errors as well. I can do it by wrapping it in a tryCatch (as in the code below), but is there a better way to do it?

catchToList <- function(expr) {   val <- NULL   myWarnings <- NULL   wHandler <- function(w) {     myWarnings <<- c(myWarnings, w$message)     invokeRestart("muffleWarning")   }   myError <- NULL   eHandler <- function(e) {     myError <<- e$message     NULL   }   val <- tryCatch(withCallingHandlers(expr, warning = wHandler), error = eHandler)   list(value = val, warnings = myWarnings, error=myError) }  

Sample output of this function is:

> catchToList({warning("warning 1");warning("warning 2");1}) $value [1] 1  $warnings [1] "warning 1" "warning 2"  $error NULL  > catchToList({warning("my warning");stop("my error")}) $value NULL  $warnings [1] "my warning"  $error [1] "my error" 

There are several questions here on SO that discuss tryCatch and error handling, but none that I found that address this particular issue. See How can I check whether a function call results in a warning?, warnings() does not work within a function? How can one work around this?, and How to tell lapply to ignore an error and process the next thing in the list? for the most relevant ones.

like image 447
Aaron left Stack Overflow Avatar asked Feb 09 '11 17:02

Aaron left Stack Overflow


2 Answers

Maybe this is the same as your solution, but I wrote a factory to convert plain old functions into functions that capture their values, errors, and warnings, so I can

test <- function(i)     switch(i, "1"=stop("oops"), "2"={ warning("hmm"); i }, i) res <- lapply(1:3, factory(test)) 

with each element of the result containing the value, error, and / or warnings. This would work with user functions, system functions, or anonymous functions (factory(function(i) ...)). Here's the factory

factory <- function(fun)     function(...) {         warn <- err <- NULL         res <- withCallingHandlers(             tryCatch(fun(...), error=function(e) {                 err <<- conditionMessage(e)                 NULL             }), warning=function(w) {                 warn <<- append(warn, conditionMessage(w))                 invokeRestart("muffleWarning")             })         list(res, warn=warn, err=err)     } 

and some helpers for dealing with the result list

.has <- function(x, what)     !sapply(lapply(x, "[[", what), is.null) hasWarning <- function(x) .has(x, "warn") hasError <- function(x) .has(x, "err") isClean <- function(x) !(hasError(x) | hasWarning(x)) value <- function(x) sapply(x, "[[", 1) cleanv <- function(x) sapply(x[isClean(x)], "[[", 1) 
like image 155
Martin Morgan Avatar answered Oct 06 '22 23:10

Martin Morgan


Try the evaluate package.

library(evaluate) test <- function(i)     switch(i, "1"=stop("oops"), "2"={ warning("hmm"); i }, i)  t1 <- evaluate("test(1)") t2 <- evaluate("test(2)") t3 <- evaluate("test(3)") 

It currently lacks a nice way of evaluating expression though - this is mainly because it's targetted towards reproducing exactly what R output's given text input at the console.

replay(t1) replay(t2) replay(t3) 

It also captures messages, output to the console, and ensures that everything is correctly interleaved in the order in which it occurred.

like image 30
hadley Avatar answered Oct 06 '22 23:10

hadley