Control Flow
Overview
Teaching: 45 min
Exercises: 20 minQuestions
How can I make data-dependent choices in R?
How can I repeat operations in R?
Objectives
Write conditional statements with
if()
andelse()
.Write and understand
for()
loops.
コードを書く際、実行の流れを制御する必要がよくあります。 これは、ある条件、または一連の条件が満たされたときに、実行されるようにすればできます。 あるいは、決まった回数実行されるよう設定することもできます。
Rでは、流れを制御する方法がいくつかあります。 条件付きの宣言で、最もよく使われるのが「構文(constructs)」です:
# if
if (condition is true) {
perform action
}
# if ... else
if (condition is true) {
perform action
} else { # that is, if the condition is false,
perform alternative action
}
例えばRに、もし変数 x
が特定の値を持っていた場合、メッセージを表示させたいとします。
x <- 8
if (x >= 10) {
print("x is greater than or equal to 10")
}
x
[1] 8
xは、10より大きくないため、表示された宣言はコンソール上に表示されません。10より小さい場合に違ったメッセージを表示させるためには、 else
宣言を追加しなければなりません。
x <- 8
if (x >= 10) {
print("x is greater than or equal to 10")
} else {
print("x is less than 10")
}
[1] "x is less than 10"
else if
を使うと、複数の条件を試すこともできます。
x <- 8
if (x >= 10) {
print("x is greater than or equal to 10")
} else if (x > 5) {
print("x is greater than 5, but less than 10")
} else {
print("x is less than 5")
}
[1] "x is greater than 5, but less than 10"
重要: Rが if()
宣言の中の条件を計算する際、論理要素( TRUE
or FALSE
)を探します。
初心者には複雑なので、混乱してしまうかもしれません。例えば:
x <- 4 == 3
if (x) {
"4 equals 3"
} else {
"4 does not equal 3"
}
[1] "4 does not equal 3"
ここで見たように、ベクトルxが FALSE
であるので、不等号のメッセージが表示されたのです。
x <- 4 == 3
x
[1] FALSE
チャレンジ1
if()
宣言を使って、gapminder
のデータセットに2002年の記録があるかどうかを伝える 適当なメッセージを表示してください。 2012年についても、同じことをしてください。チャレンジ1の解答
any()
を使わないチャレンジ1の解答をまず見てみましょう。 最初に、gapminder$year
の内、どの要素が2002
と等しいかを記した論理ベクトルを手に入れます:gapminder[(gapminder$year == 2002),]
そして、データフレーム
gapminder
の2002と対応する行の数を数えます:rows2002_number <- nrow(gapminder[(gapminder$year == 2002),])
2002年の記録が存在するということは、
rows2002_number
が1以上あるということです:rows2002_number >= 1
全てを合わせると、以下になります:
if(nrow(gapminder[(gapminder$year == 2002),]) >= 1){ print("Record(s) for the year 2002 found.") }
any()
を使えば、もっと早くできます。論理条件は、次のように書けます:if(any(gapminder$year == 2002)){ print("Record(s) for the year 2002 found.") }
次のような警告メッセージをもらった人はいますか?
Warning in if (gapminder$year == 2012) {: the condition has length > 1 and
only the first element will be used
もし、1つ以上の要素を持つベクトルを評価する条件の場合、
関数 if()
は走るのですが、最初の要素の条件しか評価しません。
ここでは、確実に条件の長さは1であるようにする必要があります。
ヒント:
any()
とall()
any()
関数は、少なくとも1つTRUE
の値がある場合、TRUE
を返し、 そうでない場合は、FALSE
を返します。 これは、%in%
演算子でも同様に使えます。 関数all()
は、その名前が示唆しているように、ベクトル内の全ての値がTRUE
である時のみ、TRUE
となります。
繰り返し行う処理
一連の値に繰り返し同じ演算をしたい場合、
そして繰り返す順番も重要である場合は、
for()
ループを使いましょう。
for()
ループは、先ほどシェルのレッスンで見ましたね。これは、最も柔軟な
ループの演算子ですが、それゆえ、正しく使うのが最も難しいです。
繰り返す順番が重要(つまり直前の繰り返しの結果をもとに、次々と
繰り返しの演算行われる形)でない限り、 for()
ループを使うのを避けましょう。
for()
ループの基本構造は:
for(iterator in set of values){
do a thing
}
例:
for(i in 1:10){
print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
1:10
の部分は、ベクトルをその場で作るもので、他のベクトルの間中、繰り返すこともできます。
for()
ループを、もうひとつの for()
ループと入れ子となる形にすれば、2つのことを同時に繰り返すこともできます。
for(i in 1:5){
for(j in c('a', 'b', 'c', 'd', 'e')){
print(paste(i,j))
}
}
[1] "1 a"
[1] "1 b"
[1] "1 c"
[1] "1 d"
[1] "1 e"
[1] "2 a"
[1] "2 b"
[1] "2 c"
[1] "2 d"
[1] "2 e"
[1] "3 a"
[1] "3 b"
[1] "3 c"
[1] "3 d"
[1] "3 e"
[1] "4 a"
[1] "4 b"
[1] "4 c"
[1] "4 d"
[1] "4 e"
[1] "5 a"
[1] "5 b"
[1] "5 c"
[1] "5 d"
[1] "5 e"
結果を表示させずに、ループの結果を新しいオブジェクトとして書くこともできます。
output_vector <- c()
for(i in 1:5){
for(j in c('a', 'b', 'c', 'd', 'e')){
temp_output <- paste(i, j)
output_vector <- c(output_vector, temp_output)
}
}
output_vector
[1] "1 a" "1 b" "1 c" "1 d" "1 e" "2 a" "2 b" "2 c" "2 d" "2 e" "3 a"
[12] "3 b" "3 c" "3 d" "3 e" "4 a" "4 b" "4 c" "4 d" "4 e" "5 a" "5 b"
[23] "5 c" "5 d" "5 e"
このアプローチが役に立つこともありますが、’結果を太らせる’ (結果のオブジェクトを 段々積み上げる)と、演算する上で非効率になります。 ゆえに、多くの値の間を繰り返すときは避けましょう。
ヒント:結果を太らせないようにしましょう
初心者と経験のあるRユーザーの両方の足を引っ張るループの使い方の一つとして、 結果のオブジェクト(ベクトル、リスト、行列、データフレーム)をループするたびに作ってしまうという ことがあります。 コンピュータはこれを扱うのがとても下手で、すぐに計算が 遅くなります。適当な次元を持つ空の結果オブジェクトを 前もって宣言しておく方が効率的です。 もし、上記の行列に蓄積される最後の結果が何になるかを知っていたら、 5行、5列の数列を作成しておいて、それぞれの繰り返しで、適切な場所へ結果を 蓄積しましょう。
よりよい方法は、(空の)出力オブジェクトを、値を埋める前に宣言することです。 この例では、より複雑に見えますが、それでもより効率的です。
output_matrix <- matrix(nrow=5, ncol=5)
j_vector <- c('a', 'b', 'c', 'd', 'e')
for(i in 1:5){
for(j in 1:5){
temp_j_value <- j_vector[j]
temp_output <- paste(i, temp_j_value)
output_matrix[i, j] <- temp_output
}
}
output_vector2 <- as.vector(output_matrix)
output_vector2
[1] "1 a" "2 a" "3 a" "4 a" "5 a" "1 b" "2 b" "3 b" "4 b" "5 b" "1 c"
[12] "2 c" "3 c" "4 c" "5 c" "1 d" "2 d" "3 d" "4 d" "5 d" "1 e" "2 e"
[23] "3 e" "4 e" "5 e"
ヒント:while ループ
ときには、ある条件が満たされるまで繰り返す必要がある場合に出くわすこともあるでしょう。 これは、
while()
ループを使えばできます。while(this condition is true){ do a thing }
例として、このwhileループは一様分布(
runif()
関数)から0.1よりも小さい数を得るまで、 0から1の間で乱数を生成します。z <- 1 while(z > 0.1){ z <- runif(1) print(z) }
while()
ループは、いつも適当であるとは言えません。特に、条件が決して満たされないことによる 無限ループに陥らないように特に注意する必要があります。
チャレンジ2
output_vector
とoutput_vector2
のオブジェクトを比較しましょう。 同じですか?もし違ったら、なぜそうなったのでしょう?output_vector2
の最後のコードのかたまりをoutput_vector
と同じにするには、どう変えればよいでしょうか?チャレンジ2の解答
2つのベクトルが同じかを調べるために、
all()
関数を使いましょう:all(output_vector == output_vector2)
しかし、
output_vector
の全ての要素は、output_vector2
にあります:all(output_vector %in% output_vector2)
そして、その逆もしかり。
all(output_vector2 %in% output_vector)
それゆえ、
output_vector
とoutput_vector2
の要素は、違う順番で蓄積されただけです。 この理由は、as.vector()
は列ごとに行列に要素を入力する形で出力するからです。output_matrix
を見てみると、要素を行ごとに欲しいということに気づくでしょう。 解決方法は、output_matrix
を転置することです。転置関数t()
を呼ぶか、 要素を正しい順番で入力するか、いずれかの方法で対応可能です。 最初の解決方法は、もともとのものを次の形で変更する必要があります:output_vector2 <- as.vector(output_matrix)
を、以下へ
output_vector2 <- as.vector(t(output_matrix))
二番目の解決方法は、次のように変える必要があります:
output_matrix[i, j] <- temp_output
を、以下へ。
output_matrix[j, i] <- temp_output
チャレンジ3
gapminder
データを大陸ごとにループし、平均余命が50歳以上かどうかを表示する スクリプトを書きましょう。チャレンジ3の解答
手順1:大陸ベクトルから、確実に全ての唯一無二な値を抜き出せるかを確かめましょう。
gapminder <- read.csv("data/gapminder_data.csv") unique(gapminder$continent)
手順2:これらの大陸のそれぞれにループをし、その
部分集合
データごとに平均余命を出す必要があります。 それは次の形でできます:
- ‘大陸(continent)’ の唯一無二の値のそれぞれについてループする
- 大陸のそれぞれの値ごとに、この部分集合の平均余命を蓄積する一時的な変数を作る
- 計算した平均余命を返し、 出力を表示させる:
for( iContinent in unique(gapminder$continent) ){ tmp <- mean(subset(gapminder, continent==iContinent)$lifeExp) cat("Average Life Expectancy in", iContinent, "is", tmp, "\n") rm(tmp) }
手順3:演習は、平均余命が50歳以上かどうかの結果だけを出力したいというものでした。 ゆえに、
if
条件を、表示させる前につける必要があります。これは、演算された平均余命が基準値以上か、基準値未満かを判別し、結果によって出力を表示させる必要があります。 上から (3) を修正する必要があります:3a. もし計算された平均余命が、ある基準(50歳)未満の場合、大陸と、平均余命は基準未満であるという宣言を、そうでない場合は、大陸と、平均余命は基準値以上であるという宣言を返しなさい:
thresholdValue <- 50 for( iContinent in unique(gapminder$continent) ){ tmp <- mean(subset(gapminder, continent==iContinent)$lifeExp) if(tmp < thresholdValue){ cat("Average Life Expectancy in", iContinent, "is less than", thresholdValue, "\n") } else{ cat("Average Life Expectancy in", iContinent, "is greater than", thresholdValue, "\n") } # end if else condition rm(tmp) } # end for loop
チャレンジ4
チャレンジ3のスクリプトをそれぞれの国とごとにループする形で修正しなさい。 今回は、平均余命は、50歳未満か、50歳以上70歳未満か、70歳以上かを 表示しましょう。
チャレンジ4の解答
チャレンジ3の解答を、
lowerThreshold
とupperThreshold
の2つの基準値を加え、if-else 宣言を拡張する形で修正します:lowerThreshold <- 50 upperThreshold <- 70 for( iCountry in unique(gapminder$country) ){ tmp <- mean(subset(gapminder, country==iCountry)$lifeExp) if(tmp < lowerThreshold){ cat("Average Life Expectancy in", iCountry, "is less than", lowerThreshold, "\n") } else if(tmp > lowerThreshold && tmp < upperThreshold){ cat("Average Life Expectancy in", iCountry, "is between", lowerThreshold, "and", upperThreshold, "\n") } else{ cat("Average Life Expectancy in", iCountry, "is greater than", upperThreshold, "\n") } rm(tmp) }
チャレンジ5 - 上級
gapminder
データセットで、それぞれの国ごとにループするスクリプトを書き、 国が ‘B’ で始まるかどうかをテストし、平均余命が50歳以下の場合、平均余命を 時間ごとの移り変わりで示した線グラフを書きましょう。チャレンジ5の解答
シェルのレッスンで紹介した
grep
コマンドを「B」で始まる国を見つけるために使います。 まず、これをどういうふうにするかを理解しましょう。 シェル節に従って、以下を試してみたいと思うかもしれません:grep("^B", unique(gapminder$country))
でも、このコマンドを演算すると、順序なし因子変数
country
の「B」で始まる要素番号を返します。 値を得るためには、grep
コマンドの、value=TRUE
オプションを加える必要があります:grep("^B", unique(gapminder$country), value=TRUE)
これらの国々をcandidateCountriesと呼ぶ変数に蓄積し、その変数のそれぞれでループするようにしましょう。 そのループの中で、それぞれの国の平均余命を演算子、もし平均余命が50歳未満でったら、平均余命の進展をみるために、
with()
とsubset()
を使い、base-plotを用いてプロットしましょう:thresholdValue <- 50 candidateCountries <- grep("^B", unique(gapminder$country), value=TRUE) for( iCountry in candidateCountries){ tmp <- mean(subset(gapminder, country==iCountry)$lifeExp) if(tmp < thresholdValue){ cat("Average Life Expectancy in", iCountry, "is less than", thresholdValue, "plotting life expectancy graph... \n") with(subset(gapminder, country==iCountry), plot(year,lifeExp, type="o", main = paste("Life Expectancy in", iCountry, "over time"), ylab = "Life Expectancy", xlab = "Year" ) # end plot ) # end with } # end for loop rm(tmp) }``` > {: .solution} {: .challenge}
Key Points
Use
if
andelse
to make choices.Use
for
to repeat operations.