Menu

Rのforループ:書き方・seq_along・break・next とループを使わない場面

Rのforループの仕組み:ベクトルや添字を巡回する方法、ベクトルを伸ばす罠を避けた結果の収集、nextとbreak、そしてベクトル化された呼び出しがループに勝る場面を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

基本の for ループ

Rの for ループは、ベクトルやリストの各要素に対してコードのブロックを1回ずつ実行します。ループ変数が値を順に受け取ります。

要素が3つ、実行も3回。用意するカウンタも、維持する条件もありません。for (fruit in fruits) は「fruits の各 fruit について」と読め、まさにそのとおりに動きます。同じかたちで、リストの要素、数値の連番(for (i in 1:10))、あるいは names() を介して何かの名前を巡回できます。

ループ内での表示には print() か cat() が必要

コンソールでは x と入力すればその値が表示されます。ループ本体の内側ではそうなりません。自動表示はトップレベルでのみ起こり、ループ内の裸の式は評価された後に捨てられます。

ループが「何もしない」ときに最初に確認すべきはここです。値をさっと見たいときは print()、出力の行を自分で組み立てるときは cat() を使いましょう("\n" を忘れずに。cat() は付けてくれません)。

seq_along() による添字の巡回

値だけでなく位置も必要になることがあります。同じ添字で別のベクトルに書き込む、番号付きのリストを表示する、といった場合です。添字を巡回するには seq_along() を使います。

古いコードでは for (i in 1:length(prices)) を見かけますが、これは本物のバグを抱えています。ベクトルが空のとき length() は0で、1:0 は「反復なし」を意味しません。コロンは降順に数えるからです。

1:length(empty) のループは2回実行され、存在しない要素に添字アクセスします(empty[1]NA で、代入すれば幽霊のような項目が作られます)。seq_along() は空のベクトルに対して空の連番を返すので、ループ本体はそもそも一度も実行されません。seq_along() を習慣にしましょう。ベクトルではなく個数を持っている場合は、兄弟の seq_len(n) が同じ仕事をします。

結果の収集:伸ばさず、事前に確保する

結果を積み上げるときに最初に思いつくのは、たいていこんなかたちです。

squares <- c()
for (i in 1:10000) {
  squares <- c(squares, i^2)   # copies the ENTIRE vector every pass
}

これでも動きますが、c(squares, ...) のたびに真新しいベクトルが確保され、古い要素がすべてコピーされます。最後には、1万個を作るためにおよそ5000万個の数値をコピーしたことになります。この「ベクトルを伸ばす」アンチパターンこそ、Rのループが遅いと言われる最大の理由です。

修正法は、まず結果を最終的な大きさで作り、それから添字で代入することです。

数値以外の結果にも同じ考え方が使えます。character(n)logical(n)、あるいは任意のものを入れるリストには vector("list", n) を使います。事前確保したループは十分に高速です。

next と break

ループ本体の内側から制御を操るキーワードが2つあります。next は現在の反復を打ち切って次の要素へ飛び、break はループそのものを抜けます。

これは 2 4 6 8 を表示します。奇数は next で飛ばされ、10が来たところで表示の前に break がループを終わらせます。早めにケースを除外するには next(本体のインデントを浅く保てます)、探していたものが見つかって続ける理由がなくなったときには break を使いましょう。

入れ子のループ

ループの本体には別のループを入れられます。外側のループの1回ごとに、内側のループが最後まで実行されます。定番の実演は九九の表です。

各行 i について内側のループがすべての列 j を巡り、内側のループが終わってからその行の改行が出力されます。入れ子は2段までなら問題ありません。3段以上になると、たいてい本体が関数になりたがっているか、計算全体が outer() や行列演算になりたがっています。

入れ子はベクトルのリストを巡回する場合にも当てはまります。外側でリストを回し、内側で各要素を回す(あるいは、より良くはベクトル化された呼び出しを使う)かたちです。

結局、内側のループはありません。mean() が内側のベクトル全体を1回の呼び出しで処理しています。この観察は一般化できて、それが正直な話につながります。

ループを使わないほうがよいとき

Rはベクトル化された言語です。基本的な演算はすでにベクトル全体に一度に作用します。要素を1つずつ変換するループは、1行で書けるものを長く書いているだけであることがよくあります。

ベクトル化された書き方が存在するなら常にそちらを選びましょう。短く、間違えにくく、しかも速いです。リストの各要素に関数を適用するなら、apply系sapplylapplyvapply)が同じ役割を果たします。

とはいえ、これを「ループは悪」と教条化しないでください。反復が前の反復に依存する場合(状態を持つ処理、シミュレーション)、ファイル書き込みのような副作用を伴う場合、あるいは単純に自分と読み手が一目で理解できるのがループ版である場合、ループこそが正しい道具です。明快なループは、誰にも読めない気の利いた1行に勝ります。

この記事のまとめ

  • for (x in v) { ... } は要素ごとに本体を1回実行します。カウンタの管理は不要です。
  • ループの中では print()cat() で明示的に表示しましょう。裸の式は捨てられます。
  • 位置の巡回には 1:length(v) ではなく seq_along(v) を。前者は空のベクトルで2回実行されます。
  • 結果は numeric(n)vector("list", n) で事前確保して添字で代入しましょう。c() で伸ばすと毎回すべてがコピーされます。
  • next は1回分を飛ばし、break は抜けます。ベクトル化された演算があるならそちらを優先しつつ、読みやすいループを恐れないこと。

次は、要素ごとに1回ではなく何かが起こるまで走るループ——whilerepeat です。

よくある質問

Rでforループはどう書きますか?

for (x in v) { ... } と書きます。ループ変数 x がベクトル(またはリスト)v の各要素を順に受け取り、本体は要素ごとに1回実行されます。値ではなく位置を巡回したいときは for (i in seq_along(v)) として v[i] で添字アクセスします。

Rで 1:length(v) ではなく seq_along を使うのはなぜ?

v が空のとき length(v) は0なので、1:length(v)1:0、つまり2要素のベクトル c(1, 0) になり、存在しない要素に対してループ本体が2回実行されてしまいます。seq_along(v) は空のベクトルに対して空の連番を返すので、ループは正しく0回になります。

Rのforループが何も表示しないのはなぜ?

Rの自動表示は、コンソールのトップレベルで入力された式に対してのみ起こります。ループ本体の内側では、x のような裸の式は評価された後に捨てられます。print(x) で包むか、出力を自分で整形したいときは cat(...) を使いましょう。

Rのforループは遅いのですか?

ループ自体は問題ありません。遅いのは、ループの中で result <- c(result, ...) としてベクトルを伸ばすことです。これは毎回ベクトル全体をコピーします。numeric(n)vector("list", n) で事前に確保して添字で代入すれば、ループは十分な性能を発揮します。とはいえ、同じ考えをベクトル化された演算やapply系の呼び出しで表現できるなら、たいていそちらのほうが速く、明快です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める