Nancy
Nancy

Reputation: 4079

sapply() and ifelse() in R

I've encountered an issue wrapping nested sapply pasting code into ifelse() that checks to see that all the components are non-NA. The sapplys work great when they aren't in the ifelse()... Why is this?

Given some parameters:

a = c(1, 2, 3)
b = c("a", "b")
c = c("X", "Y")

Here's how I've managed to paste together all the combinations

as.vector(sapply(sapply(a, function(x){paste(x, b, sep = "")}), 
                 function(x){paste(x, c, sep = "")}))

The output is this. It's exactly what I want:

[1] "1aX" "1aY" "1bX" "1bY" "2aX" "2aY" "2bX" "2bY" "3aX" "3aY" "3bX" "3bY"

However, if I put the exact same code in an ifelse() that checks to make sure the parameters aren't NA, the output is different.

ifelse(!is.na(a) & !is.na(b) & !is.na(c), 
       as.vector(sapply(sapply(a, function(x){paste(x, b, sep = "")}), 
                                  function(x){paste(x, c, sep = "")})), "Error")
[1] "1aX" "1aY" "1bX"

Warning messages:
1: In !is.na(a) & !is.na(b) :
  longer object length is not a multiple of shorter object length
2: In !is.na(a) & !is.na(b) & !is.na(c) :
  longer object length is not a multiple of shorter object length

Why? It's obvious that a, b, and c are different lengths; I don't see why that matters in an ifelse(). To clarify, the !is.na() is checking to see if the whole vector is NA, NOT for things like c(1, NA, 3) because I'll be using the code in a context where that won't ever happen. I'm doing this because the ifelse's are part of larger function where the parameters default to NA; certain combinations of non-NA parameters require a certain actions. For example, if b = NA, then the code above should produce an ERROR. How can I accomplish both the nested pasting AND the conditional checking?

Upvotes: 0

Views: 3102

Answers (2)

thothal
thothal

Reputation: 20329

ifelse does an elementwise check of a vector and uses the corresponding value at the specific position where the 'condition' was 'TRUE' or 'FALSE' the 'then' and the 'else' case respectively. You could use ifelse for example to replace each negative element in a vector by the position value :

 d <- c(1, -1, 2, -2, 3, -3)
 order <- seq_along(d)
 ifelse(d < 0, order, d)
 # [1] 1 2 2 4 3 6

So the first elemt of d is not meeting the criterion so it is replaced by the first element of d. The second element does however meet the criterion so it is replaced by the second element of order and so on. That's why all the vectors should be of equal length and if not, R uses its recycling techniques.

So what you want to do is to use a simple if statement

a <- c(1, 2, 3)
b <- c("a", "b")
d <- c("X", "Y")
if (all(!is.na(c(a, b, d))))  
    as.vector(sapply(sapply(a, function(x){paste(x, b, sep = "")}), 
                                 function(x) {paste(x, d, sep = "")})) else 
    "Error"
# [1] "1aX" "1aY" "1bX" "1bY" "2aX" "2aY" "2bX" "2bY" "3aX" "3aY" "3bX" "3bY"

d <- NA
if (all(!is.na(c(a, b, d))))  
    as.vector(sapply(sapply(a, function(x){paste(x, b, sep = "")}), 
                                 function(x) {paste(x, d, sep = "")})) else 
    "Error"
# [1] "Error"

However, your code is difficult to read and you can increase readibility by the following code:

a <- c(1, 2, 3)
b <- c("a", "b")
d <- c("X", "Y")
if (all(!is.na(c(a, b, d)))) apply(expand.grid(a, b, d), 1, paste, collapse = "") else "Error"
# [1] "1aX" "2aX" "3aX" "1bX" "2bX" "3bX" "1aY" "2aY" "3aY" "1bY" "2bY" "3bY"

d <- NA
if (all(!is.na(c(a, b, d)))) apply(expand.grid(a, b, d), 1, paste, collapse = "") else "Error"
# [1] "Error"

expand.grid creates all combinations of the three vectors. apply runs through all rows (which is the first dimension, that's why the 1 as a second argument to apply and then applies paste to each row.

Hope that helps.

Upvotes: 1

cdeterman
cdeterman

Reputation: 19950

In all honesty, with a situation like this I would not use ifelse and instead use the if and else components separately. ifelse only returns a value of the same shape as your test (hence your initial output only providing 3 outputs) as you can see explained in this question. I cannot think of concise way to get all the combinations tested without some regex which seems nothing more than an unneeded complication. The following should work just fine.

if(!any(is.na(c(a,b,c)))){
  as.vector(sapply(sapply(a, function(x){paste(x, b, sep = "")}), 
                   function(x){paste(x, c, sep = "")}))
}else{
  "Error"
}

Upvotes: 1

Related Questions