caldwellst
caldwellst

Reputation: 5956

How to check if vector is a single NA value without length warning and without suppression

I have a function with NA as a default, but if not NA should be a character vector not restricted to size 1. I have a check to validate these, but is.na produces the standard warning when the vector is a character vector with length greater than 1.

so_function <- function(x = NA) {
  if (!(is.na(x) | is.character(x))) {
    stop("This was just an example for you SO!")
  }
}

so_function(c("A", "B"))
#> Warning in if (!(is.na(x) | is.character(x))) {: the condition has length >
#> 1 and only the first element will be used

An option to prevent the warning I came up with was to use identical:

so_function <- function(x = NA) {
  if (!(identical(x, NA) | is.character(x))) {
    stop("This was just an example for you SO!")
  }
}

My issue here is that this function will generally be taking Excel sheet data loaded into R as inputs, and the NA values generated from that are often NA_character_, NA_integer_, and NA_real_, so identical(x, NA) is often FALSE when I actually need it to be TRUE.

For the broader context, I am experiencing this issue for S3 classes I am creating for a package, and the function below approximates how I am validating multiple attributes for that class, which is when the warnings are appearing. Because of this, I am trying to avoid suppressing warnings as the solution, so would be interested to know what best practice exists to solve this issue.

Edit

In order to make use cases clearer, this is validating attributes for a class, where I want to ensure the attribute is either a single NA value, or a character vector of any length:

so_function(NA_character_) # should pass
so_function(NA_integer_) # should pass
so_function(c(NA, NA)) # should fail
so_function(c("A", "B")) # should pass
so_function(c(1, 2, 3)) # should fail

Upvotes: 3

Views: 809

Answers (2)

James
James

Reputation: 66844

The length warning comes from the use of if, which expects a length 1 vector, and is.na which is vectorised.

You could use any or all around the is.na to compress it to a length 1 vector but there may be edge cases where it doesn't work as you expect so I would use shortcircuit evaluation to check it is length 1 on the is.na check:

so_function <- function(x = NA) {
  if (!((length(x)==1 && is.na(x)) | is.character(x))) {
    stop("This was just an example for you SO!")
  }
}

so_function(NA_character_) # should pass

so_function(NA_integer_) # should pass

so_function(c(NA, NA)) # should fail
Error in so_function(c(NA, NA)) : This was just an example for you SO!

so_function(c("A", "B")) # should pass

so_function(c(1, 2, 3)) # should fail
Error in so_function(c(1, 2, 3)) : This was just an example for you SO!

Another option is to use NULL as the default value instead.

Upvotes: 4

Xlrv
Xlrv

Reputation: 509

I don't think the problem arises from is.na() - it is a vectorized function which produces a vector as an output. is.character(x) on the other hand is not vectorized so it only will output a single value.

You can leverage apply-like functions to overcome this e.g.

sapply(c("a", NA, 5), is.character)

if also functions similarly - you are better off using ifelse for by-element comparison. I don't think I quite grasped what you what do to with you function but it could rewritten like this:

so_function_2 <- function(x = NA) {
  condit <- !(is.na(x) | sapply(x, is.character))
  ifelse(condit, "This was just an example for you SO!", "FALSE")
}

Upvotes: 0

Related Questions