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 FALSE

The 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] FALSE

The 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] TRUE

Note 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 FALSE

Operator && will not complain.

x == 1 && z == 2
## [1] FALSE

Practical 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: 1
my_func(NA)
## x is NA

So 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 zero

You 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 zero

It 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 NULL

The && 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 NULL
my_func(c(1, NA)) # the second NA is not evaluated
## value: 1, value: NA

You 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