サーチ…


備考

forループは、ドメイン上のタスクまたはタスクのセットを繰り返すためのフロー制御メソッドです。 forループのコア構造は次のとおりです。

for ( [index] in [domain]){
  [body]
}

どこで

  1. [index]は名前であり、ループの繰り返しごとに[domain]値を1つだけとります。
  2. [domain]は、反復する値のベクトルです。
  3. [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つの異なる方法で各列の平均を計算します。

  1. 最適化されていないループを使用する
  2. forループ用に最適化されたものを使用する
  3. *apply関数ファミリを使用する
  4. 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変換( mtcarsmtcarsであるdata.frame )にvapplyます。

他のループ構造:whileとrepeat

Rは必要な反復回数が不確定な状況で通常使用されるwhilerepeat 2つの追加ループ構成を提供します。


whileループ

whileループの一般的な形式は次のとおりです。

while (condition) {
    ## do something
    ## in loop body
}

conditionはループ本体に入る前に評価されます。 conditionTRUEと評価されると、ループ本体のコードが実行され、 conditionFALSEまでこのプロセスが繰り返され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
}


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow