"hello" + "world"
Error in "hello" + "world": non-numeric argument to binary operator
This lecture, as the rest of the course, is adapted from the version Stephanie C. Hicks designed and maintained in 2021 and 2022. Check the recent changes to this file through the GitHub history.
Before class, you can prepare by reading the following materials:
Material for this lecture was borrowed and adopted from
At the end of this lesson you will:
stop
, stopifnot
, warning
, and message
.Errors most often occur when code is used in a way that it is not intended to be used.
The +
operator is essentially a function that takes two numbers as arguments and finds their sum.
Since neither "hello"
nor "world"
are numbers, the R interpreter produces an error.
Errors will stop the execution of your program, and they will (hopefully) print an error message to the R console.
In R there are two other constructs which are related to errors:
Warnings are meant to indicate that something seems to have gone wrong in your program that should be inspected.
Here’s a simple example of a warning being generated:
The as.numeric()
function attempts to convert each string in c("5", "6", "seven")
into a number, however it is impossible to convert "seven"
, so a warning is generated.
Execution of the code is not halted, and an NA
is produced for "seven"
instead of a number.
Messages simply print to the R console, though they are generated by an underlying mechanism that is similar to how errors and warning are generated.
Note that using message()
is better than print()
because users can suppress (censor) the messages if they want to with suppressMessages()
as shown below. There’s super easy equivalent for print()
as capture.output()
is more complicated to use.
Also, something I like to do with messages is take advantage of the fact that it uses paste0()
inside. That is, it is easy to combine our message with more information, such as the current time. Note the extra space before T
from This
.
2023-09-28 11:31:20.438794 This is a message.
See https://research.libd.org/spatialLIBD/reference/registration_wrapper.html for example.
There are a few essential functions for generating errors, warnings, and messages in R.
The stop()
function will generate an error.
If an error occurs inside of a function, then the name of that function will appear in the error message:
Error in name_of_function(): Something bad happened.
As we saw in the previous lesson, we can combine stop()
with an if()
if we want to provide our users a more informative error message than the one they would get otherwise. For example, see https://github.com/LieberInstitute/qsvaR/blob/feb9a9e4f8a499baba76271de37ca39a9969f400/R/k_qsvs.R#L30-L32.
The stopifnot()
function takes a series of logical expressions as arguments and if any of them are false an error is generated specifying which expression is false.
I recommend using stopifnot()
only with objects that are arguments to your function. Otherwise, users will get confusing error messages involving objects that they do not know how they were created.
stopifnot()
is mostly used at the beginning of functions to check our inputs. You might also want to use rlang::arg_match()
https://rlang.r-lib.org/reference/arg_match.html to provide error messages that include hints when we have a set of allowed options.
fn <- function(x = c("foo", "bar")) {
x <- rlang::arg_match(x)
## Known scenario 1
if (x == "foo") {
print("I know what to do here with 'x = foo'")
}
## Known scenario 2
if (x == "bar") {
print("I know what to do here with 'x = bar'")
}
}
fn("foo")
[1] "I know what to do here with 'x = foo'"
Error in `fn()`:
! `x` must be one of "foo" or "bar", not "zoo".
ℹ Did you mean "foo"?
The warning()
function creates a warning, and the function itself is very similar to the stop()
function. Remember that a warning does not stop the execution of a program (unlike an error.)
Just like errors, a warning generated inside of a function will include the name of the function in which it was generated:
Warning in make_NA("Sodium"): Generating an NA.
[1] NA
Messages are simpler than errors or warnings; they just print strings to the R console.
You can issue a message with the message()
function:
Stopping the execution of your program with stop()
should only happen in the event of a catastrophe - meaning only if it is impossible for your program to continue.
An example includes:
stopifnot()
so that the user can quickly realize something has gone wrong.You can think of a function as kind of contract between you and the user:
Of course it’s impossible for you to anticipate all of the potential uses of your program.
It’s appropriate to create a warning when this contract between you and the user is violated.
A perfect example of this situation is the result of
The user expects a vector of numbers to be returned as the result of as.numeric()
but "seven"
is coerced into being NA, which is not completely intuitive.
R has largely been developed according to the Unix Philosophy, which generally discourages printing text to the console unless something unexpected has occurred.
Languages that commonly run on Unix systems like C and C++ are rarely used interactively, meaning that they usually underpin computer infrastructure (computers “talking” to other computers).
Messages printed to the console are therefore not very useful since nobody will ever read them and it’s not straightforward for other programs to capture and interpret them.
In contrast, R code is frequently executed by human beings in the R console, which serves as an interactive environment between the computer and person at the keyboard.
If you think your program should produce a message, make sure that the output of the message is primarily meant for a human to read.
You should avoid signaling a condition or the result of your program to another program by creating a message.
Imagine writing a program that will take a long time to complete because of a complex calculation or because you’re handling a large amount of data. If an error occurs during this computation then you’re liable to lose all of the results that were calculated before the error, or your program may not finish a critical task that a program further down your pipeline is depending on. If you anticipate the possibility of errors occurring during the execution of your program, then you can design your program to handle them appropriately.
The tryCatch()
function is the workhorse of handling errors and warnings in R. The first argument of this function is any R expression, followed by conditions which specify how to handle an error or a warning. The last argument, finally
, specifies a function or expression that will be executed after the expression no matter what, even in the event of an error or a warning.
Let’s construct a simple function I’m going to call beera
that catches errors and warnings gracefully.
This function takes an expression as an argument and tries to evaluate it. If the expression can be evaluated without any errors or warnings then the result of the expression is returned and the message Finally done!
is printed to the R console. If an error or warning is generated, then the functions that are provided to the error
or warning
arguments are printed. Let’s try this function out with a few examples.
Finally done!
[1] 4
An error occurred:
Error in "two" + 2: non-numeric argument to binary operator
Finally done!
A warning occured:
simpleWarning in doTryCatch(return(expr), name, parentenv, handler): NAs introduced by coercion
Finally done!
Notice that we’ve effectively transformed errors and warnings into messages.
As a real use case of tryCatch()
check https://github.com/leekgroup/recount/blob/742c5ea7cc321729d6b3f03412d5829dd55e023e/R/download_retry.R#L40 which is based on the Bioconductor guidelines for querying data from the web https://contributions.bioconductor.org/querying-web-resources.html.
Now that you know the basics of generating and catching errors you’ll need to decide when your program should generate an error. My advice to you is to limit the number of errors your program generates as much as possible. Even if you design your program so that it’s able to catch and handle errors, the error handling process slows down your program by orders of magnitude. Imagine you wanted to write a simple function that checks if an argument is an even number. You might write the following:
[1] TRUE
Error in n%%2: non-numeric argument to binary operator
You can see that providing a string causes this function to raise an error. You could imagine though that you want to use this function across a list of different data types, and you only want to know which elements of that list are even numbers. You might think to write the following:
is_even_error <- function(n) {
tryCatch(n %% 2 == 0,
error = function(e) {
FALSE
}
)
}
is_even_error(714)
[1] TRUE
[1] FALSE
This appears to be working the way you intended, however when applied to more data this function will be seriously slow compared to alternatives. For example I could check that n
is numeric before treating n
like a number:
[1] TRUE
[1] FALSE
Notice that by using is.numeric()
before the “AND” operator (&&
), the expression n %% 2 == 0
is never evaluated. This is a programming language design feature called “short circuiting.” The expression can never evaluate to TRUE
if the left hand side of &&
evaluates to FALSE
, so the right hand side is ignored.
To demonstrate the difference in the speed of the code, we will use the microbenchmark
package to measure how long it takes for each function to be applied to the same data.
Unit: microseconds
expr min lq mean median uq max neval
sapply(letters, is_even_check) 46.224 47.7975 61.43616 48.6445 58.4755 167.091 100
Unit: microseconds
expr min lq mean median uq max neval
sapply(letters, is_even_error) 640.067 678.0285 906.3037 784.4315 1044.501 2308.931 100
The error catching approach is nearly 15 times slower!
Proper error handling is an essential tool for any software developer so that you can design programs that are error tolerant. Creating clear and informative error messages is essential for building quality software.
One closing tip I recommend is to put documentation for your software online, including the meaning of the errors that your software can potentially throw. Often a user’s first instinct when encountering an error is to search online for that error message, which should lead them to your documentation!
Errors, warnings, and messages can be generated within R code using the functions stop
, stopifnot
, warning
, and message
.
Catching errors, and providing useful error messaging, can improve user experience with functions but can also slow down code substantially.
─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────
setting value
version R version 4.3.1 (2023-06-16)
os macOS Ventura 13.6
system aarch64, darwin20
ui X11
language (EN)
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/New_York
date 2023-09-28
pandoc 3.1.5 @ /opt/homebrew/bin/ (via rmarkdown)
─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
cli 3.6.1 2023-03-23 [1] CRAN (R 4.3.0)
colorout 1.3-0 2023-09-28 [1] Github (jalvesaq/colorout@8384882)
digest 0.6.33 2023-07-07 [1] CRAN (R 4.3.0)
evaluate 0.21 2023-05-05 [1] CRAN (R 4.3.0)
fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.3.0)
htmltools 0.5.6 2023-08-10 [1] CRAN (R 4.3.0)
htmlwidgets 1.6.2 2023-03-17 [1] CRAN (R 4.3.0)
jsonlite 1.8.7 2023-06-29 [1] CRAN (R 4.3.0)
knitr 1.44 2023-09-11 [1] CRAN (R 4.3.0)
rlang 1.1.1 2023-04-28 [1] CRAN (R 4.3.0)
rmarkdown 2.24 2023-08-14 [1] CRAN (R 4.3.1)
rstudioapi 0.15.0 2023-07-07 [1] CRAN (R 4.3.0)
sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.0)
xfun 0.40 2023-08-09 [1] CRAN (R 4.3.0)
yaml 2.3.7 2023-01-23 [1] CRAN (R 4.3.0)
[1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────