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)に渡した変数の長さと同じ個数の値しか返ってこない、ということです。
以下、この挙動をちょっと実験してみましょう。
testもyesもnoもひとつのとき
> 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にはもう一つクセがあります。
こっちは回避方法はなさそうです(あれば教えてください。。)。
ifelseはyesやnoをそのまま返してくれるわけじゃない
> 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
と同じわけでした。なるほどなー。ぐぬぬ。
まとめ
ということで、
yesやnoに入る値の長さが異なるときyesやnoの値をそのまま返してほしいとき
はif-else構文を使う必要があります。
あげた例で言うと、回りくどいですがこんな感じになります。
> f <- function(x) {
if ( x ) {
unbox(1)
} else {
0
}
}
> f(T)
[1] 1
attr(,"class")
[1] "scalar" "numeric"
うー、悩ましいですね。。