Rのプログラミング


R はインタープリタ型なので、プログラムを実行させると、毎回1行ずつ機械語に翻訳して実行するので実行速度は遅い。そのため、プログラムは関数として定義し、あらかじめ機械語に翻訳させておき、必要に応じて関数を呼び出して計算させる、というのが R 流の使い方である。

R での関数は、C 言語と同じように、通常の意味での関数、すなわち(数学関数のように)ある値を別の値に変換する規則と、一連の計算の「手続き」という両方の意味で使用されている。

R には、複雑な計算アルゴリズムにとって必須の条件分岐や繰り返しを実現する構文が用意されている。これらを組み合わせて与えれた問題を解く R の命令文群(プログラムという)を作ることをプログラミングという。ここでは、プログラミングの知識を解説する。

プログラミング、エディタ

関数の名前を決めて fix(関数名) と入力すると「function() { ... }」 と記入された「Rエディタ」ウィンドウが開くので、そこで作業する。Enterキーを押してもRconsole画面には反映されない。完成したらクローズ ボックスをクリックして Rconsole 画面にもどり、Rの関数と同じように命令文として使うことができる。思い通りの結果にならない場合は edit(関数名) として、Rエディタを表示させ、そこで修正をする。

関数を複数扱う場合、個別の関数ごとに、fix() を使って作業してもよいが、すべての定義関数が一覧できた方が文書管理の上からも都合がよい。また、関数の定義だけではなく、その使われ方についても記録に残しておく方が作業効率が上がる。Rエディタは一関数の定義だけに止まらず、通常のエディタとして使うことができる。RStudio ならば左上にエディタが表示されているが、RGui 画面では「ファイル」メニューの「新しいスクリプト」サブメニューを選択すると「Rエディタ」ウィンドウが表示され、テキストが入力可能になる。そこにRの命令文を書き、それを選択して F5キーを押すと、Rconsole 画面に転送され、結果が Rconsole 画面に表示される。

関数定義ではなく、数式入力の場合でも、入力内容が複雑になった場合は、エディタで入力すべき数式を整えてから R console に送る、という作業手順は作業効率を高めてくれるだろう。

書式

関数を定義する場合は、関数名 = function( ) に続けて関数定義の命令文群を書く。function( ) のかっこ内には必要に応じて仮引数リストを書くことができる。

定義した関数を呼び出す場合は、「関数名(実引数リスト)」と書く。引数がない場合でも「( )」を書かなければいけない。
「( )」を書かないで関数名だけ入力して Enter キーを押すと、関数の定義が表示される。

関数名 = function(引数リスト) { 関数定義命令群 }

引数リスト、デフォルト設定

関数定義の引数リストは、変数名を「,」で区切って並べる。

変数名だけではなく、代入文(右辺は定数)とすると、その右辺値がデフォルト値として与えられる。

関数を呼び出す場合、デフォルト設定をしていない引数は必ず実引数を書かなければいけない。

条件分岐

if(), if() else

ある条件を満たした場合だけある命令群を実行したい場合には「if」構文を使う。
ある条件を満たした場合はある命令群を実行し、満たさなかった場合は別の命令群を実行したいという場合には「if ... else ...」構文を使う。

if(条件式) { ... }

if(条件式) { ... } else { ... }

たとえば:

if(n == 1) stdev = 0 else stdev = sd(x)

else 以下の命令群がまた条件分岐のif 文という場合は「else if」と書く。

複数行にわたっても良いが、else の前で改行すると if 構文とみなされ、else は新たな命令とされるが、else で始まる命令はないので文法違反になる。else の前の実行文を { } で囲み、最後の } の前で改行し、まだ入力が終わっていないことを明示的に知らせる必要がある。あるいは、全体を { } で囲むと } が読み込まれるまでをひとかたまりの命令とみなされるので、else が行頭にあっても文法違反にならない。

> a = 1
> if(a == 0) print("zero")
> else print("non-zero")
 エラー:  予想外の 'else' です  in "else"

# else の前に } が来るようにする
> if(a == 0) { print("zero") + } else print("non-zero") [1] "non-zero"
# あるいは、全体を { } で囲む
> { + if(a == 0) print("zero") + else print("non-zero") + } [1] "non-zero"

数学関数のように、スカラーの独立変数をある値に変換する関数で、独立変数に関して条件分岐によって値を決める場合には注意が必要である。このような関数の場合、ベクトルを独立変数にしたときに、要素ごとに関数値を計算して、結果をベクトルとして返すようにしておかないと、ある種の計算で不都合が生じる。

例えば、max(x, 0) を関数として定義する場合を例にとると、

maxplus = function(x) if(x > 0) x else 0

と書けばよさそうなものだが、引数にベクトルを指定すると正しい計算が実行されない。なぜならば if で判定した結果は一つの論理値しかかえすことができないので、この場合はベクトルの先頭の要素が正である時に TRUE さもなければ FALSE とする約束になっている。そうならないために、ベクトル化した条件分岐の関数 ifelse を使う。

ifelse()

長さ n の論理ベクトル A と長さ n の2つのベクトル u, v に対して ifelse(A, u, v) は、A[i]=TRUE ならば u[i]、さもなければ v[i] (i=1,...,n) を返す関数である。従って、先に挙げた関数 max(x,0) を ifelse 関数を使って書いておけば、引数がベクトルであっても対処できる。

> maxplus = function(x) if(x > 0) x else 0
> maxplus(-1)					# スカラーならば結果は正しい
[1] 0
> maxplus(-1:1)					# ベクトルには対応できない
[1] 0
 警告メッセージ: 
In if (x > 0) x else 0 :
   条件が長さが 2 以上なので、最初の 1 つだけが使われます 
> curve(maxplus, -2, 2)				# グラフが描けない
 以下にエラー curve(maxplus, -2, 2) : 
   'expr' は長さ 'n' のオブジェクトを評価しませんでした 
 追加情報:  警告メッセージ: 
In if (x > 0) x else 0 :
   条件が長さが 2 以上なので、最初の 1 つだけが使われます 

> maxplus = function(x) ifelse(x > 0, x, 0) # 正しい関数定義 > curve(maxplus, -2, 2)

多元分岐:switch()

else if」が繰り返される場合は「switch」構文で置き換えた方が見やすくなる場合がある。
「which(条件式の列)」関数は、条件式を満たす要素番号を返すので、これと組み合わせることで複雑な計算を簡略化することができる。たとえば、x が 0 以上 1 以下の時は 2x、1 以上 2 以下の時は 2(2 - x)、それ以外の時は 0、という三角形の形をした関数を定義するには次のようにすればよい。

> x = 0.5
> switch(which(c(x<0, 0<=x && x<1, 1<=x && x<2, x>= 2)), 0, 2*x, 2*(2-x), 0)
[1] 1
>  switch(which(c(x<0, x<1, x<2, x>= 2)), 0, 2*x, 2*(2-x), 0)
 以下にエラー switch(which(c(x < 0, x < 1, x < 2, x >= 2)), 0, 2 * x, 2 * (2 -  : 
   EXPR は長さ 1 のベクトルでなければなりません  

条件式に使われる記号一覧表

関係演算子
< 左辺が右辺より大きい
<= 左辺が右辺以上
> 左辺が右辺より小さい
>= 左辺が右辺以下
== 左辺が右辺と等しい
!= 左辺が右辺と等しくない

論理演算子
&, &&
右辺かつ左辺が正しい
|, ||
右辺または左辺が正しい
! 否定
論理関数
all 要素がすべて真値の場合のみ TRUE
any 要素の中に一つでも真値があれば TRUE

論理演算子「&&」「||」はスカラー量に対して適用され、結果は論理値 TRUE あるいは FALSE になる。「&」「|」はベクトルの各要素ごとに適用され、結果は論理値ベクトルになる。「!」はスカラーでもベクトルでもよく、ベクトルの場合は各要素ごとに適用され、結果は論理値ベクトルになる。また、「!」は「&」「|」に優先する。

all は「&」を多項演算に拡張したもので、引数の論理値ベクトルの要素すべてが TRUE の場合にのみ TRUE となる。結果は論理値である。
any は「|」を多項演算に拡張したもので、引数の論理値ベクトルの要素すべてが FAULE という場合以外に TRUE となる。結果は論理値である。

条件式が正しい場合は TRUE という論理定数が返される。さもなければ FALSE が返される。論理型変数に論理値を代入する時、TRUE, FALSE の省略形で T, F も許されるが、T, F は一般の変数として使われやすいので注意が必要である。T, F に値が代入されると、通常の変数と同じ扱いになる。

論理式に数が現れると、ゼロを FALSE、ゼロ以外を TRUE に置き換えてから計算される。

> x = c(0,0,1,1); y = c(0,1,0,1)
> !x
[1]  TRUE  TRUE FALSE FALSE
> x & y
[1] FALSE FALSE FALSE  TRUE
> x | y
[1] FALSE  TRUE  TRUE  TRUE
> ! x & y				# ! は & に優先する
[1] FALSE  TRUE FALSE FALSE
> ! (x & y)
[1]  TRUE  TRUE  TRUE FALSE
> x && y # x[1] && y[1] と同じ [1] FALSE
> any(x) # ゼロ以外の数が混ざっているので TRUE [1] TRUE 警告メッセージ: In any(x) : 'double' 型の引数を論理型に変換します

もどる

フィードバック

while()

ある条件を満たしている場合だけ指定された計算を実行する場合には「while」構文を使う

while(条件式) {
	...
}

repeat, break

ある命令群を無条件で繰り返し実行する場合には「repeat」構文を使う。
ループから脱出する場合は、「break」の入った条件文を使う。

repeat {
	...
	if(...) break
...
}

next, break

ループの途中である条件を満たしたら残りのループをスキップして、再びループの先頭からやり直す場合には「next」命令を使い、ある条件を満たしたらループを脱出する場合には「break」命令を使う。

for()

ある値の集合のそれぞれのケースについて命令を実行する場合には「for」構文を使う。
while構文、あるいは repeat + break 構文を使っても表現可能であるが、プログラムのわかりやすさ(デバッグの手間)を考えると、標準的な for 構文で書ける場合は、なるべくそうしたほうが良い。

for(i in ...) {
	...
}

いくつかの例

> for(k in 1:3) print(k)
[1] 1
[1] 2
[1] 3
> n = 0; for(k in 1:n) print(k)
[1] 1
[1] 0
> x = 1:7; for(a in which(x%%3 == 0)) print(a)
[1] 3
[1] 6
> for(a in c("A", "B", "AB", "O")) print(a)
[1] "A"
[1] "B"
[1] "AB"
[1] "O" 

for(a in A) の A は、その後に続く { ... } の処理とは無関係である。また、a は { ... } が終わると、最初に与えられた A の次の要素に設定し直される。したがって、{ ... } の中で a や A の値を変更しても、繰り返し回数には影響が無い。

> A = 1:3
> for(a in A) {
+ 	print(a)
+ 	a = a*10; print(a)
+ 	A = c(A, a); print(A)
+ }
[1] 1
[1] 10
[1]  1  2  3 10
[1] 2
[1] 20
[1]  1  2  3 10 20
[1] 3
[1] 30
[1]  1  2  3 10 20 30

変数の有効範囲

変数は定義しないで使って良いが、関数の内部で値が代入される変数は、関数が終了すると同時に消去され、その値は保存されない。このような変数はローカル変数と呼ばれる。

関数の外側で使用している変数を実引数としないで、関数内部で使いたい場合、参照するだけならば特別な注意はいらない。値を変更したい場合は、代入演算子として「<-」あるいは「=」ではなくて「<<-」(永続代入演算子という)としなければいけない。このような変数はグローバル変数と呼ばれる。

> a = 10
> test = function() {
+ 	a = 100			# これはローカル変数 a への代入
+ 	print(a)
+ }
> test()
[1] 100
> a					# 関数定義の代入文は反映されない
[1] 10
> test = function() {
+ 	a <<- 100			# これはグローバル変数 a への代入
+ 	print(a)
+ }
> test()
[1] 100
> a
[1] 100

関数値の引き渡し

関数で計算した値を返すには「return()」命令文を使う。

返す値が複数ある場合は、c 関数を使ってベクトルで返す。names() 関数を使って名前を付けておくと、どのような計算をしたのかわかりやすい。

返す複数のものが異なるデータ型の場合は、list 関数を使う

########################## 名前を付けて返す
> stats = function(x) { + res = c(mean(x), sd(x), max(x)-min(x)); + names(res) = c("平均", "標準偏差", "範囲") + return(res) + } > > stats(c(1,2,3,4)) 平均 標準偏差 範囲 2.500000 1.290994 3.000000

########################## list 関数で返す
> statss = function(x) { + return(list(mean = mean(x), sd = sd(x), order = sort(x))) + } > statss(c(3,4,1,2)) $mean [1] 2.5 $sd [1] 1.290994 $order [1] 1 2 3 4

もどる

データの入力

キーボードからの入力

関数の実行中に、キーボードからデータを入力させたい場合は、readline() 関数を使う。入力された行を文字列として受け取るので、strsplit(z, "[ ,]") 関数で文字列に分解する。"[ ,]" は正規表現で、データはスペース区切りでもカンマ区切りでもよいとしている。文字列は list 構造になっているので、unlist 関数でベクトル化し、数値に置き換える(as.numeric 関数)という作業が必要になる。

# 入力データの平均と標準偏差を計算する
> test = function() {
+ 	z = readline()
+ 	w = as.numeric(unlist(strsplit(z, "[ ,]")))
+ 	return(c(mean(w), sd(w)))
+ }
> test()  
1 2 3
[1] 2 1

結果の表示

関数定義の中で、結果を表示させる場合は print 関数を使う。複数ある場合は c 関数でベクトルとしてから表示させる、あるいは文字列を表示する cat 関数を使う。変数は自動的に文字列に変換されて、文字列が書かれた順番に表示される。改行したい場合は、文字列「"\n"」を使う。

printf 関数を使っても良い。これは、C言語の printf() とほとんど同じで、結果を書式にしたがって変換した文字列を返す関数である。

> y = 11; cat("y=", y, "\n")
y= 11 

もどる

定義関数の管理

保存

fix(関数名) を実行して関数を定義した場合は、save ボタンをクリックすれば良い。通常のエディタ画面で作業している場合は、拡張子「.R」を付けて保存する。とする。この場合は一つの関数だけを hello.R の名前で保存しているが、定義関数はいくつあってもかまわない。

test = function() {
	cat("Hello R world!¥n")
}
save(test, file="hello.R")

定義関数の呼び出し

source(hello.R)
# デフォルトのディレクトリを使う場合
# R console 「ファイル」メニューの「ディレクトリの変更」を使って保存したファイルを指定する

# 関数の実行
> test()
Hello R world!

# 定義関数の除去
rm(test)

# 定義内容の確認
> test

 

 

 

もどる