ifelse()のつらみ アップデート版

ifelse()のつらみについては以前、ブログに書きました。

notchained.hatenablog.com

が、今日、こんなツイートを見かけて、ちゃんと理解していなかったことに気づいたのでメモ。

ちょっと例が分かりづらいので、もうちょっとシンプルなやつにします。

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 the class attribute (see oldClass) of the result is taken from test and may be inappropriate for the values selected from yes and no.

具体的に、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

どこがイケてないかというと、

まずanstestがつっこまれています。ここでansは問答無用でlogical型になります。

    ans <- test

そのあと、ansにむりやり値を入れています。おいおい!

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

そりゃー無茶ってもんでしょ...。当然情報は失われます。

型に厳格じゃないRには荷が重いとはいえ、もうちょっといい実装があったのでは。。

まとめ

ということで、前回のまとめを再掲しておくと、

  • yesnoに入る値の長さが異なるとき
  • yesnoの値をそのまま返してほしいとき(日時とかfactorとかもアウト)

if-else構文を使う必要があります。注意してください。