The R language has two AND operators: & and &&. Knowing the difference is key when working with if clauses.
The R documentation states that:
The longer form the left to right examining only the first element of each vector. Evaluation proceeds only until the result is determined. The longer form is appropriate for programming control-flow and typically preferred in if clauses.<
Consider the following variables:
x = c(1, 2)
y = c(2, 1)The code below produces a logical vector of size 2: the evaluation is done for each of the elements.
x == 1 & y == 1## [1] FALSE FALSEThe second example below ends up testing the first element of each vector (i.e. x[[1]] == 1 & y[[1]] == 1) and produces a single value (TRUE & FALSE = FALSE).
x == 1 && y == 1## [1] FALSEThe last example produces TRUE as both the first value of x is equal to 1 and the first value of y is equal to 2.
x == 1 && y == 2## [1] TRUENote that if the comparison is done on vectors of different lengths, operator & will throw a warning.
z = c(1, 2, 3)
x == 1 & z == 2## Warning in x == 1 & z == 2: longer object length is not a multiple of
## shorter object length## [1] FALSE FALSE FALSEOperator && will not complain.
x == 1 && z == 2## [1] FALSEPractical case
Suppose that you are writing the following R function:
my_func <- function(x) {
if (!is.na(x)) {
message(paste("value:", x, collapse = ", "))
} else {
message("x is NA")
}
}Le’s run the function.
my_func(1)## value: 1my_func(NA)## x is NASo far, so good. But what happens when you pass NULL?
my_func(NULL)## Warning in is.na(x): is.na() applied to non-(list or vector) of type
## 'NULL'## Error in if (!is.na(x)) {: argument is of length zeroYou got an error because is.na(NULL) returns a logical vector of size 0 that is not compatible with the if statement. So you adjust naively your function as follows:
my_func <- function(x) {
if (!is.null(x) & !is.na(x)) {
message(paste("value:", x, collapse = ", "))
} else {
message("x is NA or NULL")
}
}my_func(NULL)## Warning in is.na(x): is.na() applied to non-(list or vector) of type
## 'NULL'## Error in if (!is.null(x) & !is.na(x)) {: argument is of length zeroIt still fails because the & operator evaluates is.na even when the first condition has been made. Adding the && operator fixes the issue:
my_func <- function(x) {
if (!is.null(x) && !is.na(x)) {
message(paste("value:", x, collapse = ", "))
} else {
message("x is NA or NULL")
}
}my_func(NULL)## x is NA or NULLThe && operator is more in line with .NET for example where additional conditions may not be evaluated based on result of previous conditions. On a side note, in .NET, the & operator performs bitwise AND operation.
Finally, and to be complete, passing a vector of length greater than 1 will produce the following:
my_func(c(NA, 1))## x is NA or NULLmy_func(c(1, NA)) # the second NA is not evaluated## value: 1, value: NAYou may want to use expressions such as any(is.na(x)) to check if any of the elements in x are unknown.
Recap
I have compiled below the output of the & and && operators based on different parameters.
| x | print(x) | length(x) | !is.null(x) && !is.na(x) | !is.na(x) && !is.null(x) | !is.null(x) & !is.na(x) |
|---|---|---|---|---|---|
| NULL | NULL | 0 | [1] FALSE | [1] FALSE [Warning: is.na() applied to non-(list or vector) of type ‘NULL’] | logical(0) [Warning: is.na() applied to non-(list or vector) of type ‘NULL’] |
| as.numeric(0) | [1] 0 | 1 | [1] TRUE | [1] TRUE | [1] TRUE |
| as.numeric(NA) | [1] NA | 1 | [1] FALSE | [1] FALSE | [1] FALSE |
| c(1, as.numeric(NA)) | [1] 1 NA | 2 | [1] TRUE | [1] TRUE | [1] TRUE FALSE |
| c(as.numeric(NA), 1) | [1] NA 1 | 2 | [1] FALSE | [1] FALSE | [1] FALSE TRUE |