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)。
Rを使った正規分布関連の覚書。 正規分布 (normal distribution) とは 正規分布とは、平均値と最頻値・中央値が一致し、それを軸として左右対称となっている確率分布。 多くの統計的手法において、データが正 …
例えば、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()
を使用して作成できる。
空ベクトルの作り方は下記参照。
データフレームの要素などにあてがう時は、空vectorなどを先に作っておき、そこに処理結果を書き込むようにする。 空のvectorやlistを作る場合は、 vector() 、data frameの場合は matrix( …
他、下記のような方法も。
例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
# 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
これを無理やり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 ループを使わない方が速い場合が多い
多くの言語で、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. は、余計な操作を繰り返さないため。
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秒と、後者の方が速かった。