Rのこと。

記事は引っ越し作業中。2023年中までに引っ越しを完了させてブログは削除予定

Advanced R: Control flow

はじめに

Advanced R 2nd EditionのPaperBook版が届いたので、Rについてのおさらい備忘録。また、書籍を読んでいるにも関わらず誤った解釈や用語の誤用もあるかもしれませんので、参考にする際は自己責任でお願いします。

この記事のライセンスはAdvanced R 2nd Editionと同様で下記の通りです。

Control flow

Control flowについて。いわゆる制御構文ififelse()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"

その他にも、whilerepeatもあり、これらは便利なんでけれども、できる限りforを使うことが推奨されるとのこと。