Errors in software are common and they are useful.
This is to help understanding how to make better use of errors in R.
First, it is important to understand there are different types of error "messages" in R, and the effects of each one is different.
Type of messages
Error
As the name implies, this message is thrown when there is an error. Most used for fatal errors. An error usually stops code execution.
myErrorFun <- function(x) {
log(x)
print("if there was an error in log(x), I'm not executed :(")
}
log("not a number!")
# Error in log("not a number!") :
# non-numeric argument to mathematical function
Warning
A warning message is another type of error message. It will not stop code execution, and the order it is displayed may surprise you.
fun <- function(x) {
if (x == 0) {
warning("Using zero!")
}
print("If x is not 0, I'm there!")
}
fun(0)
# [1] "If x is not 0, I'm there!"
# Warning message:
# In fun(0) : Using zero!
Message
This is an informative message. It will not change code execution.
It looks different then calling a print()
statement.
fun <- function(x) {
if (x != 0) {
message("Not using zero!")
}
print("I'm here!")
}
fun(1)
# Not using zero!
# [1] "I'm here!"
Throwing errors
If you have noticed from the examples above, you can throw error messages using warning()
and message()
. However, neither will stop code execution and a warning may not be displayed immediately.
To throw an error, we need to use the function stop()
.
throwError <- function(x, xx) {
if (xx == x^2) {
stop("Squares not allowed!")
}
else if (xx == x^3) {
warning("I'll let one cube")
}
print("I may not be executed...")
message("How about me?")
}
throwError(2, 4)
# Error in throwError(2, 4) : Squares not allowed!
throwError(2,8)
# [1] "I may not be executed..."
# How about me?
# Warning message:
# In throwError(2, 8) : I'll let one cube
There is also stopifnot()
. In this case, one or more statements may be used in the function call and an error is thrown if any expression evaluates to FALSE
.
noZeroDivision <- function(dividend, divisor) {
stopifnot(divisor != 0)
print("If you are using zero, I'm out...")
return(dividend / divisor)
}
noZeroDivision(4,0)
# Error in noZeroDivision(4, 0) : divisor != 0 is not TRUE
It is important to understand how errors change code execution. Now, let's have a look how we can handle errors in R.
try
The try()
function allows you to execute a code and continue with the execution even if there is an error. It is not exactly handling the error, but ignoring it, with the possibility of setting parameter that can display or hide the error messages.
fun <- function(x) {
print(paste("Calculating log of", x))
try(log(x))
print("I don't care about errors...")
}
fun("nine")
# [1] "Calculating log of nine"
# Error in log(x) : non-numeric argument to mathematical function
# [1] "I don't care about errors..."
tryCatch
tryCatch()
is a more complete function to handle errors. With it, you can control what your code should do in case of an error or of a warning.
As other programming languages, there is also a finally
block available in this function.
However, note that the code in the error handling block will be executed, but not the code in the block after the call that generated the error, and the code execution will continue after the tryCatch()
block.
fun <- function(x) {
print(paste("Calculating log of", x))
tryCatch({
result <- log(x)
print("Do I get a turn?")
}, error = function(e) {
print(paste("There was an ERROR:", e))
result <- "empty"
}, warning = function(w) {
print(paste("There was a WARNING:", w))
result <- "empty"
}, finally = {
print("I don't care to what happened...")
})
print("Are we there yet?")
return(result)
}
fun(9)
# [1] "Calculating log of 9"
# [1] "Do I get a turn?"
# [1] "I don't care to what happened..."
# [1] "Are we there yet?"
# [1] 2.197225
fun("nine")
# [1] "Calculating log of nine"
# [1] "There was an ERROR: Error in log(x): non-numeric argument to mathematical function\n"
# [1] "I don't care to what happened..."
# [1] "Are we there yet?"
# Error in fun("nine") (from #16) : object 'result' not found
fun(-9)
# [1] "Calculating log of -9"
# [1] "There was a WARNING: simpleWarning in log(x): NaNs produced\n"
# [1] "I don't care to what happened..."
# [1] "Are we there yet?"
# Error in fun("nine") : object 'result' not found
Noticed that even in the error handling blocks there was no value assigned to the variable result
?
The scope of variables inside the handling blocks is different. Changing
variables values in here may not change the variable after the execution
(strange, right?). Again, see for yourself…
fun <- function(x) {
print(paste("Calculating log of", x))
result <- ""
tryCatch({
result <- log(x)
print("Do I get a turn?")
}, error = function(e) {
print(paste("There was an ERROR:", e))
result <- "empty after error"
}, warning = function(w) {
print(paste("There was a WARNING:", w))
result <- "empty after warning"
}, finally = {
print("I don't care to what happened...")
})
print("Are we there yet?")
return(result)
}
fun(-9)
## [1] "Calculating log of -9"
## [1] "There was a WARNING: simpleWarning in log(x): NaNs produced\n"
## [1] "I don't care to what happened..."
## [1] "Are we there yet?"
## [1] ""
> fun("nine")
## [1] "Calculating log of nine"
## [1] "There was an ERROR: Error in log(x): non-numeric argument to mathematical function\n"
## [1] "I don't care to what happened..."
## [1] "Are we there yet?"
## [1] ""
In order to get a return value from those functions you can use global variables (not ideal).
fun <- function(x) {
print(paste("Calculating log of", x))
tryCatch({
result <- log(x)
print("Do I get a turn?")
}, error = function(e) {
print(paste("There was an ERROR:", e))
result <<- "empty"
}, warning = function(w) {
print(paste("There was a WARNING:", w))
result <<- "empty"
}, finally = {
print("I don't care to what happened...")
})
print("Are we there yet?")
return(result)
}
fun(-9)
# [1] "Calculating log of -9"
# [1] "There was a WARNING: simpleWarning in log(x): NaNs produced\n"
# [1] "I don't care to what happened..."
# [1] "Are we there yet?"
# [1] "empty"
Or, get a return value from the tryCatch()
call. The function returns the value of the last executed command:
fun <- function(x) {
print(paste("Calculating log of", x))
result <- tryCatch({
print("Do I get a turn?")
log(x)
}, error = function(e) {
print(paste("There was an ERROR:", e))
"empty"
}, warning = function(w) {
print(paste("There was a WARNING:", w))
"empty"
}, finally = {
print("I don't care to what happened...")
})
print("Are we there yet?")
print(result)
}
fun(9)
## [1] "Calculating log of 9"
## [1] "Do I get a turn?"
## [1] "I don't care to what happened..."
## [1] "Are we there yet?"
## [1] 2.197225
fun(-9)
## [1] "Calculating log of -9"
## [1] "Do I get a turn?"
## [1] "There was a WARNING: simpleWarning in log(x): NaNs produced\n"
## [1] "I don't care to what happened..."
## [1] "Are we there yet?"
## [1] "empty"
fun("nine")
## [1] "Calculating log of nine"
## [1] "Do I get a turn?"
## [1] "There was an ERROR: Error in log(x): non-numeric argument to mathematical function\n"
## [1] "I don't care to what happened..."
## [1] "Are we there yet?"
## [1] "empty"
The finally block should be used for cleanup after the code execution. The last
line executed in this block is not returned. However, if you call
return(something)
in there, this is the value that will always be returned
by the tryCatch
, in any condition.
withCallingHandlers
This is another option to handling errors. It is very similar to the tryCatch
with some differences. One is that it is possible to resume the code execution from the point the a warning was generated with invokeRestart()
.
noZeroDivision <- function(dividend, divisor) {
warning("Division in progress!")
print("My turn?")
stopifnot(divisor != 0)
print("If you are using zero, I'm out...")
return(dividend / divisor)
}
noZeroDivision(4,2)
## [1] "My turn?"
## [1] "If you are using zero, I'm out..."
## [1] 2
## Warning message:
## In noZeroDivision(4, 2) : Division in progress!
withCallingHandlers(
noZeroDivision(4,0),
print("Hello"),
error = function(e) {
print("I've catched an error!")
}, warning = function(w) {
print("Warning detected!")
invokeRestart("muffleWarning")
})
## [1] "Warning detected!"
## [1] "My turn?"
## [1] "I've catched an error!"
## Error in noZeroDivision(4, 0) : divisor != 0 is not TRUE
withCallingHandlers(
noZeroDivision(4,2),
error = function(e) {
print("I've catched an error!")
}, warning = function(w) {
print("Warning detected!")
invokeRestart("muffleWarning")
})
## [1] "Warning detected!"
## [1] "My turn?"
## [1] "If you are using zero, I'm out..."
## [1] 2
Using withCallingHandlers
with tryCatch
increases your range of options to handling errors (and stack trace). However, it may be easier to use…
tryCatchLog
This package is an implementation of combining tryCatch
with withCallingHandlers
. It is not in the base install of R and must be installed and loaded before use.
So, let's remember how the code behaves without handling:
library(tryCatchLog)
fun <- function(x) {
print("before")
print(log(x))
print("after")
}
fun(9)
## [1] "before"
## [1] 2.197225
## [1] "after"
fun(-9)
## [1] "before"
## [1] NaN
## [1] "after"
## Warning message:
## In log(x) : NaNs produced
fun("nine")
## [1] "before"
## Error in log(x) : non-numeric argument to mathematical function
Now, let's compare how tryCatchLog
responds to warnings and errors. We can use it simply to log the error and continue with the code execution, or to also how to handle the error:
tryLog
tryLog(fun(9))
## [1] "before"
## [1] 2.197225
## [1] "after"
tryLog(fun(-9))
## [1] "before"
## WARN [2022-03-02 13:30:56] NaNs produced
## Compact call stack:
## 1 tryLog(fun(-9))
## 2 #3: print(log(x))
## 3 #3: .signalSimpleWarning("NaNs produced", base::quote(log(x)))
## Full call stack:
## 1 tryLog(fun(-9))
## 2 tryCatchLog(expr = expr, execution.context.msg = execution.context.msg, wri
## 3 tryCatch(withCallingHandlers(expr, condition = cond.handler), ..., finally
## 4 tryCatchList(expr, classes, parentenv, handlers)
## 5 tryCatchOne(expr, names, parentenv, handlers[[1]])
## 6 doTryCatch(return(expr), name, parentenv, handler)
## 7 withCallingHandlers(expr, condition = cond.handler)
## 8 fun(-9)
## 9 #3: print(log(x))
## 10 #3: .signalSimpleWarning("NaNs produced", base::quote(log(x)))
## 11 withRestarts({
## .Internal(.signalCondition(simpleWarning(msg, call),
## 12 withOneRestart(expr, restarts[[1]])
## 13 doWithOneRestart(return(expr), restart)
## [1] NaN
## [1] "after"
## Warning message:
## In log(x) : NaNs produced
tryLog(fun("nine"))
## [1] "before"
## ERROR [2022-03-02 13:38:59] non-numeric argument to mathematical function
## Compact call stack:
## 1 tryLog(fun("nine"))
## 2 #3: print(log(x))
## 3 #3: .handleSimpleError(function (c)
## Full call stack:
## 1 tryLog(fun("nine"))
## 2 tryCatchLog(expr = expr, execution.context.msg = execution.context.msg, wri
## 3 tryCatch(withCallingHandlers(expr, condition = cond.handler), ..., finally
## 4 tryCatchList(expr, classes, parentenv, handlers)
## 5 tryCatchOne(expr, names, parentenv, handlers[[1]])
## 6 doTryCatch(return(expr), name, parentenv, handler)
## 7 withCallingHandlers(expr, condition = cond.handler)
## 8 fun("nine")
## 9 #3: print(log(x))
## 10 #3: .handleSimpleError(function (c)
## {
## if (inherits(c, "condition
There is more information to the error messages. Check the package manual to see more options about all the available parameters.
tryCatchLog
tryCatchLog(fun(x),
error = function(e) {
print("I can handle errors!")
},
warning = function(w) {
print("I can handle warnings!")
},
finally = print("Always number 1"))
x <- 9
## [1] "before"
## [1] 2.197225
## [1] "after"
## [1] "Always number 1"
x <- -9
## [1] "before"
## WARN [2022-03-02 13:50:28] NaNs produced
## Compact call stack:
## 1 tryCatchLog(fun(-9), error = function(e) {
## 2 #3: print(log(x))
## 3 #3: .signalSimpleWarning("NaNs produced", base::quote(log(x)))
## Full call stack:
## 1 tryCatchLog(fun(-9), error = function(e) {
## print("I can handle error
## 2 tryCatch(withCallingHandlers(expr, condition = cond.handler), ..., finally
## 3 tryCatchList(expr, classes, parentenv, handlers)
## 4 tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), names
## 5 doTryCatch(return(expr), name, parentenv, handler)
## 6 tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
## 7 tryCatchOne(expr, names, parentenv, handlers[[1]])
## 8 doTryCatch(return(expr), name, parentenv, handler)
## 9 withCallingHandlers(expr, condition = cond.handler)
## 10 fun(-9)
## 11 #3: print(log(x))
## 12 #3: .signalSimpleWarning("NaNs produced", base::quote(log(x)))
## 13 withRestarts({
## .Internal(.signalCondition(simpleWarning(msg, call),
## 14 withOneRestart(expr, restarts[[1]])
## 15 doWithOneRestart(return(expr), restart)
## [1] "I can handle warnings!"
## [1] "Always number 1"
x <- "nine"
## [1] "before"
## ERROR [2022-03-02 13:51:58] non-numeric argument to mathematical function
## Compact call stack:
## 1 tryCatchLog(fun("nine"), error = function(e) {
## 2 #3: print(log(x))
## 3 #3: .handleSimpleError(function (c)
## Full call stack:
## 1 tryCatchLog(fun("nine"), error = function(e) {
## print("I can handle e
## 2 tryCatch(withCallingHandlers(expr, condition = cond.handler), ..., finally
## 3 tryCatchList(expr, classes, parentenv, handlers)
## 4 tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), names
## 5 doTryCatch(return(expr), name, parentenv, handler)
## 6 tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
## 7 tryCatchOne(expr, names, parentenv, handlers[[1]])
## 8 doTryCatch(return(expr), name, parentenv, handler)
## 9 withCallingHandlers(expr, condition = cond.handler)
## 10 fun("nine")
## 11 #3: print(log(x))
## 12 #3: .handleSimpleError(function (c)
## {
## if (inherits(c, "condition
## [1] "I can handle errors!"
## [1] "Always number 1"
More reading about it
Check this page with more information about this subject. There's more information about it and try it yourself!