Handling errors before warnings in tryCatch
This relates to the "is it possible to process the warning and then have the function continue while still catching errors?" question.
I had a similar issue where I wanted to bind my logging functions for warnings and errors to the try catch and always continue after warnings and also be able to perform multiple attempts at try catch, e.g. for accessing a flimsy network drive. This is what I ended up using. This or a more simplified version could help with what your after.
MyLibrary.Sys.Try <- function(
expr, # Expression that will be evaluated by the call to Try
errorMessage="", # Optional prepended string to add to error output
warningMessage="", # Optional prepended string to add to warning output
failureMessage="", # Optional prepended string to add to failing all attempts
finally=NULL, # Optional expression to be executed at the end of tryCatch
attempts=1, # Number of attempts to try evaluating the expression
suppressError=TRUE, # Should the call just log the error or raise it if all attempts fail
quiet=FALSE # Return expression normally or as invisible
) {
tryNumber <- 0
while(tryNumber<attempts) {
tryNumber <- tryNumber + 1
# If not suppressing the error and this is the last
# attempt then just evaluate the expression straight out
if(tryNumber == attempts && !suppressError){
# NOTE: I think some warnings might be lost here when
# running in non-interactive mode. But I think it should be okay
# because even nested dispatch seems to pick them up:
# MyLibrary.Sys.Try(MyLibrary.Sys.Try(function(),suppressError=F))
return(expr)
}
tryCatch({
# Set the warning handler to an empty function
# so it won't be raised by tryCatch but will
# be executed by withCallingHandlers
options(warning.expression=quote(function(){}))
withCallingHandlers({
if(quiet) {
return(invisible(expr))
} else {
return(expr)
}
},error=function(ex){
MyLibrary.Sys.Log.Error(errorMessage,ex)
}, warning=function(warn){
# Had issues with identical warning messages being
# issued here so to avoid that only log it the first
# time it happens in a given minute.
warnStamp <- paste(Sys.time(),warn,collapse="_",sep="")
if(!identical(warnStamp,getOption("MyLibrary.LAST.WARNING"))) {
if(!(interactive() && is.null(getOption("warning.expression")))){
MyLibrary.Sys.Log.Warning(warningMessage,warn)
}
options(MyLibrary.LAST.WARNING=warnStamp)
}
})
},error=function(ex){
# Suppressing the error since it's been logged
# by the handler above. Needs to be suppressed
# to not invoke the stop directly since the
# handler above passes it through.
},finally={
# Set the warning handler to the default value
# of NULL which will cause it to revert to it's
# normal behaviour. If a custom handler is normally
# attached it would make sense to store it above
# and then restore it here. But don't need that now
options(warning.expression=NULL)
if(!is.null(finally)){
if(quiet) {
return(invisible(finally))
} else {
return(finally)
}
}
})
}
msg <- paste(ifelse(nchar(failureMessage)>0," - ",""),"Failed to call expression after ",attempts," attempt(s)",sep="")
MyLibrary.Sys.Log.Error(failureMessage,msg)
}
Store errors and warnings with tryCatch() in a list
Here's a version that puts the tryCatch
within the loop, and saves the errors and warnings that were generated:
hospitals_url <- "https://services1.arcgis.com/Hp6G80Pky0om7QvQ/arcgis/rest/services/Hospitals_1/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json"
hospitals_num <- c(0, 2000, 4000, 6000)
hosp_e <- list()
hosp_w <- list()
hosget <- lapply(hospitals_num, function(num) {
tryCatch({
hospitals_url_loop <- paste(hospitals_url, "&resultOffset=", num)
hospitals_json <- fromJSON(hospitals_url_loop)
hospitals_df <- as.data.frame(hospitals_json$features$attributes)},
error = function(e){
hosp_e <<- c(hosp_e, list(e))
print(e)},
warning = function(w){
hosp_w <<- c(hosp_w, list(w))
print(w)}
) })
hospitals_df <- do.call(rbind, hosget)
I didn't adapt your finally
code to this rearrangement; I'll leave that to you. But at the end, hosp_e
will be a list holding all the errors, and hosp_w
will be a list holding all the warnings.
How to correctly log warnings and errors using `tryCatch` in R?
Based on Ronak's helpful comment and the following question How do I save warnings and errors as output from a function?, the code can be simplified as follows:
# Storage.
warns = list()
errs = list()
# Example function.
fun <- function(i) {
# Issue warning.
warning(paste("Warn.", i))
# Stop.
if(i == 3) { stop(paste("Err.", i)) }
}
# Evaluate `fun`.
for (i in 1:4) {
tryCatch(withCallingHandlers(
expr = fun(i),
# Handle the warnings.
warning = function(w) {
warns <<- c(warns, list(w))
invokeRestart("muffleWarning")
}),
# Handle the errors.
error = function(e) {
errs <<- c(errs, list(e))
}
)
}
The output then looks like:
warns
# [[1]]
# <simpleWarning in fun(i): Warn. 1>
#
# [[2]]
# <simpleWarning in fun(i): Warn. 2>
#
# [[3]]
# <simpleWarning in fun(i): Warn. 3>
#
# [[4]]
# <simpleWarning in fun(i): Warn. 4>
errs
# [[1]]
# <simpleError in fun(i): Err. 3>
More information and links are provided in the question linked above.
R tryCatch handling one kind of error
You could use try
to handle errors:
result <- try(log("a"))
if(class(result) == "try-error"){
error_type <- attr(result,"condition")
print(class(error_type))
print(error_type$message)
if(error_type$message == "non-numeric argument to mathematical function"){
print("Do stuff")
}else{
print("Do other stuff")
}
}
# [1] "simpleError" "error" "condition"
# [1] "non-numeric argument to mathematical function"
# [1] "Do stuff"
R tryCatch but retain the expression result in the case of a warning
Here's a base
solution with a custom function myCatch()
, which has a form similar to tryCatch()
(and identical to withCallingHandlers()
). Feel free to adapt it, especially in the areas designated by my # ADAPT...
comments.
By request, I have also Updated myCatch()
to accept a user-defined function custom_fun
. Based on the result
from expr
, custom_fun
will process any warning object thrown when evaluating expr
, and its output will be returned as diagnostics
(alongside results
).
myCatch <- function(# The expression to execute.
expr,
# Further arguments to tryCatch().
...,
# User-defined function to extract diagnostic info from
# warning object, based on output that resulted from expr.
custom_fun = function(result, w){return(w)}) {
######################
## Default Settings ##
######################
# Defaults to NULL results and empty list of diagnostics.
DEFAULT_RESULTS <- NULL
DEFAULT_DIAGNOSTICS <- NULL
# Defaults to standard R error message, rather than a ponderous traceback
# through the error handling stacks themselves; also returns the error object
# itself as the results.
DEFAULT_ERROR <- function(e){
message("Error in ", deparse(e$call), " : ", e$message)
return(e)
}
################
## Initialize ##
################
# Initialize output to default settings.
res <- DEFAULT_RESULTS
diag <- DEFAULT_DIAGNOSTICS
err <- DEFAULT_ERROR
# Adjust error handling if specified by user.
if("error" %in% names(list(...))) {
err <- list(...)$error
}
#######################
## Handle Expression ##
#######################
res <- tryCatch(
expr = {
withCallingHandlers(
expr = expr,
# If expression throws a warning, record diagnostics without halting,
# so as to store the result of the expression.
warning = function(w){
parent <- parent.env(environment())
parent$diag <- w
}
)
},
error = err,
...
)
############
## Output ##
############
# Package the results as desired.
return(list(result = res,
diagnostics = custom_fun(res, diag)))
}
Application
For your purposes, use myCatch()
like so
x <- myCatch(someLongRunningFunctionThatMightGenerateWarnings())
or more generally
x <- myCatch(expr = {
# ...
# Related code.
# ...
someLongRunningFunctionThatMightGenerateWarnings()
},
# ...
# Further arguments like 'finally' to tryCatch().
# ...
custom_fun = function(result, w){
# ...
# Extract warning info from 'w'.
# ...
})
and feel free to customize error
or finally
as you would with tryCatch()
. If you do customize warning
, your diagnostics
will still be preserved in the output, but you will lose your intended output for result
(which will instead become the return value you specified in warning
).
If we followed your specific example here, and used myCatch()
like so
output <- myCatch(
log(-5),
custom_fun = function(result, w){paste(as.character(result), "with warning", w$message)}
)
output
then R would display the warning message
Warning message:
In log(-5) : NaNs produced
and give us the following output
:
$result
[1] NaN
$diagnostics
[1] "NaN with warning NaNs produced"
Further Examples
When we apply myCatch()
to certain example expr
essions, using simply the default for custom_fun
, here are the results:
Normal
output_1 <- myCatch(expr = {log(2)},
finally = {message("This is just like using 'finally' for tryCatch().")})
output_1
will display the custom message
This is just like using 'finally' for tryCatch().
and give us the output:
$result
[1] 0.6931472
$diagnostics
NULL
Warning
output_2 <- myCatch(expr = {log(-1)})
output_2
will display the warning message
Warning message:
In log(-1) : NaNs produced
and give us the output:
$result
[1] NaN
$diagnostics
<simpleWarning in log(-1): NaNs produced>
Error (Default)
output_3 <- myCatch(expr = {log("-1")})
output_3
will gracefully handle the error and display its message
Error in log("-1") : non-numeric argument to mathematical function
and still give us the output (with an error object for results
):
$result
<simpleError in log("-1"): non-numeric argument to mathematical function>
$diagnostics
NULL
Error (Custom)
output_4 <- myCatch(expr = {log("-1")}, error = function(e){stop(e)})
output_4
will kill myCatch()
and immediately throw an error, with a ponderous traceback through the handling functions (here tryCatch()
) within myCatch()
:
Error in log("-1") : non-numeric argument to mathematical function
6. stop(e)
5. value[[3L]](cond)
4. tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]),
names[nh], parentenv, handlers[[nh]])
3. tryCatchList(expr, classes, parentenv, handlers)
2. tryCatch(expr = {
withCallingHandlers(expr = expr, warning = function(w) {
parent <- parent.env(environment())
parent$diag <- w ...
1. myCatch(expr = {
log("-1")
}, error = function(e) {
stop(e) ...
Since myCatch()
was interrupted, it return
s no value to be stored in output_
, which leaves us with
Error: object 'output_4' not found
Disregarding simple warnings/errors in tryCatch()
I think you're looking for the difference between tryCatch
, which catches a condition and continues evaluation from the environment where the tryCatch was defined, versus withCallingHandlers
, which allows you to 'handle' a condition and then continue on from the location where the condition occurred. Take a look at warning
(or the help page for warning, but that's less fun), especially the lines
withRestarts({
.Internal(.signalCondition(cond, message, call))
.Internal(.dfltWarn(message, call))
}, muffleWarning = function() NULL)
This says -- signal a condtion, but insert a 'restart' where the condition was signaled from. Then you'd
withCallingHandlers({
warning("curves ahead")
2
}, warning = function(w) {
## what are you going to do with the warning?
message("warning occurred: ", conditionMessage(w))
invokeRestart("muffleWarning")
})
Although withCallingHandlers
is often used with warnings and tryCatch
with errors, there is nothing to stop one from 'handling' an error or catching a warning if that is the appropriate action.
Related Topics
Loess Regression on Each Group with Dplyr::Group_By()
Display a Summary Line Per Facet Rather Than Overall
Dplyr String as Column Reference
Finding Overlapping Ranges Between Two Interval Data
Understanding Element Wise Clearing of R's Workspace
How to Order Bars Within All Facets
Partially Read Really Large CSV.Gz in R Using Vroom
Creating Accompanying Slides for Bookdown Project
Shapes and Linetypes in Ggplot
Show Element Values in Barplot
Reshape Data Long to Wide - Understanding Reshape Parameters
Why Does Rm Inside a Function Not Delete Objects
Find All Positions of All Matches of One Vector of Values in Second Vector
Fitting a Curve to Specific Data
How to Manually Change the Key Labels in a Legend in Ggplot2