Basic error handling with R


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!