Rを使ったプログラミング演習2: シミュレーション †はじめに †Rでシミュレーションの授業を始めたころ、受講生から次のような質問があった。 「シミュレーションのプログラミングって、これから生物学を学ぶのに、どのくらい必要なんですか?」 これまでプログラミングなどしたことの無い人にとって、とても素直な疑問だろう。私からの答えとしては、 シミュレーションそのものが直接必要になるような分野もあれば、そうでない分野もあります。 プログラミングは、生物学における発展的な情報処理として、とても重要なものだと思います。 将来、数理生物学、理論集団遺伝学、進化生態学、バイオインフォマティクスを志す人には、 必須技術といっても良いでしょう。 ということで、今回は、Rを使ったシミュレーションに挑戦してみよう。 第11回授業の獲得目標: †
おさらい0:演習・グループワーク プログラミングは手順書き! †前の授業で、「プログラミングは数学というよりも、コンピュータにことばで手順を伝えることだ」という説明をした。 お財布の中に500円入っているから、これを持って買い物に行ってね。 この通りをまっすぐに進むとお店が3軒あるから、 最初のお店で、100円のゴーヤを1本もらって、100円を渡すんだよ。 次のお店で100円のお豆腐を一丁もらって、100円を渡すんだよ。 次のお店で100g 100円の豚肉を、200gもらって、200円を渡すんだよ。 残ったお金はいくらだった?(あとでお菓子をかってもいいよ。) このお買い物の手順を、先週学んだ「繰り返し・代入・条件分岐」 を使っておおざっぱに表現してみると、どうなるだろうか?
このプログラムの中に含まれている「繰り返し・代入・条件分岐」 の働きを、十分に理解しておこう。 おさらい1: プログラミングの基本: 繰り返し・代入・条件分岐 †次のそれぞれのプログラムの_部分に何を入れるべきかを選択肢から選びなさい。また、Rを使って実行してみよう。 繰り返し †
代入 †
条件分岐 †
プログラミングTips †・1行に1つの命令を書いたら、必ず改行 ・{ } のそれぞれの後で改行すると見やすい(このページの例を参照) おさらい3:ユーザー定義関数 †「ユーザー定義関数」というのは、関数を定義する関数function()を使って、自分で作成したプログラムに名前を付けるようなものだった。オサライのため、1つ演習をしておこう。
ransu(50) などと入力すれば、0.5より小さい乱数がいくつか表示される おさらい4. 授業で使ったRの基本関数 †→授業/H20/情報処理/R関数一覧 これまでに学んだRのいろんな関数を一覧できる。 Rを用いたシミュレーション †これまでに学んだ繰り返し・代入・条件分岐の3つの命令それぞれは単純だが、組み合わせれば、かなり複雑で、研究に役立ちそうなことでも表現できる。そこで、今回は、これらの命令を組み合わせて、Rを用いたシミュレーションに挑戦してみよう。 円周率(π)を「繰り返し」・「乱数」・「条件分岐」で求める †
正方形の面積は 1 扇形の面積は π/4 この図形の上に、砂粒をばらまいてみる。 砂粒が図形の上にランダムに散らばるとすると、砂粒をすごく沢山まいて数を数えれば、 正方形の中の砂粒の数(n) : 扇形の中砂粒の数(m) = 正方形の面積 :扇形の面積 = 1 : π/4 になると考えられる。つまり n : m = 1 : π/4 だから、 π = 4m/n もしあなたが、砂粒をたくさんランダムにまいて、正方形の中の砂粒と扇形の中の砂粒の数を数えることができれば、πの値がわかるということになる。自分で実際に実験することを想像すると、恐ろしく大変だが、そういう面倒な実験はコンピュータにやらせてしまえばいい! シミュレーションの考え方 †
砂を1粒まくことは、無作為に(ランダムに)図形の上に点を打つことと同じことだ。先週学んだ、runif() という乱数を発生させる関数を使えば、図形の上に1つの点を打つことは、2つの乱数を発生させることで表現できるだろう。
sqrt(0.230^2+0.782^2) < 1 となり、ランダムに発生させた(図形の上に偶然落ちた)1つの点は、扇形の中だったということになる。 point=runif(2) #この命令で、0-1の範囲の乱数を2つ発生させ、pointというベクトルに入れる このとき、pointに入ったそれぞれの値を指定するには、 point[1] point[2] のようにカギ括弧[]を使う。 例: []の使い方を、試してみよう > x=runif(2) > x > x[1] > x[2]
以上のように、これまでに学習した、繰り返し、条件分岐、代入で、図形の上に砂粒を1000個まく実験が表現できた。πの値は砂粒の個数を4倍して、全体の個数で割ったものだから、以上をまとめると次のようになる。 kaisu=0; #個数の初期値を0にする for (i in 1:1000) { point=____(2) ____=sqrt(point[1]^2+point[2]^2) if(distance<1){ kaisu=kaisu+1 } } answer=4*____/1000 print(answer) さらにユーザー定義関数:function()を使って、好きな回数繰り返せる関数を定義してみよう simpai=function(x){ kaisu=0; #個数の初期値を0にする for (i in 1:x) { point=runif(2) distance=sqrt(point[1]^2+point[2]^2) if(distance<1){ kaisu=kaisu+1 } } answer=4*kaisu/x print(answer) } > simpai(10) [1] 2.8 > simpai(100) [1] 3.16 > simpai(1000) [1] 3.156 > simpai(10000) [1] 3.1624 > simpai(100000) [1] 3.15652 > simpai(1000000) #ここから先は時間がかかるので、授業中はやらない方がいい [1] 3.13994 > simpai(10000000) #ここから先は時間がかかるので、授業中はやらない方がいい [1] 3.140556 せっかくだから、点をうつところを図で表現してみよう †plot()関数でylimというオプションを使うと、グラフの軸の範囲を指定できる(詳しくはマニュアルを参照) simpai=function(x){ kaisu=0; #回数の初期値を0にする for (i in 1:x) { point=runif(2) par(new=T) #複数のグラフを重ね合わせる plot(point[1], point[2], ylim=c(0,1), xlim=c(0,1)) #0-1の範囲で乱数の値をプロット distance=sqrt(point[1]^2+point[2]^2) if(distance<1){ kaisu=kaisu+1 } } answer=4*kaisu/x print(answer) } 発展演習: 円周率の計算を乱数を使わずに行うプログラム †
simpai=function(x){ kaisu=0; #個数の初期値を0にする for (_ in 1:_) { for (_ in 1:_) { point=c(___,___) distance=sqrt(point[1]^2+point[2]^2) if(distance<1){ kaisu=kaisu+1 } } } answer=4*kaisu/x^2 print(answer) } 遺伝的浮動のシミュレーションに挑戦する! †次に、生物学科の学生なら、大学入試にそなえて必ず勉強した遺伝的浮動のシミュレーションをやってみよう。 大きさの決まった集団で起きる、配偶子のランダムサンプリングによる遺伝子頻度の変動 のことだ。例えば、集団中の2つの対立遺伝子を、青玉と赤玉で表すと、袋(配偶子の集団: 配偶子の数はものすごく多く、青と赤の比率はそれぞれ50%)から取り出される青玉・赤玉の割合(集団中の遺伝子頻度)には、下の図に示したような変動が生じる(下の例で、青玉の遺伝子頻度は、0.8, 0.7, 1.0, と変動している)。もし、袋から取り出されたのが青玉ばっかり(あるいは、赤玉ばっかり)だと、それ以降、何度繰り返しても変化は生じない(下の例だと、「集団は青玉に固定した」などという)。 出席確認演習+グループワーク: 0か1に固定するまでの世代数 †まず、上のような袋と玉をつかった実験を手作業でやった場合、取り出した玉が赤か青かどちらか1色になってしまうのは、何回目だろうか?下の条件それぞれで、グループで相談してみよう。また、相談後に自分が考えてた固定までの回数を、授業Moodleページの出席確認演習に記入しよう。
遺伝的浮動のシミュレーションの学び方 †では、上で説明した実験を、これまでに習った繰り返し、代入、条件分岐というプログラミングの基本技を使って表現してみよう。 演習・グループワーク: 遺伝的浮動のシミュレーション: 完成版(ただし、一部、抜け落ち) †下のプログラムは遺伝的浮動をシミュレートするRのプログラムだが、一部、抜け落ちている。また、このプログラムでは、頭の中での置き換えを簡単にするために、オブジェクト(変数)名が、日本語で表示されている。
これらの値を与えて実行すれば、ランダムに選ばれた遺伝子が、次世代の集団の遺伝子頻度にどのように影響するかを、シミュレートする観察集団数の数だけ、色つきの折れ線グラフで表示することができる。
drift = function(観察集団数,観察世代数,玉の総数,青玉初期数){ 結果 = c() #結果を入れる空ベクトルを準備 for( i in 1 : ________ ){ 青玉の数 = ________ #青玉の数は観察集団が代わる度に初期値に戻す for( j in 1 : ________ ){ 結果 = append(結果, 青玉の数 / 玉の総数) #青玉の頻度を「結果」ベクトルに入れる #最初の繰り返しでは青玉の初期頻度が入る 青玉計数 = 0 #袋の中の青玉の数を青玉計数に入れる。初期値は0 for( k in 1 : ________ ){ if (runif(1) < 青玉の数 / 玉の総数 ){ #「青玉の数/玉の総数」は青玉の頻度 青玉計数 = 青玉計数 + 1 #青玉計数の値を1つ増やす(青玉を数えるということ) } } 青玉の数 = 青玉計数 #次の世代の袋の中の「青玉の数」に、今数えた「青玉計数」を入れる } } 結果の表 = matrix(結果, nrow = 観察世代数, ncol = 観察集団数) print(結果の表) matplot(結果の表, type="l", ylim=c(0,1)) } 遺伝的浮動のシミュレーションの解説 †上の演習問題の答えは、以下の囲みのようになる。以下、画像を使って、このシミュレーションプログラムを、「繰り返し」、「条件分岐」、「代入」のそれぞれについて見て行こう。 drift = function(観察集団数,観察世代数,玉の総数,青玉初期数){ 結果 = c() #結果を入れる空ベクトルを準備 for( i in 1 : 観察集団数 ){ 青玉の数 = 青玉初期数 #青玉の数は観察集団が代わる度に元に戻す for( j in 1 : 観察世代数 ){ 結果 = append(結果, 青玉の数 / 玉の総数) #青玉の頻度を「結果」ベクトルに入れる #最初の繰り返しでは青玉の初期頻度が入る 青玉計数 = 0 #袋の中の青玉の数を青玉計数に入れる。初期値は0 for( k in 1 : 玉の総数 ){ if (runif(1) < 青玉の数 / 玉の総数 ){ #「青玉の数/玉の総数」は青玉の頻度 青玉計数 = 青玉計数 + 1 #青玉計数の値を1つ増やす(青玉を数えるということ) } } 青玉の数 = 青玉計数 #次の世代の袋の中の「青玉の数」に、今数えた「青玉計数」を入れる } } 結果の表 = matrix(結果, nrow = 観察世代数, ncol = 観察集団数) print(結果の表) matplot(結果の表, type="l", ylim=c(0,1)) }
第11回授業の課題 †
|