前回から使いはじめたRというソフトウェア。コマンドを入力するのはちょと難しいけど、エクセルみたいにマウスを使って面倒な操作をしなくても、一瞬にして統計解析をやってくれるし、グラフも簡単に描けてしまう優れもの。これなら、レポートや卒論のために、何度も解析をやりなおして、グラフを描き直すという作業も面倒ではないだろう。 初めて使ったソフトなのに、受講生の皆さんは、すでにRの操作には慣れてきているし(全員、課題ができていた)、興味も感じているよう。そこで、今回はRを使ったプログラミングに挑戦する。
プログラミングっていったい何をすることだろうか?IT用語辞典で調べてみても適当な定義は載っていない。
唯一近いところでは、「プログラミング言語」というのがある。IT用語辞典の説明文からプログラミングに関わるところを抜き出して、「プログラミング」の説明文を作ってみると、
つまり、プログラミングとは、簡単に言うと、「言葉を使ってコンピュータが理解できる命令を作ること」。 今日使うRは統計解析ソフトでもあるけれど、インタプリタ型のプログラミング言語でもある。
「Wordを立ち上げて文章を作成し、印刷する」とか、「Excelで家計簿を管理する」とかは、市販のソフトウェアを使った作業。いずれも、非常に高度で複雑な命令が、使用者からコンピュータに向かって発せられている。例えば、
・マウスを動かしてマウスカーソルを動かし、
・メニューをクリックして印刷を選択して、
・ファイルを印刷する
なんていうのは、かなり複雑な「命令」の集まりだ。しかし、多くの場合、我々がコンピュータで行っている作業の多くは、「命令」を意識しなくても、「カーソルを動かす」とか「クリックする」など、利用者が理解しやすく・使いやすいように設計されている。。
でも、本当に市販のソフトウェアを使うだけでいいのだろうか?良くないと点として次のようなことに思い当たる:
1. 自分の目的にあったソフトウェアがいつも存在するというわけではない~ 2. 目的の数だけ、異なるソフトの使い方を覚えなければならない
特に1番目は大きな問題かもしれない。生物学の研究で、何かの処理をしたいと考えたとき、誰かがソフトウェアを作ってくれていないと目的が達成できないということになってしまう。例えば、今日の授業で話に出てくる「遺伝的浮動」の解析ソフトなんていうのは、パソコンショップに行っても売っていない。
そこで、この授業では次の3つを目的としてプログラミングを勉強する。
実を言うと、生物学の研究者でも、プログラミングなどを経験したことの無い人は大勢いる。今や、DNAシーケンスの決定や整列、系統樹作成にだって、専用のソフトウェアがある。。。しかし、そういうソフトウェアも、多くの場合は研究者自身が作成したものだ(学生が作ったものもたくさんある)。この授業では、頭の柔らかい1年生のうちにプログラミングについての動機付けを行うことで、このクラスの中から将来は新しいソフトウェアを開発できる人が出ることも期待している。。
今日の授業で出てくるRの関数(命令とかコマンドとも言う)の一覧表。
演算子 | 意味 | 使用例 | 追加 |
+ | 足し算 | > 4+3 [1] 7 | 編集 |
- | 引き算 | > 4-3 [1] 1 | 編集 |
* | 掛け算 | > 4*3 [1] 12 | 編集 |
/ | 割り算 | > 4/3 [1] 1.333333 | 編集 |
^ | 累乗 | > 4^3 [1] 64 | 編集 |
%/% | 整数商 | > 7%/%3 [1] 2 | 編集 |
%% | 剰余 | > 7%%3 [1] 1 | 編集 |
> LETTERS [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" [22] "V" "W" "X" "Y" "Z" > letters [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" [22] "v" "w" "x" "y" "z" > month.abb [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec" > month.name [1] "January" "February" "March" "April" "May" "June" "July" [8] "August" "September" "October" "November" "December" > pi [1] 3.141593
Rの関数 | 意味 | 使用例 | 追加 |
c() | ベクトルの作成 | x = c(1, 2, 3, 4) | 編集 |
mean() | ベクトルの平均 | mean(1, 2, 3, 4, 5) | 編集 |
scan() | コピー・ペーストや手入力でデータをベクトルとして読み込む。 | x=scan() | 編集 |
hist() | ヒストグラムを表示 | x=c(1,1, 3, 1, 5, 3, 4, 4); hist(x) | 編集 |
summary() | 要約統計量の表示 | x=c(1,1, 3, 1, 5, 3, 4, 4); summary(x) | 編集 |
plot() | 散布図の表示 | x=c(1,1, 3, 1, 5, 3, 4, 4); plot(x) | 編集 |
print() | オブジェクトを表示 | x=c(1,1, 3, 1, 5, 3, 4, 4); print(x) | 編集 |
for(ループ変数 in リスト) {式} | ループ変数をリストの内容に従って、1つずつ変化させる。変化の1回ごとに、{ }の式を実行。 | for(i in 1:10) { print(i) } #注:1:10は 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 を表す | 編集 |
matrix(ベクトル, 行数, 列数) | ベクトルを行列に変換して表示 | matrix(1:6, nrow=2, ncol=3) | 編集 |
if (条件式) {式1} else {式2} | 条件式が真の時は 式1を実行、偽のときは式2を実行 | if (runif(1) < 0.5) { print("0.5より小さい")} else { print("0.5以上")} | 編集 |
runif() | 0-1の乱数を指定した個数発生 | runif(5) | 編集 |
function(引数1, 引数2,....){式 } | 自分で関数を定義する | menseki=function(r){r*r*pi} | 編集 |
plot.new() | 図を新しく書き直すという関数 | 編集 |
「Hello World!」だなんて、誰が使い始めたのかは知らないが、多くのプログラミング言語の教科書で、最初に出てくるのがこのプログラムだ(プログラムを勉強したことのある人なら、誰でも知っている)。では、早速やってみよう。Rを立ち上げて、次のように入力。
print("Hello World!")
うまくご挨拶できただろうか?
ここでやったのは、print()という関数<注:命令とほとんど同じ意味>を使って"Hello World!"と画面に表示させただけだが、これも
画面にHello Worldと表示させる
ということを目的とした立派なプログラムだ。
「えっ?それなら、Rの最初の授業でやったオブジェクトの内容を画面に出すのもプログラミング?」と聞かれるかもしれないが、その通り。次の囲みの中を全てコピーして、Rのコンソールにペーストしてみよう。
x=c(1,2,3,4,5,6,7,8,9,10) sum(x)
上でやったプログラミングは、
画面に1から10までの整数の合計を表示させる
というもの。先週やったRを使った操作も、何らかの処理の結果を画面に表示させるプログラミングだったわけだ。なんとなく、プログラミングが身近になっただろうか?
上でやった2つのプログラミングは、見比べてみると違いがある。"Hello World!"の方は1行だけの命令だが、sumの方は2行になっている。プログラムは通常、いろんな処理を組み合わせて作るので、複数行になることが多い。
では3行からなるプログラムを作ってみよう。
q=c(10,15,20,25,30,35,40,45,50,55,60,65,70,80,100) y=c(2,3,4,5,6,7,8,9,10,11,10,11,9,10,12) plot(log10(q^2), log10(y))
このプログラムは、
2つの数値ベクトルについて計算を行って、対数グラフを描く
というものだ。使ったデータは、昨年の生物学科1年生がコドラート調査でとったデータで、面積が広くなると種数がどう増えるかを示したものだ。
授業では一つ一つ解説するが、復習課題をやるときとか、自分で新たな解析に挑戦するときは、どういう命令があるかが書かれた説明書が必要になる。前回も紹介したが、
> ?plot > example(plot)という風に、?の後に関数名を打てば、説明や使用例が英語で表示される。説明文の一番最後にはExample(使用例)が載っているので、けっこう参考になる。
上の例で、紹介したプログラムは、個々の命令を順番に並べて、一度にペーストしただけだった。こんなことなら、Rに命令を1行ずつ与えるのと、ぜんぜん変わらない。そこで、もっとプログラムらしい命令をRに与えてみよう。それは、繰り返しと代入と条件分岐というもので、プログラミングの基本中の基本。
人が不得意でコンピュータが得意なことの一つは、単純な繰り返し作業を際限なく(文句も言わずに)行うことだろう。つまり、プログラミングの一つの目的は、面倒な繰り返し計算をコンピュータにやらせることだと言っても言い過ぎではない。では、何回繰り返すかという命令をどうやってコンピュータに与えるかというと、 for という命令を使う。
for(i in 1:10) { print(i) }この命令は、
{ } で囲まれた部分の命令を10回繰り返しなさいというものだ。
{ } の中は何をする命令か分かるだろうか?では、上の囲みの中をコピーして、Rのコンソールにペーストしよう。
for と (i in 1:10) と { print(i) } for は これから繰り返し命令が始まるよということを示す (i in 1:10) は 繰り返しの回数が10回であることを示す また、iは1回の繰り返しのたびに1ずつ、10まで増加する もし、50回繰り返したかったら (i in 1:50) と書く iという文字の代わりに、jでも、kaisuでも、構わない { print(i) } は 繰り返しの度に{ }の中を実行せよという命令で、 print(i)でiという変数の値を画面に表示させなさいという意味 { } のところは複数行に分かれていてもよく for(i in 1:10) { print(i) }と書いても結果は同じ。
なお、1:10 は
1 2 3 4 5 6 7 8 9 10
という数字の集まりを示す。次の囲みの中を1行ずつRに実行させてみよう。
1:10 x=c(1:10) x
練習問題: 2から18までの偶数を全て表示させるプログラムをfor命令を使って作りなさい
解答例:(穴埋め形式) for(i in _:_){ # _ のところには数字が入る print(i*_) }
上の for(i in 1:10) { print(i) } というプログラムでは、iの値は繰り返しの度に違う値になる。
1回目 iの値は1 2回目 iの値は2 3回目 iの値は3 .... ........ 10回目 iの値は10
では、一つ前の回のiの値を使いたい場合はどうすれば良いだろうか?例えば、1から10までの合計をfor命令を使って計算することを考えると、
現在のiの値に、1つ前のiの値の合計を加えて表示させたい 1回目 iの値は1 1つ前までの合計は0 1 を表示 2回目 iの値は2 1つ前までの合計は1 3 を表示 3回目 iの値は3 1つ前までの合計は3 6 を表示 .... ........ 10回目 iの値は10 1つ前までの合計は45 55 を表示
こういう場合に、代入という命令を使う。代入は、前回すでにオブジェクトへの数値やベクトルの代入で学んだように、
=
を使う。我々が通常行う計算では、'='は「3+5=8」というように、「左辺の計算結果が右辺に等しくなる」というる意味で用いられるが、プログラミングにおいては、
左辺の変数に右辺の計算結果を代入する
という意味で用いられる。上の合計の計算プログラムは次のようになる。
goukei=0 #goukeiという変数に初期値0を代入 for(i in 1:10) { #以下を10回繰り返す goukei=goukei+i #goukeiに前回までのgoukeiの値にiの値を足したもの代入 print(goukei) #goukeiの内容を画面に表示 }
では、上の囲みの中をRに実行させてみよう。
上の命令の中には2箇所、代入が使われている。最初の行では、goukeiという変数に0を代入するという単純なもので、goukeiの初期値を決めている。
初期値というのも耳慣れない言葉かもしれません。プログラムに登場する変数は、最初の値が何かをはっきりコンピュータに教えてやらないと、メモリに残っている数値がそのまま使われてしまう。試しに、上のプログラムから1行目を省いたプログラムを何度か実行させてみよう。初期値が入っていないので、合計値がどんどん加算されてゆくのが分かる。
次の、goukei=goukei+iという命令文では、右辺の「goukei+i」を先に計算して左辺の「goukei」に代入している。
「右辺と左辺に同じ変数が出てくる」ところが分かりにくいかもしれないが、理解して欲しい。言葉で説明すると、
今の goukei の値に 1 を加えたものを、goukei に代入する(これまでのgoukeiは上書きされる)
という意味だ。
練習問題: 1から10までの数字を1つずつ順々にかけ合わせたた結果を全て表示させるプログラムを作りなさい (1x1=1, 1x2=2, 2x3=6, 6x4=24, 24x5=...... と計算してゆくということ)
以下、穴埋め形式でヒントを書いておく。_の部分(1文字とは限らない)を補ってプログラムを完成しなさい。
seki=_ #sekiという変数に初期値1を代入(かけ算だから) for(i in 1:10) { #以下を10回繰り返す _=_*_ #sekiに前回までのsekiの値にiの値を掛けたもの代入 print(seki) #sekiの内容を画面に表示 }
うまくできただろうか?
縦に長くプリントされるのは格好悪いので、毎回の計算結果をオブジェクトにベクトルとして代入してから表示させてみよう。
seki=1 #sekiという変数に初期値1を代入 kekka=c() #kekkaというオブジェクトに空ベクトルを初期値として代入 for(i in 1:10) { #以下を10回繰り返す seki=seki*i #sekiに前回までのsekiの値にiの値を掛けたもの代入 kekka=c(kekka,seki) #kekkaというベクトルにsekiの値を要素として追加 } print(kekka) #kekkaの内容を画面に表示
ここでは、計算結果を順々に保存しておく入れ物を、kekkaという名前で作成し、初期値に空ベクトル(データは無いが、ベクトル)を作っている。ベクトルの作成は、前回の授業でやったように
c()
という関数を使う。 また、ベクトルに要素を追加するにも、同じ関数 c() を使っている。 では、上の囲みの中をRに実行させてみよう。
kekka=c(kekka,seki) kekkaにsekiを要素として追加したものをkekkaに代入 goukei=goukei+i goukeiにiを加えたものをgoukeiに代入いずれの命令でも、現在の変数の値が上書きされて変更されていることに注目。
練習問題(穴埋め式): seki=1 #sekiという変数に初期値0を代入 kekka=c() #kekkaというオブジェクトに空ベクトルを初期値として代入 for(i in 1:10) { #以下を10回繰り返す seki=seki*i #sekiに前回までのsekiの値にiの値を掛けたもの代入 kekka=c(kekka,seki) #kekkaというベクトルにsekiの値を要素として追加 } print(kekka) #kekkaの内容を画面に表示 _(log10(kekka)) #kekkaの対数をグラフにプロットずいぶんとプログラムらしくなってきた
#**次のプログラムの実行結果を予測してみよう。** for(i in 1:9) { #iの値を1から9まで1ずつ変化させる for(j in 1:9){ #jの値を1から9まで1ずつ変化させる print(i*j) #iとjを掛けたものを表示する } }
matrix()という関数はベクトルを行列に変換する働きがある。例えば、
x=c(1, 2, 3, 4, 5, 6, 7, 8, 9)
というベクトルを、3 x 3の行列に変換したいときは、
matrix(x, nrow=3, ncol=3) #nrow= は行の数、 ncol= は列の数を指定 matrix(x, 3, 3) #nrow=, ncol=を省略して、こういう書き方もできる #演習:九九の表をmatrixという関数を使って表示さなさい。_を埋めなさい result=c() #resultに空ベクトルを初期値として代入 for(i in 1:9) { #iの値を1から9まで1ずつ変化させる for(j in 1:9){ #jの値を1から9まで1ずつ変化させる result=c(_, _*_) #iとjを掛けたものをresultの要素として追加 } } print(result) print(matrix(result, nrow=i, ncol=j)) #iとjはそれぞれ9で終わっているので、9x9のマスになる
プログラミングの基本技の最後は条件分岐。if()を使って表現する。
i=4 #iに4を代入 if(i==3){ #iの値が3ならば print("三") # 三 と表示する } else { #iの値が3では無ければ print("三以外") # 三以外と表示する }
if命令の()の中の式を条件式といい、iの値を評価している。評価に使われるのは比較演算子というもので、
== 等しければ != 等しく無ければ >, >= 左辺が右辺より大きいなら、左辺が右辺以上ならば <, <= 左辺が右辺より小さいなら、左辺が右辺以下ならば
ということを意味している
''比較演算子''である''=='' に対して、代入の時に使った ''='' は''代入演算子''といいう。
上のif命令では、次のような条件分岐が行われている。
if(i==3){ print("三") } ・もしiの値が3ならば、画面に三を表示。 ・もしiの値が3で無いならば、何もしない else { #iの値が3では無ければ print("三以外") # 三以外と表示する }
i=3 #iに3を代入 if(i==3){ print("三") } #iが3ならば三と表示
#演習:乱数を1つ発生させ、値が0.5以上なら「0.5以上」、0.5より小さいなら「0.5より小さい」と表示させる この演習では、乱数を発生させるrunif()という関数を使う。試しに
runif(5)
と入力してみよう。0から1までの乱数が5個表示されたはずだ。乱数はシミュレーションではよく使うので、もう少し慣れ親しんでおこう。 例えば、runif()を使って乱数を10個発生させ、ヒストグラムを書いてみよう。同じことを、100個、1000個、10000個、100000個についてもやってみよう。
hist(runif(10))
バラツキがだんだんと無くなってきたのがわかるだろうか。
#穴埋め式練習問題 if (runif(1) >= _ ) { #条件式0.5以上ならば print("0.5以上") } _ { print("0.5より小さい") }
では、上の繰り返しと条件分岐を組み合わせて使ってみよう
練習問題:for命令を使って1から10までの数字を表示させなさい、5だけは"five"と英単語で表示させなさい
for(i in _:_){ # 10回繰り返し。iは1から10まで変化 if(i == _) { #iが5の場合 print("five") #fiveと表示 } _ { #それ以外の場合 print(_) #iの値を表示 } }