R logo

For ループは「仕事を反復 (iterate) する」ループ機能の一つ。

for(条件){処理}

で表す。

前回紹介した While ループと違うのは、()内に繰り返す回数が入る事。

For ループの書き方と While ループとの違い

例1:"Hello"とプリントする。

while ループだと、

# TRUEだったら"Hello"とプリントする。
while(TRUE){
   print("Hello")
}

上記をRUNすると、永遠に "Hello" を繰り返す。

output
> while(TRUE){
+ print("Hello")
+ }

[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
.
.
.

"Hello" を永遠に繰り返す事になる。

これが for ループだと、

i in ○○

の条件を加える。

例えば、1:5とすると、1,2,3,4,5 と5回繰り返す事になる。

# "Hello"と5回プリントする。
for (i in 1:5){
    print("Hello")
}

上記をRUNすると、"Hello"が5回繰り返してプリントされる。

output
> for(i in 1:5){
+ print("Hello")
+ }

[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"

別に1:5が(1,2,3,4,5)の5回でなくても、50:54の5回でも、"Taro","Jiro","Sabro","Shiro","Goro" の5回でもいい。

重要なのは、i (iterate) が条件内の数だけ繰り返す、という事。

for(i in 50:54){
print("Hello")
}

for(i in c("Taro","Jiro","Saburo","Shiro","Goro")){
print("Hello")
}
output
> for(i in 50:54){
+ print("Hello")
+ }

[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"

> for(i in c("Taro","Jiro","Saburo","Shiro","Goro")){
+ print("Hello")
+ }

[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"
[1] "Hello"

例2:rnormで出てきた数 (5回) をプリントする。

rnormとは、特定の正規分布から無作為に値を抽出するための関数 (r:random, norm:normal)。

例えば、rnormで無作為に5回数字を抽出する時は、

# 無作為に数を5回抽出する
x <- rnorm(5)
x
output
[1] 1.8495625 -0.1489451 1.0428246 0.7056592 -0.7004000

これをプリントするためには、

# x をプリントする
print(x[1])
print(x[2])
print(x[3])
print(x[4])
print(x[5])
output
> print(x[1])
[1] 1.849562

> print(x[2])
[1] -0.1489451

> print(x[3])
[1] 1.042825

> print(x[4])
[1] 0.7056592

> print(x[5])
[1] -0.7004

[]の中が繰り返しになっているので、これをfor loopを使って書くと、

# for loopを使って5回プリント
for(j in 1:5){
    print(x[j])
}
output
[1] 1.849562
[1] -0.1489451
[1] 1.042825
[1] 0.7056592
[1] -0.7004

For ループと空ベクトル

上記は "print" で出力しているが、通常 R の for ループは出力を返さない。

データフレームの要素などにあてがう時は、空ベクターなどを先に作っておき、そこに処理結果を書き込むようにする。

空のベクトルやリストを作る場合は、

vector()

、データフレームの場合は

matrix()

data.frame()

を使用して作成できる。

空ベクトルの作り方は下記参照。

他、下記のような方法も。

例3:データフレームCの列にAとBの和の値を入れる

Rはベクトル化されているので、本来は[]で充てがったり、dplyr::mutateを使って書けばOK。

# CがNAのベクターのデータフレームを作る
df1 <- data.frame(
    A = c(1,2,3,4,5),
    B = c(6,7,8,9,10),
    C = rep(NA,5)
    )

# Cの列にA + Bの結果を入れる
df1[,"C"] <- df1[,"A"] + df1[,"B"]
df1
output
r-for-2
# tidyverseをインストール
install.packages("tidyverse")
library(tidyverse)

# AとBの列が格納されたデータベースを作る
df2 <- data.frame(
    A=c(1,2,3,4,5),
    B=c(6,7,8,9,10)
)

# mutateで新しい列を作ってdf2に追加する
df2 <- mutate(df2, C = A + B)
df2
output
r-for-2

 

これを無理やりfor ループを使って書くとこんな感じ (※悪い例だけど、練習のために。)

# CがNAのベクターのデータフレームを作る
df3 <- data.frame(
    A = c(1,2,3,4,5),
    B = c(6,7,8,9,10),
    C = c(NA)
    )

# Cの列にはA + Bの結果が入る
for (i in 1:5){
    df3[i,"C"] = df3[i,"A"] + df3[i,"B"]
}
df3
output
r-for-2

Rでは for ループを使わない方が速い場合が多い

多くの言語で、for ループは非常に高速。

けれどもRでは for ループを使わない方が速いコードを書ける場合が多い。

それは、Rはベクトルベースの言語だから (だから、上記データフレームも、わざわざ for を使わず、ベクトルを利用した方が速い)。

 

小さなデータなら問題ないが、大きなデータになるほど処理速度が重要となってくる。

例4:1,-1,1,-1...という繰り返しの数が1000000回続いたベクトルの、すべての数を絶対値に変換する。

まずは、1,-1,1,-1...を1000000回繰り返すベクトルを作成する。

# ベクトルを作る
num1 <- rep(c(1,-1), 1000000)
num1
output
> num1 <- rep(c(1,-1),1000000)
> num1
[1] 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1
[24] -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1
[47] 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1
[70] -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1
[93] 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1
[116] -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1
...(1000000回まで続く。)

これを for ループと if 構文を使って絶対値に変換する場合、

# for と if を使って絶対値にする
abs_for <- function(eg){
    for (i in 1:length(eg)){
        if (eg[i] < 0){
            eg[i]=-eg[i]
        }
    }
    eg
}

# 試しに vector X1 の中に入れて結果を見てみる
X1 <- abs_for
X1
output
> X1 <- abs_for
> X1
[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[36] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[71] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[106] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[141] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
...(1000000回まで続く。)

次に、ベクトルを利用した書き方の場合。

# ベクトル内の各要素を絶対値に(要素が負の整数なら-1を掛ける)
abs_vec <- function(eg){
    neg <- eg < 0
    eg[neg] <- eg[neg] * -1
    eg
}

# 試しに vector X2 の中に入れて結果を見てみる
X2 <- abs_vec
X2
output
> X2 <- abs_vec
> X2
[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[36] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[71] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[106] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[141] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
...(1000000回まで続く。)

結果は同じだけど、

system.time()

で処理速度をみてみると、

# For ループを使った時の処理速度
system.time(abs_loop(num1))

# ベクトルを利用した時の処理速度
system.time(abs_vec(num1))
output
> system.time(abs_loop(num1))
user system elapsed
0.12 0.00 0.17

> system.time(abs_vec(num1))
user system elapsed
0.03 0.00 0.03

"elapsed" の処理速度を見ると、For ループを使った時は0.17秒、ベクトルを利用して計算した時は0.03秒と、6倍ほど速くなっている。

 

ちなみに、Rには多くのベクトル化された関数があり、もっと高速に実行できるよう最適化されている。

上例の場合は、

abs()

を使うと、もっと速くできる。

# abs関数を使って絶対値に変換する
abs(num1)

# 処理速度をみてみる
system.time(abs(num1))
output
> system.time(abs(num1))
user system elapsed
0.00 0.02 0.02

結果は0.02秒と一番早かった。

For ループを高速化するためのポイント

R でも、ベクトル化できないコードはたくさんあり、For ループが効率的な場面はいくつもある。

その場合、下記2つのポイントを押さえて書くと、より高速化できる。

For ループを高速化するためのポイント
  1. forループが必要ないものはできる限りループの外で処理を行う。
  2. ループと共に使う保存用オブジェクトを、ループのすべての結果を格納できるくらいの大きさに設定しておく。

1. は、余計な操作を繰り返さないため。

2. は、保存用オブジェクトの大きさを予め決めておくことで、Rが何回も output を書き直さずに済むから。

例5:1から1000000まで順に並べていく。

# 初期状態の長さが1のオブジェクトに値を格納する場合
loop1 <- function() {
    output <- NA for (i in 1:1000000){
        output[i] <- i + 1
    }
}

# 初期状態の長さが1000000のオブジェクト (output) の値を格納する場合
loop2 <- function(){
    output <- rep(NA, 1000000)
    for (i in 1000000){
    output[i] <- i + 1
    }
}

# loop1 の処理速度は?
system.time(loop1())
# loop2の処理速度は?
system.time(loop2())
output
> system.time(loop1())
user system elapsed
0.19 0.10 0.35

> system.time(loop2())
user system elapsed
0.00 0.00 0.01

格納するオブジェクトの長さを決めていない場合 (loop1) の処理速度は 0.35秒。

格納するオブジェクトの長さを予め10000000と決めていた場合 (loop2) の処理速度は 0.01秒と、後者の方が速かった。

References