79

In R, how can I determine whether a function call results in a warning?

That is, after calling the function I would like to know whether that instance of the call yielded a warning.

Alex Holcombe
  • 2,453
  • 4
  • 24
  • 34
  • 2
    Closely related to https://stackoverflow.com/q/4948361 that provides some excellent answers with error catching as well. – JWilliman Nov 08 '18 at 23:27

6 Answers6

86

If you want to use the try constructs, you can set the options for warn. See also ?options. Better is to use tryCatch() :

x <- function(i){
  if (i < 10) warning("A warning")
  i
}

tt <- tryCatch(x(5),error=function(e) e, warning=function(w) w)

tt2 <- tryCatch(x(15),error=function(e) e, warning=function(w) w)

tt
## <simpleWarning in x(5): A warning>

tt2
## [1] 15

if(is(tt,"warning")) print("KOOKOO")
## [1] "KOOKOO"

if(is(tt2,"warning")) print("KOOKOO")

To get both the result and the warning :

tryCatch(x(5),warning=function(w) return(list(x(5),w)))

## [[1]]
## [1] 5
## 
## [[2]]
## <simpleWarning in x(5): A warning>

Using try

op <- options(warn=2)

tt <- try(x())
ifelse(is(tt,"try-error"),"There was a warning or an error","OK")
options(op)
Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519
Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • 7
    thanks for your edit showing how to get both the result and the warning; however, the function is called twice when there's a warning. Can it be done with only one call (in case the function is slow, for example)? – Aaron left Stack Overflow Feb 10 '11 at 02:17
  • @Aaron : then I'd go for the handlers as shown below by you. That's the cleanest way, and those are made to do that. It just requires a bit more puzzling. – Joris Meys Feb 10 '11 at 08:56
  • Thanks. It is definitely puzzling. Though it does what I need, there's a lot in that code I don't yet understand. – Aaron left Stack Overflow Feb 10 '11 at 15:18
31

On the R-help mailing list (see http://tolstoy.newcastle.edu.au/R/help/04/06/0217.html), Luke Tierney wrote:

"If you want to write a function that computes a value and collects all warning you could do it like this:

withWarnings <- function(expr) {
    myWarnings <- NULL
    wHandler <- function(w) {
        myWarnings <<- c(myWarnings, list(w))
        invokeRestart("muffleWarning")
    }
    val <- withCallingHandlers(expr, warning = wHandler)
    list(value = val, warnings = myWarnings)
} 
Aaron left Stack Overflow
  • 36,704
  • 7
  • 77
  • 142
11

2019 update

You can you use 'quietly' from the purrr package, which returns a list of output, result, warning and message. You can then extract each element by name. For instance, if you had a list, which you want to map a function over, and find the elements which returned a warning you could do

library(purrr)
library(lubridate)

datelist <- list(a = "12/12/2002", b = "12-12-2003", c = "24-03-2005")

# get all the everything
quiet_list <- map(datelist, quietly(mdy))

# find the elements which produced warnings
quiet_list %>% map("warnings") %>% keep(~ !is.null(.))

# or 
quiet_list %>% keep(~ length(.$warnings) != 0)

For this example it's quite trivial, but for a long list of dataframes where the NAs might be hard to spot, this is quite useful.

Tom Greenwood
  • 1,502
  • 14
  • 17
  • Your first statement is not accurate, quietly does not handle errors see https://purrr.tidyverse.org/reference/quietly.html – pietrodito Aug 23 '23 at 11:51
  • @pietrodito thanks for the heads up, I've edited my answer. Maybe they changed the return values since 2019 or maybe I just had it wrong. Either way it's corrected now, thanks! – Tom Greenwood Aug 24 '23 at 16:15
7

here is an example:

testit <- function() warning("testit") # function that generates warning.

assign("last.warning", NULL, envir = baseenv()) # clear the previous warning

testit() # run it

if(length(warnings())>0){ # or !is.null(warnings())
    print("something happened")
}

maybe this is somehow indirect, but i don't know the more straightforward way.

kohske
  • 65,572
  • 8
  • 165
  • 155
  • Although this is inelegant, it's good because I can't find any other way to catch a warning and nevertheless return the normal result from the function. That is, if testit() returns a value, catching a warning with tryExcept means you lose the value. Unless I'm missing something? – Alex Holcombe Oct 22 '10 at 04:43
  • 1
    @Alex : you could do pretty easily using a tryCatch formula. You can do something with the warning in the argument, eg: warning=function(w) {...do something ... return(normal result)} – Joris Meys Feb 04 '11 at 10:29
  • @Joris: I couldn't get tryCatch to return the normal result. Am I missing something? I did find a way using withCallingHandlers, from the R mailing list (included as a separate answer). – Aaron left Stack Overflow Feb 09 '11 at 16:21
  • @AlexHolcombe You could also set `options(warn=1)` in which case the warnings are printed to `stdout` rather than `stderr`. – isomorphismes Nov 11 '11 at 09:30
0

For a simple TRUE/FALSE return on whether a given operation results in a warning (or error), you could use the is.error function from the berryFunctions package, after first setting options(warn = 2) so that warnings are converted to errors.

E.g.,

options(warn = 2)
berryFunctions::is.error(as.numeric("x")) # TRUE
berryFunctions::is.error(as.numeric("3")) # FALSE

If you want to limit the option change to the use of this function, you could just create a new function as follows.

is.warningorerror <- function(x) {
 op <- options()
 on.exit(options(op))
 options(warn = 2)
 berryFunctions::is.error(x)
}

is.warningorerror(as.numeric("x")) # TRUE
options("warn") # still 0 (default)
0

I personally use the old good sink redirected into a text connection:

# create a new text connection directed into a variable called 'messages'
con <- textConnection("messages","w")
# sink all messages (i.e. warnings and errors) into that connection
sink(con,type = "message")
# a sample warning-generating function
test.fun <- function() {
    warning("Your warning.")
    return("Regular output.")
}
output <- test.fun()
# close the sink
sink(type="message")
# close the connection
close(con)
# if the word 'Warning' appears in messages than there has been a warning
warns <- paste(messages,collapse=" ")
if(grepl("Warning",warns)) {
    print(warns)
}
# [1] "Warning message: In test.fun() : Your warning."
print(output)
# [1] "Regular output."

Possibly more straightforward and cleaner than the other suggested solutions.

kurpav00
  • 138
  • 6