Advanced R: Control flow
はじめに
Advanced R 2nd EditionのPaperBook版が届いたので、Rについてのおさらい備忘録。また、書籍を読んでいるにも関わらず誤った解釈や用語の誤用もあるかもしれませんので、参考にする際は自己責任でお願いします。
この記事のライセンスはAdvanced R 2nd Editionと同様で下記の通りです。
Control flow
Control flowについて。いわゆる制御構文if
、ifelse()
、for
など。
if
について
if
の基本形は下記の通り。condition
は単一に評価される必要がある。
if (condition) true_action else false_action
なので、単一であっても下記のような場合はエラーが返される。
if ("x") 1 Error in if ("x") 1: argument is not interpretable as logical if (logical()) 1 Error in if (logical()) 1: argument is of length zero if (NA) 1 Error in if (NA) 1: missing value where TRUE/FALSE needed
実は単一ではない場合警告が出るだけで実行される仕様になっていたが、R3.5以降ではエラーが返されるように、下記の通り修正できるようになったとのこと。
Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "true") if (c(TRUE, FALSE)) 1 Error in if (c(TRUE, FALSE)) 1: the condition has length > 1
下記のような場合は注意が必要。0 == FALSE
なので、FALSE
と判定されてしまいます。
x <- numeric() if (length(x)) "not empty" else "empty" length(x) [1] 0
ifelse
について
if
は単一の値しか評価されないが、ベクトル化されているifelse
を使えばベクトルを処理できる。dplyr::case_when()
使えばより細かく判定することが可能。
ifelse(condition == TRUE, true_action, false_action) x <- c(1:8, NA, 9) ifelse(x %% 2 == 0, "even", "odd") [1] "odd" "even" "odd" "even" "odd" "even" "odd" "even" NA "odd"
for
ループについて
for
ループは、ベクトル内の項目を反復処理するために使用されます。基本形は下記の通り。
for (item in vector) perform_action
for
ループは、指定した回数分、反復して処理させるものではあるが、途中で中断させることも方法が2つある。それが、現在の繰り返しを終了するnext
と、for
ループ全体を終了するbreak
。
for (i in 1:10) { if (i < 3) next print(i) if (i >= 5) break } [1] 3 [1] 4 [1] 5
落とし穴
for
ループを使用する場合、先にアウトプットを用意すること。そうでなければ、ループは非常に遅くなる。これはCopy on modifyのところで説明した通り、オブジェクトのコピーが発生するため。
means <- c(1, 50, 20) out <- vector("list", length(means)) for (i in 1:length(means)) { out[[i]] <- rnorm(10, means[[i]]) } [[1]] [1] 2.76356185 0.01937585 1.89864124 -0.52372274 -0.40924149 0.71505632 [7] 1.12387042 1.16842304 1.21244371 1.65902875 [[2]] [1] 50.33797 50.65605 48.21239 50.06447 50.24509 49.93773 48.16690 49.84902 [9] 49.93927 51.46821 [[3]] [1] 21.93245 20.18337 20.73120 20.98492 17.91800 19.61014 20.09043 18.87215 [9] 18.82155 19.71031
次に、1:length(x)
は使わないこと。x
の長さが0
の場合、エラーが返されます。下記の例では、イタレーションのインデックスが1のあとに0になってしまいます。
means <- c() out <- vector("list", length(means)) for (i in 1:length(means)) { out[[i]] <- rnorm(10, means[[i]]) } rnorm(10, means[[i]]) でエラー: 引数 (複数) が不正です
seq_along(x)
を代わりに使うことが推奨される。これは0
ベクトルに対しては0
を返すため。
eq_along(means) integer(0) out <- vector("list", length(means)) for (i in seq_along(means)) { out[[i]] <- rnorm(10, means[[i]]) } out list()
ベクトルがいつ評価されるのかが気になるが、これは最初に評価されます。
xs <- c(1, 2, 3) for (x in xs) { xs <- c(xs, x * 2) } xs [1] 1 2 3 2 4 6
for
ループは属性を削除するのでS3ベクトルを使う場合は注意が必要。例えば、S3ベクトルではない、ただの文字列の場合は問題ない。
for (i in nm) { print(i) } [1] "Sepal.Length" [1] "Sepal.Width" [1] "Petal.Length" [1] "Petal.Width" [1] "Species"
S3ベクトルを使う場合は属性が削除される。
Sys.time() [1] "2019-07-29 22:19:08 JST" xs <- Sys.time() for (x in xs) { print(x) } [1] 1564406356
S3ベクトルの場合、[[
を使って呼び出します。
xs <- Sys.time() for (i in seq_along(xs)) { print(xs[[i]]) } [1] "2019-07-29 22:20:16 JST"
その他にも、while
やrepeat
もあり、これらは便利なんでけれども、できる限りfor
を使うことが推奨されるとのこと。