ifelse()のつらみ アップデート版
ifelse()
のつらみについては以前、ブログに書きました。
が、今日、こんなツイートを見かけて、ちゃんと理解していなかったことに気づいたのでメモ。
test <- as.POSIXct("2015-06-01 02:03:54")
test2 = NULL
ifelse(is.null(test2),http://t.co/yTACaeMBw6(test),test2)
— Oliver Keyes (@quominus) 2015, 9月 23
ちょっと例が分かりづらいので、もうちょっとシンプルなやつにします。
ifelse(TRUE, as.Date("2015-09-24"), as.Date("2015-09-25"))
これを実行するとどうなるか、わかるでしょうか。
こうです。
#> [1] 16702
????
なんでDate型じゃないの?という疑問で頭はいっぱいです。
しかし、これはバグでもなんでもなく、ドキュメントに書かれている期待通りの動作です。「Warning」というセクションにこんなことが書いてあります。
The mode of the result may depend on the value of
test
(see the examples), and theclass
attribute (see oldClass) of the result is taken from test and may be inappropriate for the values selected fromyes
andno
.
具体的に、Exampleにはこんなコードがついてます。
## ifelse() strips attributes ## This is important when working with Dates and factors x <- seq(as.Date("2000-02-29"), as.Date("2004-10-04"), by = "1 month") ## has many "yyyy-mm-29", but a few "yyyy-03-01" in the non-leap years y <- ifelse(as.POSIXlt(x)$mday == 29, x, NA) head(y) # not what you expected ... ==> need restore the class attribute: class(y) <- class(x) y
need restore the class attribute
って...ちょっと何言ってるかわからないってばよ...
具体的にifelse
のコードを見てみましょう。
ifelse <- function (test, yes, no) { ...(略)... ans <- test ok <- !(nas <- is.na(test)) 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] ans[nas] <- NA ans }
r-source/ifelse.R at master · wch/r-source · GitHub
どこがイケてないかというと、
まずans
にtest
がつっこまれています。ここでans
は問答無用でlogical
型になります。
ans <- test
そのあと、ans
にむりやり値を入れています。おいおい!
ans[test & ok] <- rep(yes, length.out = length(ans))[test & ok]
そりゃー無茶ってもんでしょ...。当然情報は失われます。
型に厳格じゃないRには荷が重いとはいえ、もうちょっといい実装があったのでは。。
まとめ
ということで、前回のまとめを再掲しておくと、
yes
やno
に入る値の長さが異なるときyes
やno
の値をそのまま返してほしいとき(日時とかfactorとかもアウト)
はif-else
構文を使う必要があります。注意してください。