複雑システム系演習1(鈴木泰博 担当分)

第2回


行列

ベクトルを組み合わせることによる行列をつくる仕方としては、

列ベクトルを組み合わせる場合cbind

行ベクトルを組み合わせる場合rbind

がある。cbind(ベクトル1, ベクトル2,...,)とすると各々のベクトルを列ベクトルとして組み合わせた行列がつくられる。rbindの場合には行ベクトルの組み合わせとしてつくられる。

また行と列と要素を指定するmatrix matrix(要素、nrow=行数,ncol=列数)もある。

たとえば、3行3列の零行列をつくる場合はmatrix(0, nrow=3,ncol=3)となる。matrix関数により3行3列の行列Aをつくり、ベクトル(1, 2, 3,..,9)を要素とする場合、

>A<-matrix( c(1:9), nrow=3, ncol=3)とすると、

[,1] [,2] [,3]

[1,] 1 4 7

[2,] 2 5 8

[3,] 3 6 9

となり、列ベクトルとして折り畳まれてデータが読み込まれていく。もし、行ベクトルとして折り畳んでデータを読み込ませたい場合には、

>A<-matrix(c(1:9), nrow=3, ncol=3, byrow=TRUE) 

とする。すると、

[,1] [,2] [,3]

[1,] 1 2 3

[2,] 4 5 6

[3,] 7 8 9

>

となる。

行列Aの次元はdim(A)により得られる。また、行数はnrow(A)、列数はncol(A)によりそれぞれ得られる。

また転置行列は、関数tを用いてt(A)としてそれぞれ得られる。

演習

  1. 1 2 3 4, 5 6 7 8, 9 10 11 12をそれぞれ要素とするベクトルX_1, X_2, X_3をつくる
  2. ①でつくったベクトルを列ベクトルとして組み合わせた行列A_1をつくる。
  3. ①でつくったベクトルを行ベクトルとして組み合わせた行列A_2をつくる。
  4. A_1とA_2の次元を調べる
  5. A_2を転置行列にしたA_3をもとめる。

行列Aのなかから一部の要素を取り出すには、

A[1,] ... 1行目の要素のみをとりだす

A[,1]...1列目の要素のみをとりだす

A[1,3]は1行3列目の要素を取り出す

A[1:3, c(1,3)]は1から3行目までと、1,3行列の要素を取り出す。

また、行列の操作を行うこともできる。

A[A<3] <- 0とすることで、Aの要素で3よりも小さい要素を0とすることができる。また、A[-1,]やA[,-3]のように行や列の番号をマイナスで表記することにより、該当する行および列を取り除くことができる。

演習 前の演習でつくったA_3について以下を行え。

  1. 1行目のみをとりだす
  2. 1列目のみをとりだす
  3. 1行3列目の要素を取り出す
  4. 1から3行目までと、1,3行列の要素を取り出す。
  5. A_3の要素で3よりも小さい要素を0として、A_3が変更されているか確認せよ。

行列の演算

ベクトルの演算と同様に2つ以上の行列の対応する要素の加減乗除は + - * /により行うことができる。行列の積については%*%を用いる。

演習 前の課題でのA_1とA_3をもちいて以下を行え。

  1. A_1とA_3から第4行目を削除して、それらをA_1, A_3とせよ
  2. A_1とA_3の行列積をもとめよ。また、A_3とA_1の行列積をもとめよ。

Rによるプログラミング

繰り返し

  1. for

Rのforは一般的なプログラミング言語と「繰り返しの条件部の書き方」が少し違っている。Javaなどでは

for (繰り返し変数の初期化; 繰り返しの条件; 繰り返し変数の更新)、例えば、

for(int i=1; i<=5, i++) ... i++とはi=i+1のこと

のように記述するが、Rの場合には

for (繰り返し変数in繰り返し変数の更新の仕方)

i <- 0 ... 繰り返し変数の初期化

for ( i in 1:5 )

のように記述する。まず他の言語と異なり、繰り返し変数の初期化は不要である。for文の条件部(i in 1:5)について、1:5とは「:」演算子、つまり列挙の演算子となっており、1:5を実行すると1 2 3 4 5となる。なので( i in 1:5)とは( i in c(1,2,3,4,5))と同じ意味になる。そして[iはなにもしなくても1→2→3→4→5と勝手に変化していく]{.underline}。よってjavaや他の多くの言語のように"i=i+1"のように繰り返し変数の更新を記述する必要はない。もしiを2→4→6のように変化させたい場合には、for( i in c(2,4,6))とすれば,iは"in"の右側の指示通りに変化していくので、iは2,4,6と変化する。

for文により繰り返し行う処理は for(i in 1:5){処理}のように記述するもし処理が2つ以上ある場合には、for (x in 1:5){処理1; 処理2; ...}と「;」で区切るか、

for (x in 1:5)

{

処理1

処理2

...

}

のように改行して記述する。

演習

  1. 繰り返し変数が1,2,3,4,5の場合に、繰り返し変数を表示させるプログラムを作成し実行せよ。
  2. 繰り返し変数が2,4,6の場合に、繰り返し変数を表示させるプログラムを作成せよ。

ヒント: ①解答例は以下のようになる。

for (i in 1:5){

print(i)

}

これを実行させたら、1:5の部分をc(1,2,3,4,5)に変えて実行してみよ。これをもとにすれば②のプログラムのつくりかたがわかるであろう。

  1. 1から5までの和と、2乗和1^2+2^2+...5^2 (^2とは2乗)を計算するプログラムを作成せよ。

ヒント: 1+2+3+4+5と1^2+...5^2を計算するわけであるが、どうやったら計算ができるであろう?まず単純和についてかんがえてみると最初の値は1だからこれに2を足す1+2を行うことになる。その次にはこの1+2の計算結果に3を足すことになるので、(1+2)+3となる。さらに(1+2)+3の計算結果に4を足すことになるから((1+2)+3)+4、さらにこれに5を足すわけなので(((1+2)+3)+4)+5となる。こうやって計算するにはどうしたらいいだろう?まず最初1+2を行うところまではよいが、この結果に3を足さねばならぬので1+2の計算結果をどこかに"メモっておく"必要がある。その"メモ"のために変数(オブジェクト)が必要となる。オブジェクト名は(先述した)きまりに従えば自由につけられるが、和なのでsumxとでもしておこう。ちなみにsumxのxとはオブジェクト1,2,3,4,5のつもりである。

まずsumxという文字列をオブジェクトとして使うことをR言語処理系に伝える必要がある。もしかしたら、どこかですでにsumxというオブジェクトは使っているかもしれない(もし、それでは困る場合には...ls()としてみると、これまでに使われているオブジェクト名がリストされるのでしたね..)。そこで、このsumxを初期化しておく必要がある。sumxに代入するときにsumxがすでに1とか2だと困るので、sumxは0にしておく必要がある。そのため、

>sumx<-0

とすればよい。「でも...なんかおかしい!さっきのforの繰り返し変数は初期化しなかったじゃないか!」と思うかもしれない。これはR言語の"便利さ"でもあり、この過剰なまでの便利さが初学者を混乱させることもある(私も最初は混乱した)。for (i in c(1,2,3,4,5))の場合にはiは1,2,..,5となることを決めてしまっているので、改めてiを初期化する必要がないのだ。他の言語の場合にはiには"どんな種類の値が入るのか...(整数なのか、少数なのか、など)"が決められているだけなので、実際にはどのような値が入るのかはわからない。なのでi=i+1やi=i+0.1のように、繰り返し変数の更新を行う必要がある。

 さて、このsumxを用いることにより、まず1+2の計算結果をsumxに"書きとめる"ためにsumx <- 1+2とすればよい。これでsumxには1+2の計算結果が記入されたことになる。次の計算は(1+2)+3だが(1+2)の計算結果はすでにsumxに書き込んであるので、sumxに3を足せば、(1+2)+3が計算できる。そして、さらにその結果をsumxに"上書き"してしまえばよい。そうして"上書き"をすれば、それまでは1+2の計算結果が記入されていたすsumxが(1+2)+3となるから、次に((1+2)+3)+4の計算をするときには、ただsumx+4とすればよいことになる。そして、同様にsumx+4の結果をsumxに上書きしてしまえば、(((1+2)+3)+4)+5の計算もsumx+5とするだけでよい。

以上の計算をまとめてみると、

sumx <- 1+2

sumx <- sumx + 3

sumx <- sumx + 4

sumx <- sumx +5

となる。するとsumx<-1+2以外の計算ではsumx + 3, sumx+4, sumx+5とsumx+繰り返し変数となっているが、最初のところだけがsumx <-1+2となっている。この部分について少し考えてみると、そもそもsumx<-0としてsumxの初期値は0だから、sumx <- sumx + 1としてもよいだろう。そうするとsumx<- 1+2はsumx<-sumx+2とすることができる。こうすると、

sumx <- sumx + 1

sumx <- sumx + 2

sumx <- sumx + 3

sumx <- sumx + 4

sumx <- sumx + 5

つまり、

sumx<- sumx + 繰り返し変数

となる。 たとえば、繰り返し変数をxとすると、xは1,2,3,4,5となればよい。先述したようにこれをR言語で記述すると、

for (x in c(1,2,3,4,5))もしくは列挙の演算子「:」をもちいると、for (x in 1;5)

となる。次に処理であるが先述したR言語の文法によると処理はfor(x in 1:5){処理}と{,}でくくることになっている。この場合の"処理"と"sumx <-sumx + 繰り返し変数"で、繰り返し得変数はxであるから、sumx <- sumx + xとなる。よって、

for (x in 1:5)

{

sumx<-sumx+x

}

となる。だが、これを実行しても結果は表示されないので、以上に結果を表示させるコマンドprintともちいてprint(sumx)をくわえてみよう。「処理が2つ以上ある場合には「;」のようにセミコロンでつなぐ、もしくは改行する」のがR言語の文法なので、

for (x in 1:5)

{

sumx<-sumx+x

print(sumx)

}

とすればよい。すると、

>for (x in 1:5){

sumx <- sumx + x

print(sumx)

}

[1] 16

[1] 18

[1] 21

[1] 25

[1] 30

となる。

以上の考え方をもとに、「1から5までの和と、2乗和1^2+2^2+...5^2 (^2とは2乗)を計算するプログラムを作成せよ」をかんがえてみよう。すでに"1から5までの和"についてはプログラムを作成できているので改行して

>for (x in 1:5){

sumx <- sumx + x

print(sumx)

2乗和を計算する

結果を表示する

}

とすればよい。ただ、ここで注意しないといけないのはオブジェクトsumxは"足し算をするときのメ帳"だったので、2乗和の計算をするときには"別のメモ帳...つまりオブジェクト"が必要になる。たとえば、そのオブジェクトをsumxxとしてプログラムをつくってみよう。

for以外の繰り返しの処理によくつかわれるものとしてwhileがある。おなじ繰り返し処理でもforとwhileは大きく異なる。

for → あらかじめ繰り返し回数が決まっている場合

while → あらかじめ繰り返し回数が決まっていない場合

forでは「何回繰り返すのか」があらかじめ決まっているので、それを繰り返し変数として、例えば1,2,3,4,5のように、あらかじめ決めておく。そして繰り返し変数が自動的に1,2,..から5へと変わっていき繰り返し処理は終了する。

例えば、10,000円貯金をしよう!と決心したとしよう。"それまでにいくら貯金されたか"が表示されるような貯金箱をかってきて、懐具合によって10円だったり、100円だったりを貯金していく。いくら貯金するかはその時の懐具合によるので、[貯金箱にお金をいれる回数をあらかじめ決めておくことはできない]{.underline}。なのでforは使うことができない。このような場合にはwhileをもちいる。この場合の"繰り返しの条件"はどうしたらよいだろう?...もういちど考えてみると貯金額が10,000円になったら貯金をやめて貯金箱を開けることになる。つまり、貯金額が10,000円以下のときには貯金を繰り返す、ことになる。つまり、

while貯金額 < 10,000円

貯金を行う

となる。貯金を行う...とは、貯金箱にお金を入れる、ということであるから、まずオブジェクトとして貯金箱mboxを用意しよう。貯金箱には最初にはお金が入っていないから、mbox<-0となる。貯金額は決まっていないので、適当な乱数をつくって足していくことにしよう。そのため、

>ceiling(runif(1,1,1000))

を用いる。runifとはrunif(乱数の個数、乱数の最小値、乱数の最大値)と乱数を発生させるコマンドである。この場合だと1から1000までの実数値(つまり少数を含んだ値)がかえってくるので、その前にceilingというコマンドをつけてある。ceilingとは少数を"切り上げる"ものであり、

>ceiling(1.2)

[1]2

となる(ceilingにしていることに深い意味はない)。これによって1円から1000円までの貯金額をランダムに与えることができる。貯金箱(mbox)と貯金額を書き加えると

mbox <-0 ...貯金箱は最初は0円

while (mbox < 10,000) ...貯金箱が10000円以下の場合に繰り返す

{

mbox<-mbox+ ceiling(runif(1,1,1000)) ...貯金箱に"貯金"する

print(mbox) ... 貯金箱の中身を表示する

}

[1] 524

[1] 1487

[1] 2482

[1] 2553

[1] 3052

[1] 3552

[1] 4032

[1] 4839

[1] 5818

[1] 6256

[1] 6447

[1] 6546

[1] 7306

[1] 7620

[1] 7871

[1] 8313

[1] 8910

[1] 9497

[1] 9698

[1] 9899

[1] 10078

となって、10000円を超えたところで貯金が終わり、処理(つまり貯金が)終わる(この例の場合には10,078円貯金できた)。

whileの使い方をまとめると、

繰り返し変数の初期化

while (繰り返しの条件)

{

処理1

処理2

...

繰り返し変数の更新

}

となる

演習 貯金のプログラムを実際に動かして、貯金額がいくらになったか調べなさい。

条件分岐 "if"

if文は条件によって処理をかえるときに用いる。文法は

if (条件文)

{

処理

else

処理

}

となる。これは条件文と一致した場合には処理が行われ、もし一致しない場合にはelseの次の処理が実行される。

 たとえば、先ほどの貯金の例で「小額だと貯金しない...たとえば、300円以下だと貯金しない」の場合は、

if (貯金額 > 300)

貯金する

else

貯金しない

のようになる。

演習 さきほどつくった貯金のプログラムで、貯金額が300円以下だったら貯金しない、ようにプログラムを改変せよ。

mbox <-0 ...貯金箱は最初は0円

while (mbox < 10,000) ...貯金箱が10000円以下の場合に繰り返す

{

mbox<-mbox+ ceiling(runif(1,1,1000)) ...貯金箱に"貯金"する

print(mbox) ... 貯金箱の中身を表示する

}

R言語と"関数型言語"

プログラミングについて説明をするとなると、どうしてもR言語が関数型言語のテイストを含んでいることに触れざるを得ない。だが、初学者にとって「関数型言語」といわれても???であろう。そもそも"関数型"とはなにか?がわからないであろう。でも"関数"だったら慣れ親しんでいると思う。たとえば、

Y=2X

は"関数"とよばれる。この式が表していることはなにか?あまりにも単純すぎて却ってみえにくくなっているかもしれないが、この式は変数Yと変数Xの"関係"を表している。その関係とは..."2倍されたX"と"Y"が等しい...とのXとYの関係を表している。この関係は、Xに1を代入すればYは2、Yに4を代入すればXは2のように、XとYは一意に定まる。その場合にXとYの関係を"関数"とよぶ。また別の関係として、

Y=X

は"2乗されたY"と"X"が等しいとの関係を表している。この場合にはX=2にたいしてはY=±√2と2つの数が対応するのでXとYの関係は一意に定まらない。[このようにXとYの関係が一意に定まらない場合は関数とはよばれない]{.underline}。

理工学ではさまざまな関数をつくって研究や開発をしているが、いちいち式を書いているのは煩雑である。そこで関数になにか名前をつけたくなってくる。その場合には例えば、fやgなどの適当な関数の名前と"引数"により

関数の名前(引数)=関数の定義式(引数)

のように記述する。例えば、関数の定義を「Xを2倍する」とする場合は"引数"はXとなる。そしてこの関数をfという名前にする場合には、f(X)=2X、となる。また、「Xを2乗する」という関数を定義する場合は、関数名fはもうつかっているので、関数的をgとしてf(X) = X2となる。

R言語での関数の定義の仕方は、

関数名<-function(引数)

{

処理

...

}

である。例えば、Xを2倍する関数は

f<-function(X)

{

2*X

}

となる。R言語ではあらかじめ用意されている関数を用いて、新たに関数をつくり、それをつかって計算を行っていくことができる。

関数の合成と再帰...関数型プログラミング

さて、関数が"使いやすい"点は、引数(例えばX)に対して値(例えばf(X))が一意に定まるということである。なので、「変数Xを2倍してから2乗する」、のような処理は関数を組み合わせて行うことができる。私たちはXを2倍する関数をfとして、2乗する関数をgとして定義したので、これを組み合わせてXを2倍してから2乗する関数Hをつくることができる; H(X)=g(f(X))。こうして関数を組み合わせることを"関数の合成"とよぶ。この関数の合成をつかうと、さまざまな計算ができる。例えば「変数Xを2倍して2乗し、さらに2倍する」のであれば、f(g(f(X))とすればよい。

演習 

次に定義される関数fについてf(5)の値を[手で計算して]{.underline}求めなさい。

f(X) = X * f(X-1)

f(0) = 1

①の定義をもとに階乗のプログラムを再帰をもちいてR言語のプログラムを作成して実行例を示しなさい(この関数をfact_1としなさい)。

ヒント:アルゴリズムは次の通りである。

○もしxが0と等しい(演算子を用いる)ならば→ if(x1) 1とすればよい

○そうでない場合は X*fact_1(X-1)となる。→ else x*fact_1(x-1) とすればよい

この演習問題では、関数の定義の中で関数を呼んでいる。このような関数の定義の仕方を再帰とよぶ。手で計算してみるとわかるように、再帰により"繰り返し"による計算を行うことができる。

再帰では繰り返して関数が呼ばれていき、その関数の呼び出しは以上の例の場合にはXが0になるところまで呼ばれ続ける。演習①の場合であれば、Xは1づつ小さくなりながら、関数がよばれていく。そこでは、 f(5)→5*f(4)→4*f(3)...のように関数の値がなにになるのかはわからない。やがてX=1になると、1*f(1-1)=1*f(0)となってこの関数呼び出しの連鎖が止まる。ここに至とf(0)=1と値が定められているから、ここで1*f(0)=1*1とf(1)の値が定まる。f(1)が定まればf(2)の値が定まり...f(5)の値が求められる。

演習 以上の再帰を用いた階乗のプログラムを、再帰をつかわずにプログラムしてその関数をfact_2としなさい。

ヒント: 5回計算することがわかっているので繰り返し(for)を使うとプログラミングをつくりやすい。

以上のように、再帰をもちいてプログラムを書くと簡潔に数学的にエレガントに書くことができるが、関数呼び出しを行うあいだ値をメモリ上に展開してくので、処理速度が遅くなってしまう。

演習R言語で関数の処理時間はsystem.time (関数) とすると計測することができる。再帰をもちいた場合と、用いない場合の処理時間を比較しなさい。

ヒント:system.time(fact_1(1000))などとすると1000の階乗の処理時間を計測できる。

以上の演習から"関数を定義して計算する"との雰囲気がつかめたかと思う。このように関数を定義し、それらを組み合わせて計算を行うプログラミング言語を「関数型プログラミング言語」とよぶ。代表的な関数型言語としてLISPがある。また、最近すこし流行しているプログラミング言語としてはHaskellなどがある。 以上の例でみてきたように、関数によるプログラミングでは引数Xに代入された値は変わることがない...(ちょっと???とおもうひとは、先ほどの例f(g(X))を思い返してみればよい。これは"Xを2乗して2倍する"計算であったが、たとえばXに2を代入したとして、この2が計算の途中で変わってしまうことはない。関数が単純だから、その"ありがたみ"がわからないが、例えば、g(f(g(f(X)+g(X))+f(X))+g(X))=L(X)の場合には"どこからどの順番で計算しても代入されたXの値は変わることがない"。よって他の言語の場合での"バグ"の原因となる、変数の引き渡しや代入を繰り返しているうちに代入した値が書き換えられてしまうようなことはない。

 こうした性質はR言語でもひきつがれている。例えば、先述した行列やベクトルから要素を削除する処理、例えば、行列Xから1行目を消去する処理であるが、これを関数Hとして記述すると、H(X)=X[-1,]と書く事ができるであろう。関数H(X)の引数Xには行列が代入され、代入された行列から1行目が削除された行列がH(X)となる。たとえば、Xに行列Aを代入した場合、行列Aから1行目が削除された行列がH(X)に代入されることになるが、代入された行列Aはそのままで、「Aから1行目を削除した行列」がつくられて、これがオブジェクトH(X)に代入されることになる。このように代入された値が保存されるような処理を"非破壊的処理"とよび、関数型プログラミングの特徴となっている。処理が非破壊的であれば、もし代入された値を他の処理でも用いる場合にも影響がでない。

 一方で、ベクトルや行列に値を書き込む処理、例えばX[1,1]<-0 (行列の1行1列目の値を0とする)の処理は、Xに代入された行列の値を書き換えてしまう。"こうした関数型らしからぬ"処理のことを"破壊的処理"とよぶ。破壊的処理を行うと、代入された値が変わってしまうので、その値を用いている他の処理に影響が及んでしまう(これが時として深刻なバグとなってしまう)。このように破壊的処理によって、関係する処理に影響が及ぶことは"副作用"とよばれる。なので、破壊的処理を行う場合には他の処理に影響が出ないようによく注意する必要がある。

関数合成によるプログラミングを少し練習してみよう。もういちど復習すると関数の

定義の仕方は、

関数名<-function(引数)

{

処理

...

}

であった。そして、Xを2倍する関数は

f<-function(X)

{

2*X

}

となるのであった。以上をもとに以下の演習をやってみよう。

演習

①変数Xを2乗する関数gを定義し実行せよ。

②関数fとgを組み合わせ、変数Wを2乗して2倍したものと、2倍して2 乗したものを足したものを2乗する関数を定義しなさい(関数名はfooとせよ)。

  1. Xの値が3以下であれば2倍し、そうでない場合は3を足す関数を定義しなさい(関数名はf_1とせよ)
  2. Xの値が3以下であれば3を足し、そうでない場合は2倍する関数を定義しなさい(関数名はf_2とせよ)

"ベクトル"によるプログラミング

ここで"ベクトル"とは、数学の教科書に出てくるベクトルをそのままさしているのではない(そのため"ベクトル"としている)。JavaやCなどの、いわゆるALGOL型のプログラミング−パラダイム(プログラムの仕方についての哲学/方針のようなもの)では、データや変数間の計算は「1つずつ行う」と考える。たとえば、「ベクトル(1,2,3,4,5)の要素を2倍する」、場合であれば、

f<-function(X)

{

listx<-NULL

for(i in 1:length(X))...length関数はベクトルXの長さを返す関数

listx<-append(listx, 2*X[i])...append関数は結果を追記する関数

listx

}

となる。この場合には、ベクトルXから1つずつデータが読み出されて2倍されてから、その結果をlistxに書き込んでいく。X=(1,2,3)とすると、最初はlistxは初期化されているので、

NULL

(2)

(2, 4)

(2, 4, 6)

と処理がなされていく。

これにたいして"ベクトル"での演算とは、入力されたベクトルX=(1, 2, 3)を「バラバラにせずにそのままデータの塊り•集まり(集合)として扱う」と考える。そして、

「データの集合」---処理→「データの集合」−処理→「データの集合」−処理→...

と「データ処理した結果を入力として処理を行っていく」と考える。ここでの"処理"を"関数"とおきかえると、

f(データの集合)→出力...処理されたデータの集合

となるので、この"出力"を入力としてg(出力)と新たな計算を行うのであるが、出力とはf(データの集合であるから)これは、

g(f(データの集合)

となる。よって、以上のように「処理したデータの集合を入力として処理を繰り返す」ことは、先述した関数型プログラミングの仕方である「合成関数による計算」と同じことになる。

 先述の演習でつくった、以下の関数f_1(X), f_2(X)を用いてXをベクトルとして扱う計算を行ってみよう;

f_1(X) : Xの値が3以下であれば2倍し、そうでない場合は3を足す関数、 f_2(X):Xの値が3以下であれば3を足し、そうでない場合は2倍する関数。

ベクトルXを(2, 4, 5, 3)とすると、f_1(X)は、f_1(2, 4, 5, 3)となる。f_1は1変数関数であるから、

f_1(c(2,4,5,3))

[1] 4 8 10 6

警告メッセージ:

In if (x < 3) 2 * x else x + 3 :条件が長さが2以上なので,最初の一つだけが使われます

となってしまいベクトルの最初のあたいである2が3以下であるため、この条件を用いて残りすべてのベクトルの要素が2倍されてしまっている。ここで本当にやりたいことは、

f_1(2, 4, 5, 3) → (f_1(2), f_1(4), f_1(5), f_1(3))と関数f_1をすべての要素に並列に適用してベクトルをつくることである。こうすることで、データの集合−処理→データの集合、この場合であれば、(2, 4, 5, 3)−f_1→(f_1(2), f_1(4), f_1(5), f_1(3))と計算を行うことができる。

マッピング

こうした処理は"マッピング"とよばれ、マッピングを行うための関数が"apply系"の関数である。apply系の関数には以下のようなものがある。

sapply(xs, f): xsの各要素にfを適用して、その結果をベクトルでかえす

lapply(xs, f):xsの各要素にfを適用して、その結果をリストでかえす

rapply(xs, f):xsの各要素に対して再帰的に関数fを適用して、その結果をリストでかえす

#これら以外にも、mapply, apply, tapply, eapplyなどがある(詳細は参考文献[高階, p184など]を参考にされたい)。

このapply関数をつかうと、f_1(2, 4, 5, 3) → (f_1(2), f_1(4), f_1(5), f_1(3))は、

sapply(c(2,4,5,3), ho)

[1] 4 7 8 6

となる。

フィルタ

"ベクトル"によるプログラミングでは、マッピングの他に「フィルタ」と「還元」の手法が用いられる。還元とは2つの引数をとる関数、例えば、x + yのような関数、をベクトルに対して適用するための処理である。だが、R言語には還元の処理を行う関数は用意されていない。その理由は、R言語では、基本的に引数としてベクトルをとるように設計されているので、引数が2つの関数を使うような場合が少ないからである。そこで、ここではフィルタについて紹介することにする。

フィルタとは、入力された集合の各要素を調べて、あらかじめ指定された条件をみたすものだけを取り出して、出力する処理を行う。そのためにはベクトルや行列の処理のところでの"条件付き参照"を用いる。もう一度、復習してみると、ベクトルa=(1, 2, 3, 4)から2以上の要素を取り出すための条件つき参照は

>a[a>2]

とすればよかった。

以上の復習をふまえて、ベクトルから"偶数のみ"を取り出す処理を行うことを考えよう。そのために偶数か否かを判定する関数is.evenを定義し、条件つき参照をもちいてフィルタをつくってみよう。

演習 

  1. 数値xが偶数ならばTRUEをかえす関数is.evenを定義せよ。

ヒント: "偶数"とはxを2で割った余りが0と考えられる。除算の余りを求める演算子は%% (xを2で割った余りならば、x%%2),また、論理比較の演算子で aとbが等しければTRUEをかえす演算子は==である。

  1. ベクトルaを(1,2,3,4)としたとき、aの要素のうち偶数のみを出力せよ

  ヒント: 条件つき参照とis.evenをもちいる