Given a vector of any type, how can I get an NA value of the same class?

Viewed 106

Suppose I want to do something like:

mask_values <- function(x, mask) ifelse(mask, x, NA)

The purpose of this function is to take a vector and replace some of its values with NA based on the value of mask. However, this function doesn't guarantee that the return type is always the same as the input x. For example:

date_vec <- rep(lubridate::today(), 10)
my_mask <- rep(c(TRUE, FALSE), length.out = 10)
class(mask_values(date_vec, my_mask))

which yields "numeric" rather than the desired "Date". So I try switching to dplyr::if_else, which is supposed to preserve types:

mask_values <- function(x, mask) dplyr::if_else(mask, x, NA)
class(mask_values(date_vec, my_mask))

However, if_else also requires the input types to be the same as each other, and NA has type "logical", which means I get this error:

Error: `false` must be a `Date` object, not a logical vector.

So it seems that if I want to use if_else in order to preserve the input type, I need to be able to obtain an NA value with the same class as the input. Is there a reliable way to do this for any class? One possibility seems to be x[NA], but I'm not sure if that is a universal solution or if it just happens to work with the examples that I've tested. You can assume that the only classes that matter are "vector-like" classes for which NA values exist, such as Date and POSIXct, as well as all the basic R data types (logical, character, numeric, etc.).

Alternatively, is there another way to implement my mask_values function such that the return value always has the same type as x?

1 Answers

I recommend avoiding ifelse whenever possible. It is quite inefficient and as you have seen also quirky regarding what it returns (although that is well documented). I rarely use it and, if I do use it, only for interactive use and not programmatically.

The canonical and safe way of setting values to NA in base R is is.na<-. (Note that it supports logical and positional indexing. mask could also be a numeric vector.)

mask_values <- function(x, mask) {
  is.na(x) <- mask
  x
}

#or simply this:
#mask_values <- `is.na<-` 
#i.e., `is.na<-` is already what you want.

class(mask_values(date_vec, my_mask))
#[1] "Date"

Alternatively, you can also use simple subset-assignment. NA is a logical value. (If you create it like this. It can be coerced to other types and of course you can specify it as other types with NA_real_ etc.) If you assign a logical vector into any other vector, it will be coerced to that other vector's type (because "logical" is the most primitive type).

mask_values <- function(x, mask) {
  x[mask] <- NA
  x
}

class(mask_values(date_vec, my_mask))
#[1] "Date"

Btw., this subset-assignment is how the is.na<-.default method is defined.

I prefer doing subset-assignment explicitly in my code but occasionally the convenience function replace can be useful.

Related