@kosugitti
2015/11/28
Rやstanをもっと使いこなすために,プログラミング 技術の基礎について解説します。
プログラミングができるようになると,中級者レベルを名乗っていいと思います!
プログラミングの技術は,言い換えるとアルゴリズムという考え方でもあります。
アルゴリズムとは計算手続きのことであり,個別の算術をできるだけ一般化して表現することを考える必要があります。
アルゴリズムと言っても難しく考える必要はありません。 基本となる処理の要素は三つだけです!
様々なプログラミング言語が有りますが,処理の基本は代入と反復と分岐の三つです。
代入はRでもごく初歩的な段階で使うと思いますが,これも言語の基本です。
a <- 1 # aに1を代入
b <- 2 # bに2を代入
a + b # 代入したもの同士で演算する
[1] 3
a - b
[1] -1
a <- 3 # aの値を上書き
a + b
[1] 5
b <- b + 1 #上書きの仕方が特徴的
b
[1] 3
分岐,あるいは条件分岐というのは,状態を評価して違う処理をすることです。代表的なものはif文です。
x <- 10
if(x>5){
print("5より大きい")
}else{
print("5より小さい")
}
[1] "5より大きい"
一行でまとめて書くifelse文というのもありますね。
x <- 3
ifelse(x>5,"5より大きい","5より小さい")
[1] "5より小さい"
条件によって実行されるコードは中かっこで括れば複数行書くことができますから,条件をネストさせていくこともできます。
x <-6
if(x > 5){
if(x > 7){
print("7よりも大きい")
}else{
print("7よりは小さいけど5よりは大きい")
}
}else{
print("5より小さい")
}
[1] "7よりは小さいけど5よりは大きい"
書き方のコツとして,中かっこの対応がわかりやすくなるようにタブで位置を整えるといいでしょう。
選択肢が複数あって大変な時は,switch関数を使うといいでしょう。
x <- 1
switch(x,"first",
"second",
"third",
"other")
[1] "first"
コンピュータの計算は反復がお得意です。プログラムによって指示した通りに計算をし続け,疲れるということがありません。 反復させる方法はいろいろありますが,代表的な二つを紹介します。
forをつかって繰り返す時は,繰り返す記号,開始・終了,を指定してやります。
for( i in 1:3 ){
print(i)
}
[1] 1
[1] 2
[1] 3
繰り返す記号を演算に組み込むこともできます。コードの意味をよく考えてみてね。
x <- 0 # 値を初期化しています
y <- 0
for(i in 1:10){
x <- x + i
y <- y + (i * 2)
}
x
[1] 55
y
[1] 110
開始・終了点の指示の仕方を工夫してみましょう。
x <- 0
y <- 0
for(i in c(1,3,5,7,9)){
x <- x + i
y <- y + (i * 2)
}
x
[1] 25
y
[1] 50
中かっこでくくった中を反復するのですが,一行しかない場合は中かっこを省略することができます。
x <- 0
for(i in -1:-5)
x <- x + i # ここが反復される中身。次の行はもう違う
x
[1] -15
ネストすることもできます。実行する前に計算の中身を考えてみてね。
x <- 0
for(i in 1:5){
for(j in 1:5){
x <- x + (i*j)
}
}
x
[1] 225
注意して欲しいのは,ネストする時の記号です。代入のところで説明したように,記号の値は上書きされていきますので,指定を間違えると無限ループに陥ることがあります。
for文をネストしながら上手に組めば,行列の加減乗除が計算できる関数が作れます! アルゴリズムを考えてみましょう。
繰り返す回数がわかっていない時に使うのがwhile文です。whileの中身には条件文を記述します。
x <- 0
while(x < 8){
x <- x +1
print(x)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
最初から条件がFALSEであれば,始まりません。
x <- 0
while(x>10){
x <- x -1
print("一つ減らす")
}
複数の条件をAND/ORで繋げることもできます。
x <- 0
y <- 0
while(x < 5 && y < 10){
x <- x + 1
y <- x + y
print(paste(x,"と",y))
}
[1] "1 と 1"
[1] "2 と 3"
[1] "3 と 6"
[1] "4 と 10"
いわゆる「フラグを立てる」というのはここから来ています。
set.seed(1234)
FLG <- TRUE
while(FLG){
x <- runif(1,min=0,max=1)
if(x > 0.3){
print(x)
}else{
print(x)
print("でたぁ")
FLG <- FALSE
}
}
[1] 0.1137034
[1] "でたぁ"
関数は,一般化された手続きをいつでも呼び出して使えるようにするものです。
自分で作った手続き(=アルゴリズム)をひとまとめにして,ある作業をさせたり,その結果を返してもらったりします。必要な手続きをまとめて関数にすることを,カプセル化するということもあります。
関数といえばHello World!を表示させるのが代表例です。
say.hello <- function()
{
print("Hello World!")
}
say.hello()
[1] "Hello World!"
関数には,ある数字・変数を与えて計算してもらいますが,与えるもののことを引数といいます。 関数を作るときは,function関数で作りますが,そのときに与える引数の名前を設定すると,その関数の中ではその引数を使った処理をします。変数の中で作った数字をreturn関数に入れると返すことができます。
tasu <- function(x,y){ #x と yという引数を与えてもらう
ret <- x + y # 足したものをretという変数に入れる
return(ret) # retをreturnで返す
}
tasu(2,3)
[1] 5
引数を正しく渡さないとエラーになります。
tasu(2,3,4) #渡す引数の数を間違えるとエラー
Error in tasu(2, 3, 4): 使われていない引数 (4)
tasu(x=9,y=13) #引数を渡すときに指定してやっても良い
[1] 22
tasu(x=9,z=12) #使われていない引数を書くとエラー
Error in tasu(x = 9, z = 12): 使われていない引数 (z = 12)
引数を関数の外で呼び出し,使うことはできません。
x # 関数の中で使った変数は,関数の外では使えない
[1] 0.1137034
特に指定がない時,デフォルト値としてあらかじめ決められた値を使うようにすることができます。
enzan <- function(x=1,y=1){ #引数に特に指定がなければ,x=1,y=1として扱う
tasu <- x + y
hiku <- x - y
print(tasu)
print(hiku)
}
enzan()
[1] 2
[1] 0
関数の結果を返すには,returnで指定してやります。
enzan <- function(x=1,y=1){ #引数に特に指定がなければ,x=1,y=1として扱う
tasu <- x + y
hiku <- x - y
return(tasu)
}
enzan(5,3)
[1] 8
複数の値を返す時は,リストを使って返してやります。
enzan <- function(x=1,y=1){ #引数に特に指定がなければ,x=1,y=1として扱う
tasu <- x + y
hiku <- x - y
return(list(tasu=tasu,hiku=hiku))
}
enzan(2,4)
$tasu
[1] 6
$hiku
[1] -2
関数で数値を返すと,関数の仕事は終わってしまいます。
enzan <- function(x=1,y=1){ #引数に特に指定がなければ,x=1,y=1として扱う
tasu <- x + y
hiku <- x - y
return(list(tasu=tasu,hiku=hiku))
print("Hello World!") # 実行されない
}
enzan(-3,2)
$tasu
[1] -1
$hiku
[1] -5
オブジェクト,または文字列として関数名を指定するという使い方もできます。
enzan <- function(x,func=mean)
{
do.call(func,args=list(x))
}
enzan(1:10)
[1] 5.5
enzan(1:10,sum)
[1] 55
関数を作るときは,ユーザがこちらの想定したデータ型,データ構造で引数を渡してくれるとは限らないので,引数の中身をチェックするコードを用意しておくといいでしょう。関数を途中で抜け出すときは,stop関数,あるいはwarning関数でエラーを自作して止めます。
enzan <- function(x=10){
if(x==0){
stop("xを0にすることはできません") #関数を抜けだす
}
return(sqrt(x))
}
enzan(0)
Error in enzan(0): xを0にすることはできません
ピースは小さいですが,これの組み合わせ方で様々なものが表現できます。
いろいろなものを作ってみてください!
関数をたくさん集めたものをパッケージと言います。 ここまでくれば,自作関数をまとめたパッケージを作りたくなってきませんか?
パッケージには,サンプルデータやマニュアルが含まれています。CRANにアップロードする都いうところまではいかなくても,自分で作った関数群やよく使うデータセットをまとめてパッケージ化しておくと,それを読み込むだけで使えるようになるという使い方もあります。
パッケージは基本的にデータセット,関数,マニュアルなどの複数のファイルをまとめたもの。最初は空っぽの雛形=スケルトンを作るところから。 実際に作成する時は,package.skelton関数のlistにオブジェクトを幾つか引き渡します。
#SamplePKGという雛形ができる。何も渡してないので警告が多い
package.skeleton(name="SamplePKG")
#演算関数,足す関数,データxを渡す
package.skeleton(name="ENZAN",list=c("enzan","tasu","x"))
dataの中にはデータを(.Rdataの形式で)入れます。 manの中にはマニュアルを入れます。help関数によって呼び出されるもので,関数一つにつき一つのファイルを用意します。中身はTeXに似たマークアップ言語です。 Rの中にはRのプログラムコード本体が入っています。
そしてこれらのファイル,フォルダの準備ができれば,コマンドライン上での作業になります。
R CMD check hogeでパッケージの準備が十分できているかどうかのチェックができます。ここでエラーがなければ,R CMD build hogeでまとめた圧縮ファイルとしてパッケージの完成です。
こうしたスケルトンの作成やマニュアル作りなど,最後はコマンドラインを使わないといけないのかー,とがっかりされた人もいるかもしれません。
が,大丈夫,最近はRStudioの力を借りれば,パッケージ作成プロジェクトとしてRstudio環境内でこれらの作業を進めることができます。
プロジェクトを置いておくところにGitを指定し,オンライン上で後悔しながら開発こともできます(version control)。