ifelse()とif-else構文の違い

前回(jsonliteで要素ひとつだけのベクトルをうまくtoJSON()する - Technically, technophobic.)、 ifelseにベクトルを渡してもなぜか返ってくるのはひとつの値だけ、という挙動に悩んでたけど、

r - if-else vs ifelse with lists - Stack Overflow

を読んでそういうことかー、と思ったのでメモ。

ちゃんとifelseのヘルプに書いてありました。。

>?ifelse

(略)

ifelse(test, yes, no)

Arguments
test    an object which can be coerced to logical mode.
yes     return values for true elements of test.
no  return values for false elements of test.

(略)

If yes or no are too short, their elements are recycled. yes will be evaluated if and only if any element of test is true, and analogously for no.

Missing values in test give missing values in the result. 

つまり、第一引数(test)に渡した変数の長さと同じ個数の値しか返ってこない、ということです。
以下、この挙動をちょっと実験してみましょう。

testyesnoもひとつのとき

> ifelse(T, 1, 0)
[1] 1

値はひとつだけ返ってきます。ここはわかりやすいですね。

test複数で、yes,noの数より多いとき

> ifelse(c(T,F,T), 1, 0)
[1] 1 0 1

testに与えた分のT/Fに応じて、同じ個数だけ返ってきます。
これもまあ、言われてみれば、って感じですね。

では、次。

test複数で、yes,noの数より少ないとき

> ifelse(c(T,F), seq(1,10), seq(-1,-10))
[1]  1 -2

testの数だけしか返ってきません。

あ。。これでした。

あともう一つ。
長さの話は、気をつければいいだけですが、
ifelseにはもう一つクセがあります。
こっちは回避方法はなさそうです(あれば教えてください。。)。

ifelseyesnoをそのまま返してくれるわけじゃない

> library(jsonlite)
> unbox(1)
[1] 1
attr(,"class")
[1] "scalar"  "numeric"

jsonliteにはunboxという関数があり、scalarクラスのオブジェクトをつくります。
これを

> ifelse(T, unbox(1), 0)

とすると、

> ifelse(T, unbox(1), 0)
[1] 1
attr(,"class")
[1] "scalar"  "numeric"

となると思いきや、こうなります。

> ifelse(T, unbox(1), 0)
[1] 1

なんということでしょう。ただのnumericです。

それもそのはず、コードを見ると、ifelseが返しているのは、

> ifelse

(略)

    if (any(test[ok])) 
        ans[test & ok] <- rep(yes, length.out = length(ans))[test & 
            ok]
    if (any(!test[ok])) 
        ans[!test & ok] <- rep(no, length.out = length(ans))[!test & 
            ok]

ということで、yesから[test]で中身を取り出しちゃってるからです。

要は、上でやっていたのは、

> unbox(1)[T]
[1] 1

と同じわけでした。なるほどなー。ぐぬぬ

まとめ

ということで、

  • yesnoに入る値の長さが異なるとき
  • yesnoの値をそのまま返してほしいとき

if-else構文を使う必要があります。

あげた例で言うと、回りくどいですがこんな感じになります。

> f <- function(x) {
  if ( x ) {
    unbox(1)
  } else {
    0
  }
}
> f(T)
[1] 1
attr(,"class")
[1] "scalar"  "numeric"

うー、悩ましいですね。。