前回(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"
うー、悩ましいですね。。