=== 読者が配色を変更したい場合 ===
◎外側の色を変えるには,次の色をクリック
《体験・入門レベル》
== Rの分岐とループ == R version 4.0.3, 4.0.4Patched ----- 最終更新年月日:2021.4.23
この教材は,体験・入門のレベルで,30分から1時間ほどで「そこそこ分かる」ことを目指す.
R 分岐とループ
1. プログラミング言語ごとの特徴
以下においては,複文ブロックの始まりと終わりを表す中括弧(波括弧,ブレース,ブラケット,すなわち { と })のことを,単に中括弧と呼ぶことにする.1.1 C言語やjavascript
C言語やjavascriptでは,if文,forループ,関数定義において,中括弧は一連の記号として正しい順序にあればよく,書く位置(行とカラム)はプログラマーの自由である.ただし,読みやすくするために推奨されるスタイルはある.C言語の教科書を書いたカーニハン&リッチーのスタイルでは,
(1) ブロック開始の中括弧 { をifやforと同じ行に書く
(2) ブロック内の文をタブキーで字下げする (3) ブロック終了の中括弧 } をifやforの先頭位置と同じカラム(横位置)に書く if (条件) { 文1; 文2; } ---------- for (条件) { 文1; 文2; }これに対して,BSD/オールマンのスタイルでは,ブロック開始の中括弧 { をブロック終了の中括弧 } と同じカラム(横位置)に書く.複雑な構造になっているときに,見やすいので,この教材の作者(私)は,ほとんどこのスタイルで書いている. for (条件) { if (条件) { 文1; 文2; } else { 文3; 文4; } } 1.2 Python
Pythonでは,中括弧を使わずに,論理的な関係はタブを使った字下げによって表す.
if 条件1: for 変数1 in 範囲1: if 条件2: 文1 if 条件3: 文2 文3 else: for 変数2 in 範囲2: if 条件4 文4 1.3 R
Rでif文,forループ,関数定義を書くときの中括弧の書き方は,"ほぼ"Cにおけるカーニハン&リッチーのスタイルだと思えばよい.(単文の場合には,中括弧を省略できるが,「色々な場合があります」と覚えるよりも,初めのうちは確実にできる方法だけに単純化した方がよいと考えられる).詳しくは,次の項で述べる.
|
2. Rでのif文とforループの例
Rで分岐を制御するには,if文の他にswitch文もあるが,if文で何でもできるので,以下においてはif文に絞る.2.1 if
Rにおいて,ifによって分岐する場合は,次の書き方にすればよい.
(@) ブロック開始の中括弧 { をifと同じ行に書く
(A) ブロック内の文を定まった流儀でインデント(字下げ)する (B) ブロック終了の中括弧 } をifの先頭位置と同じカラム(横位置)に書く (*) 単文の場合(文が1つの場合)もこれでよい 【要点2.1】 if (条件) { 文 ... }ifの条件による流れの制御が届いている範囲をコンピュータに正確に伝えるために, (@)ブロック開始の中括弧{をifと同じ行に書き,ifによる条件が満たされたときに時効される文を,ブロック終了の中括弧 }までに書く.(ここまではコンピュータのため) 他方で (A)「ブロック内の文をインデント(字下げ)すること」, (B) 「ブロック終了の中括弧}をifの先頭位置と同じカラム(横位置)に書くこと」は,プログラムを書く人・点検する人が「見やすく」=「間違いにくく」するためと考えるとよい. 「Rプログラミングマニュアル」[第2版]/間瀬茂著/数理工学社では,R風のコーディングスタイルとして,「インデントは空白2文字にし,タブや,タブと空白の併用は避ける」とされている.・・・しかし,筆者(私)はC言語やjavascriptのスタイルが身に染みているので,タブと空白の併用はしないが,「インデントはタブで揃えることにしている」ので,悪しからず.・・・Rエディタのタブは空白7文字に設定されていて,習慣の違いにやや戸惑うが,TeraPadなどを用いてHTMLに移すときに,勝手に空白8文字に戻っている.HTML上ではそれが見えている・・・タブは,空白8文字か4文字に変換されることが多いと思う.
以下のプログラムは複数行に渡るので,Rのコンソール画面から1行ずつ実行するよりも,「R エディタにおいて,プログラムとして書き」「メニューから,編集→すべて実行を選ぶ」がやりやすい.
【例2.1】 n = 7 if (n %% 2 == 1) { print("奇数です") }〇 Rにおいて,整数a, b(≒0)に対して「整数割り算 a÷b=q・・・r」の商qと余りrは,
商:a %/% b
によって求められる.この例では,7を2で割ると1余るから,7 %% 2は1となる.そこでifの条件が満たされて,"奇数です"と表示される.余り:a %% b 〇 ifと( ), ( )とブロック開始の中括弧{の間の半角スペースは,あってもなくてもRとして働くが,通常は書く. |
2.2 if, else
(@) ifブロックの終わりの中括弧}と同じ行にelseとそのブロックの始まりの中括弧{も書く
(A) elseブロック内の文をインデント(字下げ)する (B) elseブロック終了の中括弧 }をifの先頭位置と同じカラム(横位置)に書く (*) 単文の場合(複文でない場合)もこれでよい 【要点2.2】 if (条件) { 文 ... } else { 文 ... } 【例2.2】 n = 8 if (n %% 2 == 1) { print("奇数です") } else { print("偶数です") }〇 この例では,8を2で割ると余りが0になるから,8 %% 2は0となる.そこでifの条件は満たされないので,elseブロックに行き,"偶数です"と表示される. 〇 ifブロックの終わりの中括弧}とelseの間,elseとブロック開始の中括弧{の間の半角スペースは,あってもなくてもよいが,通常は書く. |
2.3 if, else if
まず,次の2つの書き方を比べてください.
「Tree型」の方が「論理的な無駄が少ない」「階層が浅い」が ⇒ ▼▼
「鰻のふんどし型」「帯型」の方が読み書きに楽 ⇒ ◎◎
⇒ プログラムを書く人・点検する人が「見やすく」=「間違いにくく」することを優先させて,以下のような「鰻のふんどし型」「帯型」に書くとよい.(ベタベタの俗語でごめんね!) 【要点2.3】 鰻のふんどし型=帯型 if (条件) { 文 ... } else if (条件) { 文 ... } else if (条件) { 文 ... } else { 文 ... } 【例2.3.1】 n = 7 # 12,6 if (n >= 10) { print("10以上") } else { if (n %% 2 == 0) { print("2の倍数") } else { if (n %% 3 == 0) { print("3の倍数") } else { print("1または素数") } } }• nに正の整数を代入して調べるものとする. • この例では,10以下の整数は,3までの素因数で割り切れなければ素数(または1)であると言える. • 7は素数になる. • else ifで連結した場合分けは「排反」になっていて,2つ以上の条件に合致するかどうかをテストできないので,例えばn=6のときは"2で割り切れる"と表示されて,3で割り切れるかどうか調べずに終わる.上記の分け方では,前の段階で合致すれば,後の段階のテストはしていない. • elseが付いている以上,「重複なく」分類され,最後のelseで「もれなく」分類される.要するに,この形に書けば,どれか1つだけに当てはまるようになる. • Visual BasicやPythonのようにelseifとかelifという1つの制御文があるわけではなく,C言語やJavascriftと同様にelseの影響下にifが書かれているいるので,次のように記述すれば,論理的に同じプログラムになる.しかし,何と言っても【例2.3.1】の書き方が単純明快で,点検もしやすいと言える. 【例2.3.2】 n = 7 # 12,6,1 if ( n >= 10) { print("10以上") } else { if (n %% 2 == 0) { print("2の倍数") } else { if (n %% 3 == 0) { print("3の倍数") } else { print("1または素数") } } }この他に,switchを用いて値によって分岐する方法もあるが,ほとんどのプログラムは上記のif, else, else ifで書けるので,このページでは触れない. |
2.4 for ループ
繰り返し[ループ]を書くには,次の形が基本になる.
for (変数 in ベクトル[または数列])
Rのforループでは,項数の決まったベクトル(数列と考えてもよい)の要素であることを指定する.
[小話]
RやPythonのforループの条件の部分は,(要素 in オブジェクト)の形に書くので,オブジェクトの要素数が有限個である限り,無限循環にはならない. これに対して,C言語やjavascriptで for (初期条件;継続条件;更新条件)のうちで,継続条件が遂に達成されない場合,無限循環になることがある.・・・このミスが含まれるプログラムは,ジョークを込めてforever(永遠に)と呼ばれるが,Rのforはforeverになる心配はない. 後で述べるwhileの方は,while(継続条件)が,達成されなければ無限循環になる・・・ただ英語のジョークとしては,whileverは(永遠に)ではなく(いつでも)になる・・・お後がよろしいようで. (1) ベクトルの部分の書き方
m:n・・・m<nのとき,mからnまで1ずつ増やした数から成るベクトルを表す(m>nのときは1ずつ減らす)【例】1:5 → c(1,2,3,4,5)と同じ 【例2.4.1】 s = 0 for (n in 1:5) { s = s + n } print(s)n = 1, 2, 3, 4, 5と1つずつ増える.この数nを合計を入れる変数sに足して行く. s = 1, 3, 6, 10, 15となって,15が出力される. seq(a, b, length=c)・・・初項a,末項b,項数cの等差数列 【例】seq(2, 10, length=5) → c(2, 4, 6, 8, 10)と同じ 【例2.4.2】 s = 0 for (n in seq(2, 10, length=5)) { s = s + n } print(s)n = 2, 4, 6, 8, 10と2ずつ増える.この数nを合計を入れる変数sに足して行く. s = 2, 6, 12, 20, 30となって,30が出力される. seq(a, b, by=c)・・・初項a,公差cの等差数列(b以下のもの) 【例】seq(1, 10, by=2) → c(1, 3, 5, 7, 9)と同じ ※10は登場しない 【例2.4.3】 s = 0 for (n in seq(1, 10, by=2)) { s = s + n } print(s)n = 1, 3, 5, 7, 9と2ずつ増える.この数nを合計を入れる変数sに足して行く. s = 1, 4, 9, 16, 25となって,25が出力される. (2) for (x in 集合) が表す内容
例えば,r_ex1.csvという名のcsvファイルの内容が,次の5人の数学と英語の得点であるとき,data1<-read.csv("r_ex1.csv")により,csvファイルはdata1というデータフレームに読み出される. 氏名,数学,英語 徳川家康,50,65 織田信長,32,63 平清盛,52,58 源頼朝,28,55 武田信玄,46,54このとき,(x in data1$数学)は,data1の中の数学の列を順にたどったものになる. 【例2.4.4】 data1<-read.csv("r_ex1.csv") for (x in data1$数学) { print(x) ⇒ [1] 50 [1] 32 [1] 52 [1] 28 [1] 46 }もっと単純に,(x in data1)とすると,data1のすべての要素を順にたどったものになる.一般に,行列のように2次元的に並んでいるデータであっても,(x in データ)により,データのすべての要素を順にたどったものになる.
【例2.4.4】
data1<-read.csv("r_ex1.csv")
for (x in data1) {
print(x)
}
⇒
[1] "徳川家康" "織田信長" "平清盛" "源頼朝" "武田信玄"
[1] 50 32 52 28 46
[1] 65 63 58 55 54
}
次の例では,上記5人の英語の得点の平均が出力される.
【例2.4.5】 data1<-read.csv("r_ex1.csv") s = 0 n = 0 for (x in data1$英語) { s = s + x n = n + 1 } print(s / n) #・・・@ print(mean(data1$英語)) #・・・A ⇒ [1] 59 #・・・@は合計を人数で割ったもの [1] 59 #・・・Aはループに依らない関数で求めたもの } |
2.5 while ループ
while (条件式)
whileでは,右図のように,ブロック内に書かれた文を実行するまでに条件が満たされているか否かをテストして,条件が成り立つ場合にだけ文を実行する.条件が成り立たない場合は,ループを抜け出て次の流れに入る.次の例2.4.6では,whileループの中の文print(n)は一度も実行されない. 【例2.5.1】 n = 5 while (n < 3) { print(n) }次の例2.4.7では,n = 1, 2の場合までsに足され,n = 3になると n < 3 が成り立たなくなるから,ブロック内の文を実行せずにループを抜け出る.したがって,sの値は0+1+2=3となり,これが表示される. 【例2.5.2】 n = 1 s = 0 while (n < 3) { s = s+ n n = n + 1 } print(s)次の例2.4.8では,whileの条件が成り立つから,ブロック内の文s = s + nが実行されるが,条件n < 3に書かれている変数nが変更されないので,無限循環になる. このプログラムは実行しない方がよいが,何らかの不注意でRのプログラムが無限循環になってしまった場合の対処法
【無限循環の暴走を止めるには】
@ Escキーを押す A それでもダメなときは,マウスでRコンソールをクリックしてからEscキーを押す ※Rエディタやグラフィック画面がアクティブになっていると,Escキーを押しても止まらないことがある.その場合は,マウスでRコンソールをクリックして,アクティブ・ウインドウをRコンソールに戻してから,Escキーを押すとよい. 【例2.5.3】 n = 1 s = 0 while (n < 3) { s = s + n } print(s) |
2.6 break, next
(1) forループやwhileループの中で,何らかの条件が満たされたら,残りのループを行わずにループを抜け出るにはbreak文を書く.(2) 何らかの条件が満たされたら,ループのその回の残りを行わずに,ループの次の回の先頭から行うにはnext文を書く. (3) なお,forやwhileのループが入れ子(ネスト)になっているとき,1つのbreakによって抜け出せるのは「そのループだけ」であって,外側のループには影響しない. (4) また,ifやelseが何重に入れ子(ネスト)になっていても,breakで抜け出すことはない.
関数cat( )は,「連結と出力」Concatenate and Printの省略で,「幾つかの表現を連結して出力(print)する」ことに使える.・・・もちろん,猫とは関係がない.
(1) break次の例2.6.1では,x>2のとき,"B:..."の式が表示されて,breakによりループを抜け出る.したがって,x=3のとき,B:...が表示され,C:...は表示されず,ループを抜け出て"D:.."が表示される.【例2.6.1】 for (x in 1:5) { cat("A: x=", x, "\n") if (x > 2) { cat("B: x=", x, "\n") break } cat("C: x=", x, "\n") } cat("D: x=", x, "\n") ------------------ (結果) A: x= 1 C: x= 1 A: x= 2 C: x= 2 A: x= 3 B: x= 3 D: x= 3 (2) next次の例2.6.2では,x = 1, 2のとき,if条件は満たされないから,"A:..."の式は表示されず,"B:..."の式が表示される.x = 3, 4, 5のとき,if条件が満たされるから,"A:..."の式が表示され,"B:...."以下は実行されずに,先頭に戻る.x = 5のときは,"A:..."を表示の後,ループを抜け出るから,"C:.."が表示される.【例2.6.2】 for (x in 1:5) { if (x > 2) { cat("A: x=", x, "\n") next } cat("B: x=", x, "\n") } cat("C: x=", x, "\n") ------------------ (結果) B: x= 1 B: x= 2 A: x= 3 A: x= 4 A: x= 5 C: x= 5
* breakとnextの違い *
1つのxについて5つずつの処理があって,x=3の場合の2つ目が終わった所に分岐があって,breakまたはnextによって流れの制御が行われる場合,各々の場合の行先は右図のようになる.すなわち,breakの場合はループを抜け出るのに対して,nextの場合はループ変数xの次の値の先頭に進む. |
(3) ループの入れ子とbreak次の例2.6.3.1では,yが2になると内側のループを抜け出るので,yは3にはならない.しかし,内側のループを抜けても,外側のループで続きを行うから,xが3になるまで行われる.なお,"B:..."の式が表示されるのは,yが2の場合だけである. 【例2.6.3.1】 for (x in 1:3) { for (y in 1:3) { cat("A: x=", x, ",y=",y,"\n") if (y == 2) { cat("B: x=", x, ",y=",y,"\n") break } cat("C: x=", x, ",y=",y,"\n") } cat("D: x=", x, ",y=",y,"\n") } cat("E: x=", x, ",y=",y,"\n") ------------------ (結果) A: x= 1 ,y= 1 C: x= 1 ,y= 1 A: x= 1 ,y= 2 B: x= 1 ,y= 2 D: x= 1 ,y= 2 A: x= 2 ,y= 1 C: x= 2 ,y= 1 A: x= 2 ,y= 2 B: x= 2 ,y= 2 D: x= 2 ,y= 2 A: x= 3 ,y= 1 C: x= 3 ,y= 1 A: x= 3 ,y= 2 B: x= 3 ,y= 2 D: x= 3 ,y= 2 E: x= 3 ,y= 2次の例2.6.3.2では,内側のループを抜け出すときに外側のループを抜け出すための値markも変更する.これにより,2重になっているループが抜け出せる. 【例2.6.3.2】 mark = 0 for (x in 1:3) { for (y in 1:3) { cat("A: x=", x, ",y=",y,"\n") if (y == 2) { cat("B: x=", x, ",y=",y,"\n") mark = 1 break } cat("C: x=", x, ",y=",y,"\n") } cat("D: x=", x, ",y=",y,"\n") if (mark != 0) { break } } cat("E: x=", x, ",y=",y,"\n") ------------------ (結果) A: x= 1 ,y= 1 C: x= 1 ,y= 1 A: x= 1 ,y= 2 B: x= 1 ,y= 2 D: x= 1 ,y= 2 E: x= 1 ,y= 2 |
次の例2.6.3.3では,whileループについても,breakによって内側のループだけを抜け出ることが分かる.
【例2.6.3.3】 x = 1 y = 1 while (x <= 3) { while (y <= 3) { cat("A: x=", x, ",y=",y,"\n") if (y == 2) { cat("B: x=", x, ",y=",y,"\n") break } cat("C: x=", x, ",y=",y,"\n") y = y + 1 } cat("D: x=", x, ",y=",y,"\n") x = x + 1 } ------------------ (結果) A: x= 1 ,y= 1 C: x= 1 ,y= 1 A: x= 1 ,y= 2 B: x= 1 ,y= 2 D: x= 1 ,y= 2 A: x= 2 ,y= 2 B: x= 2 ,y= 2 D: x= 2 ,y= 2 A: x= 3 ,y= 2 B: x= 3 ,y= 2 D: x= 3 ,y= 2 |
2.7 repeat
無限循環を作るにはrepeatが使える.ただし,repeatを使う場合には,何らかの条件が満たされたら,breakによってループを抜け出す仕組みになっていなければならない.ある目的を達成するために,for,while,repeatのいずれが使いやすいか決まっているわけではないが,プログラムを書く人の内心のロジックとして使いやすいものを選べばよいと思う・・・ただし,無限循環にならないように気を付けなければならない. 原点を中心とする半径1の円の面積は,πであるから,その内の第1象限(x>0, y>0)にある部分の面積は そこで,0≦x≦1, 0≦y≦1の範囲で点(x, y)をランダムに発生させた場合,点(x, y)が円の内部にある確率は,になる. これを利用して,解析(計算)や幾何(図形)の結果を使わずに,確率を使って円周率πを求める方法は「モンテカルロ法」と呼ばれる.(モンテカルロはモナコの地区の名前.国営のカジノがある) 次の例2.7.1では,モンテカルロ法を用いて,円周率を求め,誤差が0.0000001=10−7以下になったら,breakで抜け出す. モンテカルロ法は,解析や幾何の定理を使わなくても円周率が計算できるというところに大きな意義があり,実際の計算の精度(収束の速さ)は速くない.実測では,PCが32ビット機の場合,誤差を0.0000000001=10−10以下とすると,なかなか収束せずに無限循環"風"になってしまって,終われないことがある(64ビット機の場合は,もう少し高い精度まで行けるかも?) 【例2.7.1】 N = 0 n = 0 repeat { x = runif(1) y = runif(1) N = N + 1 if (x^2 + y^2 <= 1) { n = n + 1 } ratio = n / N if (abs(ratio * 4 - pi) <= 0.0000001) { print(ratio * 4) break } } ------------------ (結果) [1] 3.141593runif(n, min, max)は,最小値をminとし,最大値をmaxとする区間で「一様分布」する乱数を発生する. nは個数で必須.min=,max=が省略された場合は,min=0, max=1となる. この例のようにruinf(1)とした場合は,0以上1以下の乱数が1個返される. 次の例2.7.2では,2桁(10以上99以下)の3数から成るピタゴラス数 を求めて,解が見つかればbreakによってループを抜け出す.実際には,この範囲の中に解は複数個ある(48 14 50, 20 21 29, 12 35 37など)が解がない場合は無限循環になるので,この問題なら.forを用いて総当たりする方が安全かもしれない. 【例2.7.2】 repeat { x = floor(runif(1, 10, 99)) y = floor(runif(1, 10, 99)) z = floor(runif(1, 10, 99)) if (x^2 + y^2 == z^2) { cat(x,y,z) break } } ------------------ (結果) 11 60 61次の例2.7.3では,ディオファントス方程式 (の正の整数解)を求めて,解が見つかればbreakによってループを抜け出す. 【例2.7.3】 repeat { x = floor(runif(1, 1, 9)) y = floor(runif(1, 1, 9)) z = floor(runif(1, 1, 9)) w = floor(runif(1, 1, 9)) if (x^3 + y^3 + z^3 == w^3) { cat(x,y,z,w) break } } ------------------ (結果) 5 3 4 6なお,上記の例2.7.3は次の例2.7.2と全く同じ構造で同じ結果を返す. 【例2.7.3】 while (TRUE) { x = floor(runif(1, 1, 9)) y = floor(runif(1, 1, 9)) z = floor(runif(1, 1, 9)) w = floor(runif(1, 1, 9)) if (x^3 + y^3 + z^3 == w^3) { cat(x,y,z,w) break } } ------------------ (結果) 5 3 4 6なお,同じことは次のようにforループを使っても書けるが,ネストが深くなる. 【例2.7.4】 mark = 0 for (x in 1:9) { for (y in 1:9) { for (z in 1:9) { for(w in 1:9) { if (x^3 + y^3 + z^3 == w^3) { cat(x,y,z,w) mark = 1 break } } if (mark != 0) break } if (mark != 0) break } if (mark != 0) break } ------------------ (結果) 1 6 8 9 |