R Language
制御フロー構造
サーチ…
備考
forループは、ドメイン上のタスクまたはタスクのセットを繰り返すためのフロー制御メソッドです。 forループのコア構造は次のとおりです。
for ( [index] in [domain]){
[body]
}
どこで
-
[index]
は名前であり、ループの繰り返しごとに[domain]
値を1つだけとります。 -
[domain]
は、反復する値のベクトルです。 -
[body]
は、各反復で適用される一連の命令です。
簡単な例として、forループを使用して値のベクトルの累積合計を取得することを考えてみましょう。
x <- 1:4
cumulative_sum <- 0
for (i in x){
cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum
Forループの構造の最適化
forループは、タスクを繰り返して概念化し実行するのに便利です。ただし、慎重に構築されていない場合は、 apply
ファンクションファミリの優先使用に比べて実行が非常に遅くなる可能性があります。それにもかかわらず、ループを最適化するためにforループ構築に含めることができるいくつかの要素があります。多くの場合、forループを適切に構築すると、適用関数の計算効率に非常に近い計算効率が得られます。
「正しく構築された」forループは、コア構造上に構築され、ループの各反復をキャプチャするオブジェクトを宣言するステートメントを含みます。このオブジェクトは、クラスと長さの両方を宣言する必要があります。
[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
[output][index] <- [body]
}
説明するために、数値ベクトル内の各値を2乗するループを作成してみましょう(これは、説明のための簡単な例です。このタスクを完了するための「正しい」方法は、 x_squared <- x^2
)。
x <- 1:100
x_squared <- vector("numeric", length = length(x))
for (i in seq_along(x)){
x_squared[i] <- x[i]^2
}
ここでも、最初に出力x_squared
レセプタクルを宣言し、 x
と同じ長さのクラス "numeric"を与えたことにx_squared
。さらに、 seq_along
関数を使用して「長さの安全なドメイン」を宣言しました。 seq_along
は、forループでの使用に適したオブジェクトのインデックスのベクトルを生成します。直感的にはfor (i in 1:length(x))
、 x
長さが0の場合、ループは1:0
ドメインを反復しようとします1:0
その結果、エラーが発生します(0番目のインデックスはR )。
レセプタクルオブジェクトと長さの安全なドメインは、 apply
ファンクションファミリによって内部的に処理され、ユーザーはforループの代わりにapply
アプローチを可能な限り採用するapply
が推奨されます。しかし、適切に構築されていれば、forループは効率の損失を最小限に抑えながらコードの明瞭度を向上させることがあります。
ループ用のベクトル化
Forループは、各繰り返し内で完了する必要があるタスクを概念化する際に有用なツールとなることがよくあります。ループが完全に展開され、概念化されると、ループを関数にすることに利点があるかもしれません。
この例では、forループを作成して、 mtcars
データセットの各列の平均を計算します(もう一度、 colMeans
関数を使用して実現できる簡単な例)。
column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
column_mean_loop[k] <- mean(mtcars[[k]])
}
ループの本体を関数として書き換えることにより、forループを適用関数に変換することができます。
col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))
結果を比較するには:
identical(column_mean_loop,
unname(column_mean_apply)) #* vapply added names to the elements
#* remove them for comparison
ベクトル化された形式の利点は、数行のコードを削除できることです。出力オブジェクトの長さと型を決定し、長さの安全な領域を反復する仕組みは、apply関数によって処理されます。さらに、適用関数はループより少し速いです。速度の違いは、反復回数と身体の複雑さによっては人間の言葉では無視できるほどです。
基本的なループ構築
この例では、データフレーム内の各列(この場合はmtcars
の2乗偏差を計算します。
オプションA:整数インデックス
squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
squared_deviance
は、期待通りに11要素のリストです。
class(squared_deviance)
length(squared_deviance)
オプションB:文字インデックス
squared_deviance <- vector("list", length(mtcars))
Squared_deviance <- setNames(squared_deviance, names(mtcars))
for (k in names(mtcars)){
squared_deviance[[k]] <- (mtcars[[k]] - mean(mtcars[[k]]))^2
}
結果としてdata.frame
必要な場合はどうすればよいですか?さて、リストを他のオブジェクトに変換するための多くのオプションがあります。しかし、この場合はおそらく最もシンプルなので、 for
結果をdata.frame
に格納することになります。
squared_deviance <- mtcars #copy the original
squared_deviance[TRUE]<-NA #replace with NA or do squared_deviance[,]<-NA
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
dim(squared_deviance)
[1] 32 11
文字オプション(B)を使用しても、結果は同じイベントになります。
Forループの最適構築
ループ構築に良い効果を説明するために、4つの異なる方法で各列の平均を計算します。
- 最適化されていないループを使用する
- forループ用に最適化されたものを使用する
-
*apply
関数ファミリを使用する -
colMeans
関数の使用
これらの各オプションはコードで表示されます。各オプションを実行するための計算時間の比較が表示されます。最後に相違点の説明を行います。
forループが最適化されていない
column_mean_poor <- NULL
for (i in 1:length(mtcars)){
column_mean_poor[i] <- mean(mtcars[[i]])
}
よく最適化されたループ
column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
column_mean_optimal <- mean(mtcars[[i]])
}
vapply
関数
column_mean_vapply <- vapply(mtcars, mean, numeric(1))
colMeans
関数
column_mean_colMeans <- colMeans(mtcars)
効率の比較
これらの4つのアプローチをベンチマークした結果を以下に示します(コードは表示されません)
Unit: microseconds
expr min lq mean median uq max neval cld
poor 240.986 262.0820 287.1125 275.8160 307.2485 442.609 100 d
optimal 220.313 237.4455 258.8426 247.0735 280.9130 362.469 100 c
vapply 107.042 109.7320 124.4715 113.4130 132.6695 202.473 100 a
colMeans 155.183 161.6955 180.2067 175.0045 194.2605 259.958 100 b
最適化されたfor
ループが、うまく構築されていないfor
ループを抹消したことに注目してください。不完全なforループは、出力オブジェクトの長さを絶えず増加させており、長さの各変更時に、Rはオブジェクトのクラスを再評価しています。
このオーバーヘッドの負担の一部は、ループを開始する前に出力オブジェクトのタイプとその長さを宣言することによって、最適化されたforループによって削除されます。
しかし、この例では、 vapply
関数を使用すると計算効率が2倍になります。その理由は、結果が数値でなければならないということです(1つの結果が数値ではない場合はエラーが返されます)。
使用colMeans
機能は、よりタッチ遅いvapply
機能。この違いは、 colMeans
で実行されたいくつかのエラーチェックと、 vapply
関数では実行されなかったas.matrix
変換( mtcars
はmtcars
であるdata.frame
)にvapply
ます。
他のループ構造:whileとrepeat
Rは必要な反復回数が不確定な状況で通常使用されるwhile
とrepeat
2つの追加ループ構成を提供します。
while
ループ
while
ループの一般的な形式は次のとおりです。
while (condition) {
## do something
## in loop body
}
condition
はループ本体に入る前に評価されます。 condition
がTRUE
と評価されると、ループ本体のコードが実行され、 condition
がFALSE
までこのプロセスが繰り返されFALSE
(またはbreak
文に達した場合、以下を参照)。 for
ループとは異なり、 while
ループが変数を使用して増分反復を実行する場合、変数は事前に宣言して初期化し、ループ本体内で更新する必要があります。たとえば、次のループは同じタスクを実行します。
for (i in 0:4) {
cat(i, "\n")
}
# 0
# 1
# 2
# 3
# 4
i <- 0
while (i < 5) {
cat(i, "\n")
i <- i + 1
}
# 0
# 1
# 2
# 3
# 4
上記のwhile
ループでは、無限ループを防ぐために、 i <- i + 1
が必要です。
さらに、 while
ループを終了するには、ループ本体の中からbreak
するための呼び出しが必要です。
iter <- 0
while (TRUE) {
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
}
iter
#[1] 4
この例では、 condition
は常にTRUE
であるため、ループを終了させる唯一の方法は、本体の中でbreak
を呼び出すことです。 iter
の最終的な値は、この例を実行するときのPRNGの状態に依存し、コードが実行されるたびに異なる結果(本質的に)を生成する必要があることに注意してください。
repeat
ループ
repeat
コンストラクトは基本的にwhile (TRUE) { ## something }
と同じですが、次の形式をとります:
repeat ({
## do something
## in loop body
})
余分な{}
は必須ではありませんが、 ()
は必須です。 repeat
を使用して前の例を書き直し、
iter <- 0
repeat ({
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
})
iter
#[1] 2
break
詳細
break
は、すぐに囲むループを終了するだけであることに注意するbreak
が重要です。つまり、以下は無限ループです。
while (TRUE) {
while (TRUE) {
cat("inner loop\n")
break
}
cat("outer loop\n")
}
しかし、少し創造性があれば、入れ子になったループから完全に壊れる可能性があります。たとえば、現在の状態で無限にループする次の式を考えてみましょう。
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
1つの可能性は、 break
とは異なり、 return
式には複数レベルの囲みループ全体に制御を戻す機能があることを認識することです。しかし、 return
は関数内でのみ有効なので、単にbreak
を上記のreturn()
置き換えることはできませんが、式全体を無名関数としてラップする必要があります:
(function() {
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
return()
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
})()
あるいは、式の前にダミー変数( exit
)を作成し、終了準備が整ったら内部ループから<<-
経由でアクティブにすることができます:
exit <- FALSE
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
exit <<- TRUE
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
if (exit) break
}