Rの基礎(2)

Rの基本的なデータ構造と制御構造について学ぶ

今回の学習項目:

Rの基本データ構造

Rの変数は型宣言が不要であるが、『型』がないわけではない。 実はデータそのものに『型』の情報をもたせているのである。 どんな型があるのかを紹介するとともに主なデータの型の使い方をみていこう。

Rの主要なデータ型

Rで用いる基本的なデータ型には以下のものがある: 数値型と論理型については既にみてきた。 文字型は他のプログラミング言語では「文字列」と呼ばれるもので、空白を含む文字の並びを二重引用符(")でくくる。 複素数型は複素数を扱うためのものであり、因子型は数値以外の値(カテゴリ)を統計で扱うためのものである。

データ型を調べる

変数にどんな型のデータが入っているかを調べるには、class関数を使う。 また、それぞれの型ごとに、例えば数値ならis.numericという、 「is.型」という名称の関数が用意されていて、引数がその型のデータであれば真(TRUE)、 そうでなければ偽(FALSE) を返す。
> class(3.0)
[1] "numeric"			# 型名は文字列
> is.numeric(3.0)		# 数値(numeric)かどうか
[1] TRUE    
> is.numeric("abc")		# "abc"は数値(numeric)かどうか
[1] FALSE	# 数値型(numeric)ではない(FALSE)と返される
> is.integer(3.0)		# 数値のなかでも整数(integer)かどうか
[1] FALSE  
> is.integer(3)			# 引数によって結果が変わる
[1] TRUE   
> class(3)			# 3は数値でもあり整数でもあるが...
[1] "numeric"	# 数値型(numeric)と返される

課題1-1

数値型(numeric)のデータに対してclass関数を適用すると"numeric"という文字列が返され、また数値型のデータかどうかを調べるためのis.numericという関数が用意されていたことを見た。このことを前提知識として、以下の問に答えよ。
  1. class(TRUE)は何を返すか。予想してからRで実行し、その結果と予想とを比較せよ。もしも違っていた場合はその理由を考察せよ。
  2. 引数が論理型のデータかどうかを調べる関数はどのような名前のものと考えるか?
    ヒント; 数値型かどうかを調べるのはis.numeric関数
  3. class("abc")は何を返すか。予想してからRで実行し、その結果と予想とを比較せよ。もしも違っていた場合はその理由を考察せよ。
  4. 引数が文字型のデータかどうかを調べる関数はどのような名前のものと考えるか?
  5. class(c(1,2,3))は何を返すか。予想してからRで実行し、その結果と予想とを比較せよ。もしも違っていた場合はその理由を考察せよ。
  6. class(c("a","b","c"))は何を返すか。予想してからRで実行し、その結果と予想とを比較せよ。もしも違っていた場合はその理由を考察せよ。
  7. class(c(1,2,"c"))は何を返すか。予想してからRで実行し、その結果と予想とを比較せよ。もしも違っていた場合はその理由を考察せよ。

[課題1-1の答え]

Rで実行した結果を示す:
> class(TRUE)				# (1)
[1] "logical"
> is.logical(TRUE)			# (2)
[1] TRUE
> is.logical("abc")
[1] FALSE
> class("abc")				# (3)
[1] "character"
> is.character("abc")			# (4)
[1] TRUE
> is.character(1.23)	
[1] FALSE
> class(c(1,2,3))			# (5)
[1] "numeric"		# 数値を要素とするベクトルに対しては「数値」型と返す
> class(c("a","b","c"))			# (6)
[1] "character"		# 文字列を要素とするベクトルに対しては「文字」型と返す
> class(c(1, 2, "c"))			# (7)
[1] "character"		# 数値と文字列が要素として混在するベクトルに対しては「文字列」型と返す
> (x <- c(1, 2, "c")) 		# 実際には、数値と文字列が混在するベクトルは...
[1] "1" "2" "c"		# 要素がすべて文字列に変換されている
最後の問題で、なぜこのようになるのか、説明を試みよ。

データを変換する

pasteは次の例で見るように、文字列と文字列を結合した文字列を創りだす関数である(デフォルトでは結合される間に空白" "が入る):
> paste("Hello","World!")
[1] "Hello World!"
> paste("You are",17,"I am",16)
[1] "You are 17 I am 16"
pasteは文字列をつなげる関数であるが、二番目の実行例からわかるように、文字列でない引数(例えば17)に対して「文字列に変換」(17を"17"に変換)したものを結合する。

このような自動的な型変換を利用するのではなく、ユーザーの都合から型を変換したい、たとえば文字列を数値型や論理型に変換したいことがある。

> "17"+16
 以下にエラー "17" + 16 :  二項演算子の引数が数値ではない 
> is.logical(1)
[1] FALSE
そのような場合は、「as.型名」という名称の関数を使う:
> as.numeric("17")+16
[1] 33
> as.logical(1)
[1] TRUE

課題1-2

次のように、2つの数を表す文字列を引数とし、それらを数値に変換することによってそれらの和を返す関数plusを作れ。
> plus("17","16")
[1] 33
[課題1-2の答え]
仮引数をxとyとすると、関数の定義の形は:
plus <- function(x,y) {  関数本体 }
ここで関数本体は、xとyをそれぞれ数値型に変換したものを足せば良い。したがって、 以下が答えとなろう:
plus <- function(x,y) { as.numeric(x)+as.numeric(y) }

課題1-3

次のように、数の文字列を要素とするベクトルを引数に取りその要素の総和を返す関数sumAllを作れ。
> sumAll(c("1","3","5","7"))
[1] 16
[課題1-3の答え]
引数のベクトルにas.numericを適用することを思いつけば簡単。
sumAll <- function (z) {  sum( as.numeric(z) ) }

この問題について、過去にはかなりの人が次のように書いて失敗してました。

> plus <- function(x,y){
       return(as.numeric("x","y"))
    }
> plus("17","16")
[1] NA        # おかしな結果…
これには二つの間違いが含まれています。 まず、as.numericのような型変換の関数は複数の引数をとれません。 また、もう一つは(プログラミング言語すべてに当てはまりますが) 型変換したいのは変数xyの「値」であって、 "x""y"というものではない、ということです。

制御構造

ほとんどのプログラミング言語と同様に、Rでも条件分岐や繰り返しという制御構造は必須の要素である。これらの制御構造について紹介する。

条件分岐

if文によって条件の真理値により実行する文を選択する。

課題2-1

引数が文字型ならば"文字型"、論理型ならば"論理型"、数値型ならば"数値型"、それ以外ならば"未知の型"を「返す」(returnを使う)関数typeを作れ。ただし条件分岐を用いるものとする。次は実行例である:
> type(3)
数値型
> type(TRUE) 
論理型
> type("abc") 
文字型
> type(Inf) 
数値型

[return]
returnは「Rの基礎(1)」で述べたように、関数の実行後、「値」(戻り値)として値を返すための関数である。Rではreturnがなくても、関数内で最後に実行された式の値が戻り値となるが、関数の実行を途中で止めたい場合や、返す値を明示するために用いる。以下は、returnを用いた階乗関数(n! = n * (n-1)* ... * 1の例である)
factorial <- function(n) {
    if (n == 0) return(1)
    return(n*factorial(n-1)) }
            [課題2-1の答え]
type <- function(x) {
    if (class(x) == "character") return("文字型") 
    else {
           if (class(x) == "logical") return("論理型") 
           else {
                  if (class(x) == "numeric") return("数値型")
                  else return("未知の型")
                }
       }
} 

課題2-2

Euclidのアルゴリズムを用いて、2つの整数の最大公約数を求める(再帰)関数euclidを作れ(注:再帰関数とは、最記法を用いた関数のこと)。ここでEuclidのアルゴリズムは以下に示すものである:
前提: a, bを引数とする
 (1) a < b ならば aとbの値を入れ替える (swapする、という)
 (2) c <- aをbで割った余り (つまり c <- a %% b)
 (3) c == 0 ならば bを答えとして終了
 (4) そうでなければ、b, cを引数とする関数euclidの値を答えとして終了(再帰法を用いる)
次は実行例である:
>  euclid(82,35)
[1] 1
>  euclid(90, 60)
[1] 30
>  euclid(6201, 11349)
[1] 117
[課題2-2の答え]
euclid <- function(a, b) {
    if (b > a)  { c <- a; a <- b; b <- c }
    c <- a %% b
    if (c == 0) return(b)
    return(euclid(b,c))
}

繰り返し: for

Rのfor文は、以下に上げる基本形からわかるように、プログラミング言語Cとは書き方が異なる(プログラミング言語Rubyと似ている)ので注意:
     for (変数  in 繰り返しの集合)  { 実行内容 } 
繰り返し集合としては、あとで述べるベクトルや行列、リストなど、いろいろなデータ構造が使える。 まずその構造の最初の要素が「変数」の値にセットされ「実行内容」が実行される。そして「変数」には『繰り返し集合」の次の要素がセットされ「実行内容」が実行される...ということが、最後の要素まで繰り返される。

例で説明しよう。

>  for (item in c("apples", "bananas", "cheese"))  cat("I love ",item, "very much\n")
I love  apples very much
I love  bananas very much
I love  cheese very much
一回目の実行では、「繰り返し集合」の最初の要素である"apples"がitemの値となり、 cat("I love ",item, "very much\n")が実行される。そのため、画面には、 I love apples very muchが表示される。
次に 2番めの要素 "bananas"がitem の値にセットされ、 cat("I love ",item, "very much\n")が実行される。そのため、画面には、 I love bananas very muchが表示される。そして最後の要素"cheese"がitemの値にセットされ、I love cheese very muchが表示される。これが最後の要素なので繰り返しが終了する。このようにfor文により繰り返し実行が行われる。

この繰返し文は入れ子にすることができる。次は九九の表を出力するプログラムであり、for文を入れ子にして使っている。

>  for (i in 1:9) {	 # i=1, 2, ...として以下を繰り返し
           for (j in 1:9) {		 # 同じiの値に対し、j=1,2,...として以下を繰り返し
		k = i*j					 # i*jを計算
		if (k < 10) cat("  ",k)                  # 10未満の数は空白を余計につけてi*jを表示
                else cat(" ",k)                         
	    }						 # この後jの値が変わる
	   cat("\n")			 # この後iの値が変わる
  }
   1   2   3   4   5   6   7   8   9
   2   4   6   8  10  12  14  16  18
   3   6   9  12  15  18  21  24  27
   4   8  12  16  20  24  28  32  36
   5  10  15  20  25  30  35  40  45
   6  12  18  24  30  36  42  48  54
   7  14  21  28  35  42  49  56  63
   8  16  24  32  40  48  56  64  72
   9  18  27  36  45  54  63  72  81

課題2-3

2つの引数x,yをとりxからyまでの『九九』表を表示する関数kukuを作れ。ただし、1 ≤ x < y < 100 とする。
> kuku(1,9)
    1    2    3    4    5    6    7    8    9
    2    4    6    8   10   12   14   16   18
    3    6    9   12   15   18   21   24   27
    4    8   12   16   20   24   28   32   36
    5   10   15   20   25   30   35   40   45
    6   12   18   24   30   36   42   48   54
    7   14   21   28   35   42   49   56   63
    8   16   24   32   40   48   56   64   72
    9   18   27   36   45   54   63   72   81
> kuku(11,19)
  121  132  143  154  165  176  187  198  209
  132  144  156  168  180  192  204  216  228
  143  156  169  182  195  208  221  234  247
  154  168  182  196  210  224  238  252  266
  165  180  195  210  225  240  255  270  285
  176  192  208  224  240  256  272  288  304
  187  204  221  238  255  272  289  306  323
  198  216  234  252  270  288  306  324  342
  209  228  247  266  285  304  323  342  361

[課題2-3の答え]

kuku <- function(x,y) { 
             for (i in x:y) {
               for (j in x:y) {
                 k = i*j 
                 if (k < 10) cat("   ",k)  # three " "
	         else { if (k < 100) cat("  ",k) # two " "
                 else cat(" ",k) }
               } 
               cat("\n")
             }
        }
参考:ただし、これだと4桁の数が出てくると表示に問題を引き起こす。 C言語のsprintf関数に似たR組込みのsprintf関数を用いれば、 もうちょっと簡単にできる:
kuku <- function(x,y) { 
             for (i in x:y) {
               for (j in x:y) {
                 cat(sprintf("%5d",i*j)) 
               } 
               cat("\n")
             }
        }

繰り返し: whileとrepeat

forが特定の集合の要素に対して、もしくはある決まった回数だけ指定された内容を繰り返し実行するのに対し、whilerepeatはある条件が成り立っている間繰り返し実行する、という違いがある。

whileとrepeatの基本形は以下である:

     while (条件)  { 実行内容 } 

     repeat { 実行内容 }
repeatはwhileと異なり、繰り返しを継続する条件を書くところがない。つまり、 repeatでは「実行内容」の中に、いつ繰り返しを止めるかを書かなければならない。

whileにおいて、実行内容を繰り返しているときに、いつかは『条件』が偽とならなければならないことに注意。そうでないと永遠に繰り返しが行われる---これを無限ループという。

注意: 無限ループに陥った場合のように、もしもRから反応がなくなった場合は、ESCキーを押すこと。

次のプログラムは、ベクトルitemsの要素の中でtargetと等しいものがあったときに"Found"を表示し、なければ"Not found"を表示する「探索」プログラムである。

  i <- 1		# ベクトルの要素の番号を管理する(カウントする)変数。このような変数をカウンター変数と呼ぶ
  len <- length(items)	 # lenはベクトルitemsの要素数 
  while ((i <= len) && (items[i] != target)) {		# i <= len かつ items[i] != targetならば以下を繰り返す 
     i <- i+1 			 # iの値を更新 
  }
  if (items[i] == target) cat("Found") else cat("Not found")	 # 繰り返しが終了した理由により出力を変える 

課題2-4

上で取り上げた「探索」プログラムを関数として表せ。関数名をsearchとし、itemsとtargetという2つの引数をとるものとする。また、その関数はreturnにより"Found"か"Not found"を返すようにせよ。なお、上の関数は条件items[i] == targetを最後に調べて答えを返すようにしているが、これはwhileの条件としても現れており、二度手間のようにみえる。if文とreturnをうまく使うことで、同じ条件を二度調べなくてもよいように工夫せよ。

以下は実行例である:

> r <- c(1,3,5,9,11,17,8,7,21,13)
> search(r,7)
"Found"
> search(r,100)
"Not found"

[課題2-4の答え]
search <- function(items, target) {
    i <- 1			# ベクトルの要素の番号を管理する(カウントする)変数。このような変数をカウンター変数と呼ぶ
    len <- length(items)
    while (i <= len) {		# i <= len でなければ以下を繰り返す 
      if (items[i] == target) return("Found")		# 見つかった
      i <- i+1 		 # iの値を更新---これを忘れると無限ループする
     }
     return("Not found")	 # これが実行されるのは見つからなかった場合
  }

課題2-5

課題2-4の探索関数をforを使って書け。ただし関数名をlook.forとする。

[課題2-5の答え]

ご覧のとおり、この問題ではforを使ったほうが簡潔に書ける。
look.for <- function(items, target) {
    for (x in items) {		 # xにitemsの要素が次々セットされる
       if (x == target) return("Found")		 # 見つかった
    }
     return("Not found")	 # これが実行されるのは見つからなかった場合
  }

繰り返しの制御: breakとnext

forやwhileにより何らかのプログラムを繰り返している時に、途中で繰り返しを打ち切りたいとか、繰り返すべき以降の実行を打ち切って次の繰り返しに移行したい、ということがある。前者は関数ならばreturnを使えばよいが、関数でなければそうはいかない(whileの例題プログラムと課題1-6の関数とを比較)。

繰り返しの打ち切りにはbreakを用いる。breakにより繰り返しが終了となる。ただし、繰り返しが入れ子で用いられている場合は、打ち切られるのは一番内側の繰り返しだけであり、外側の繰り返しは継続されることに注意しよう。

次はベクトルの要素を順に一つ一つ見て、偶数があればそれを表示して終了するプログラムである。

> ar <- c(1,3,5,9,11,17,8,7,21,13)
> for (x in ar) { if (x %% 2 == 0) { cat(x); break} }
8
なお上のプログラムではbreakがなくても結果は変わらないが、breakがあるとそこで繰り返しが終了するため効率がよい。

繰り返しの中で実行を打ち切り、次の繰り返しに移行するにはnextを使う。 次はベクトルの要素のうち奇数のものを表示するプログラムである(参考: ベクトルとして返すのならもっと簡単な方法がある)。

> ar <- c(1,3,5,9,11,17,8,7,21,13)
> for (x in ar) { 
	if (x %% 2 == 0) next
        cat(x," ") 
     }
1  3  5  9  11  17  7  21  13 

データ構造

数値型や論理型のようなデータ型は基本的に一個のデータに対するデータの種類である。 それに対し、ベクトルのような「データの集まり」の種類のことをここではデータ構造と呼ぶことにする。

ベクトルは基本的なデータ構造であるが、Rは以下に上げるようにほかのプログラミング言語よりも多くのデータ構造を提供している。これらについて解説する。

    ・ベクトル       同じデータ型の要素の集まり(1次元データ)
    ・行列           同じデータ型の要素の2次元データ(1次元データであるベクトルの集まり)
    ・配列           同じデータ型の要素の3次元以上のデータ(3次元なら、行列の集まりとみなせる)
    ・リスト         いろいろなデータの集まり(型が混在してよく、ベクトルや行列なども要素となる)
    ・データフレーム  任意の型のベクトルの表形式の集まり(データ分析のための関数が扱う基本型)

ベクトル

ベクトルはRにおける最も基本的なデータ構造であり、「同じ型のデータ」の1次元の集まりである。ここで、『同じ型』というのが重要である。前節で見たように、 c(1,2,"abc")ではすべて文字型に自動的に変換された。これはベクトルの性質上、 すべて同じ型でないといけないからで、数値型のデータはここでは文字型のデータに変換されたのである。

行列

行列は「同じ型のデータ」の2次元の集まりである。「2次元」ということは要するにデータが表で表され、行(表における横並びのデータの集まり)と列(表における縦並びのデータの集まり)から構成されるとみなされる。

次の例は、3 x 4の行列を作るものである。ここで3 x 4は「3行4列」と呼ぶ。

> m <- matrix(1:12, 3, 4) 		# 3x4の行列を作る
> m
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
> class(m)
[1] "matrix"
表示させてみてわかるように、横に3行、縦に4列数が並んでいる。 これからも3行4列の行列であることが確認できる。 また要素が縦(列方向)から埋まっていくことがわかる。 また行列の型はベクトルのように要素の型ではなく、 "matrix"と表示される。

ここで、「要素が縦(列方向)から埋まっていく」のは、行列が、複数個の列ベクトルから構成されていることを表していると考えられる。実際に、x, y, zというベクトルがあるとき、それらから構成される行列は、以下のように作られる:

> x  <- c(0,1,1)          	 # ベクトル x
> y  <- c(1,0,1)          	 # ベクトル y
> z  <- c(1,1,0)          	 # ベクトル z
> m <- matrix(c(x,y,z),3,3) 		# x, y, zの3つのベクトルから3x3の行列を作る
> m
     [,1] [,2] [,3] 
[1,]    0    1    1 
[2,]    1    0    1 
[3,]    1    1    0 

なお、横(行方向)から値を埋めた行列を作ることもできる。それには、次のようにbyrow=TRUEという引数を加えれば良い(註: 以下で用いた変数Tは初期値としてTRUEが与えられている。同様にFはFALSEが初期値である)。

>  n<-matrix(1:12,3,4,byrow=T) 		# 3x4の行列を作る
> n
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12
行列の次元について調べる関数として、dim, nrow, ncolがある。 dimは次元(dimension)の先頭3文字を表し、nrow, ncolは それぞれnがnumber(数), rowは行、colは列(column)を表す。つまり、それぞれ行の数と列の数を返す関数である。
> dim(m)
[1] 3 4
> nrow(m)
[1] 3
> ncol(m)
[1] 4
行列は数学の世界ではよく使われる表現なので、Rでもそれに関する関数や演算子が多数用意されている。そのいくつかを例によって紹介する:
> (I <- diag(3) )		# diag(n)はn x nの単位行列を作る
      [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
> ( m <- matrix(c(1, 2, 0, 0, 2, 1, -1, 2,1),3,3) )		# 3x3 の行列を作る
     [,1] [,2] [,3]
[1,]    1    0   -1
[2,]    2    2    2
[3,]    0    1    1
> ( n <- t(m) )		# mの転置行列を作る
     [,1] [,2] [,3]
[1,]    1    2    0
[2,]    0    2    1
[3,]   -1    2    1
> ( m.n <- m+n )		# 行列の和を作る。同様に差も作れる
     [,1] [,2] [,3]
[1,]    2    2   -1
[2,]    2    4    3
[3,]   -1    3    2
> det(m)		# 行列式
[1] -2
> ( mInv <- solve(m) )		# 逆行列---行列式が0でなければ存在する
     [,1] [,2] [,3]
[1,]    0  0.5   -1
[2,]    1 -0.5    2
[3,]   -1  0.5   -1
> m %*% mInv		# 行列の積 --- 逆行列との積であるから単位行列になるはず
      [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
これ以外にも、固有値・固有ベクトルを求める関数eigen、QR分解を行う関数qrなどが用意されている。

課題3-1

行列の逆行列、および行列の固有値・固有ベクトルについて、その意味(定義や応用)を調べよ。 なおこれらについては、線形代数学で学習しているはずだが、どちらも行列を使うときには重要な概念なので復習しておこう。

行列の要素のアクセス方法を次の例で示す。

> ( m <- matrix(c(1, 2, 0, 0, 2, 1, -1, 2,1),3,3) )		# 3x3 の行列を作る
     [,1] [,2] [,3]
[1,]    1    0   -1
[2,]    2    2    2
[3,]    0    1    1
> m[1, ]		# mの1行目を取り出す
[1]  1  0 -1
> m[,2]		# mの2列目を取り出す
[1] 0 2 1
> m[2,3]		# mの2行3列目の要素を取り出す
[1] 2
> m[2,3] <- 5 ; m		# mの2行3列目の要素を5にする
     [,1] [,2] [,3]
[1,]    1    0   -1
[2,]    2    2    5
[3,]    0    1    1
> m[c(1,3),]		# mの1行目と3行目の要素を取り出す---取り出されたものは行列
     [,1] [,2] [,3]
[1,]    1    0   -1
[2,]    0    1    1
> m[,c(1,3)]		# mの1列目と3列目の要素を取り出す---取り出されたものは行列

     [,1] [,2]
[1,]    1   -1
[2,]    2    5
[3,]    0    1
> rowSums(m)		# 行の総和---結果はベクトル
[1] 0 9 2
> colSums(m)		# 列の総和---結果はベクトル
[1] 3 3 5
これ以外にも、行ごとの平均をベクトルで返す関数rowMeans、列ごとの平均を返す関数colMeansなどがある。

配列

配列は、同じ型のデータが多次元に配置されたものであり、関数arrayによって作られる。

リスト

リストは複数の型のデータを混在させたデータの集まりである。リストをつくるには関数listを用いる。 リストの要素をすべて一つのベクトルにまとめる関数unlist、これと逆にベクトルをある基準で分けてリストとしてまとめる関数splitが用意されている。

データフレーム

データフレームは、データ分析において最もよく使われるデータ構造である。 これは行列などで見てきた考え方や関数が使えるが、データフレーム特有のものも多数ある。 データ分析を行うにはデータフレームに慣れておく必要があるだろう。

データフレームについては別項で紹介する。


関数の引数について

Rでは関数の引数を名前付きで呼ぶことにより引数の順番に縛られずに関数をよぶことができる。また、引数を省略した時の振る舞いも設定することができる。これについて次の累乗を計算する関数の定義を例にとって解説する。
  power <- function(x, y) { x^y }		# x**yを返す
この関数は次のようにして引数名を指定して呼び出すことができる。見てわかるように、引数名を指定した時には、引数の順番に縛られなくなる。
> power(2,10) 		# 2**10の計算
[1] 1024
> power(y=2,x=10)		# 10**2の計算
[1] 100
また、これを次のように改変すると、yの値を省略でき、省略した時の値(デフォルト値という)を10として計算が行われる:
> power10 <- function(x, y=10) { x^y }		# x**yを返す
> power10(2,10) 		# 2**10の計算
[1] 1024
> power10(y=2,x=10)		# 10**2の計算
[1] 100
> power10(2)		# 2**10の計算---yの値が省略されている
[1] 1024

課題4-1

以下のことを前提知識とする(怪しい場合はRの基礎(1)から見直すこと): 以下はその実行例である:
> z <- NULL		# NULLは空ベクトル
> ( z <- c(z, 5) )
[1] 5			# 5を要素とするベクトル
> ( z <- c(z, c(10,15)) )
[1] 5 10 15			# c(5, 10, 15)と同じベクトル
> ( z <- c(0,z) )
[1] 0 5 10 15			# c(0, 5, 10, 15)と同じベクトル
xとyはそれぞれ昇順にソートされた(小さいものから大きなものへと要素が並んでいる)ベクトルとする。これらの要素をすべてもち、かつ昇順にソートされたベクトルを返す関数mergeを作れ。もちろん、Rの関数を組み合わせて簡単にやる方法もあるが、ここでは次のアルゴリズムによって行うものとする:
  1. ixを1、iyを1とする。zをNULLとする。(意味: zにxとyの要素をいれる)
  2. ix <= length(x)かつiy <= length(y)である限り次を繰り返す(意味: xもyもまだzに入れられていない要素がある限り次を繰り返す)
    x[ix] < y[iy] ならばz < c(z,x[ix])ix <- ix+1 とする。さもなくば、z < c(z,y[iy])iy <- iy+1 とする。
    (意味: xとyでzに入れられていない先頭の要素(ix, iyがそういう要素の番号なので、x[ix]y[iy]がその要素となる)を比較し、小さい方をzにいれる)
  3. ix > length(x)ならばz <- c(z,y[iy:length(y))、さもなくば z <- c(z,x[ix:length(x))を行う
    (意味: 繰り返しが終了した、ということはxかyのどちらかの要素がzに入れられないまま残っているはず。それをすべてzに付け加える)
  4. zを返す
次はその実行例である:
> a <- seq(2,20,3)		# c(2,5,8,11,14,17,20)
> b <- seq(3,21,3)		# c(3,6,9,12,15,18,21)
> merge(a,b)
 [1]  2  3  5  6  8  9 11 12 14 15 17 18 20 21
> merge(b,a)
 [1]  2  3  5  6  8  9 11 12 14 15 17 18 20 21

[課題4-1の答え]
merge <- function(x,y) {
    ix <- 1; iy <- 1
     xlast <- length(x); ylast <- length(y)
     z <- NULL
     while ((ix <= xlast) && (iy <= ylast)) {
       if (x[ix] < y[iy]) {
   	 	z <- c(z,x[ix]) 
    		ix <- ix+1
       } else {
            	z <- c(z,y[iy])
    		iy <- iy+1
       }
     }
     if (ix > xlast)  z <- c(z,y[iy:ylast]) else
        z <- c(z,x[ix:xlast])
     return(z)
}

課題4-2

課題4-1と同じ結果を出す関数mergeRを、今まで学んだRの関数を使い1行で作れ。
[課題4-2の答え]
mergeR <- function(x,y) sort(c(x,y))
とする。以下はその実行例である:
> a <- seq(2,20,3)		# c(2,5,8,11,14,17,20)
> b <- seq(3,21,3)		# c(3,6,9,12,15,18,21)
> mergeR(a,b)
 [1]  2  3  5  6  8  9 11 12 14 15 17 18 20 21
> mergeR(b,a)
 [1]  2  3  5  6  8  9 11 12 14 15 17 18 20 21

このページで出てきたRの関数や記号(出てきてはいないが関連するものを含む)


演習問題

演習問題2-1

(1) まずあなたの学籍番号の下二桁の数 N (0≦n≦99) により、 次のようにして変数xに25個の要素を持つベクトルを値として与えよ。 以下は学籍番号がT214099の場合である。
> N <- 99   # 学籍番号がT214099 なので N=99
> set.seed(N)
> x <- runif(25,1,10)     # 最小値1,最大値10の乱数を25個生成しベクトルを作る

(2) このベクトルから5 x 5の行列を作り、変数yの値とせよ。

(3) この逆行列を求め、変数zの値とせよ。もしも「特異のためエラー」 になったとしたら、再度x <- runif(25,1,10) として、(2)からやり直すこと。

(4) yとzの「行列の積」を求め、単位行列である、 もしくは単位行列に十分近いことを確認せよ (単位行列とはどんなものか忘れていませんよね?)。

演習問題2-2 (下にある『天気の遷移確率行列について』の項目参照のこと)

ある町の天気は必ず晴れ、曇、雨のいずれかであり、前日の天気によって次の日の天気が以下のように決まるものとする(言い換えれば、この町の天気はマルコフ過程でモデル化できる---マルコフ過程を知らない者は各自調べること)。 (1) 上の規則から、天気の遷移確率行列を行列として表し、変数Mの値とせよ。
ヒント:以下の様なコードになる:
M <- matrix(c(...), 3, 3)                # ...の部分を適切なもので埋めよ
なお、この町のある日の天気の状態を「晴れ、曇、雨」の確率からなるベクト ルで表すとする。例えば、c(0.7, 0.3, 0.0)(注意: これは列ベクトルである)は、その日が晴れである確率が0.7、曇である確率が0.3、雨である確率が0、であることを表すとする。すると、Mとこのベクトルの積 M %*% c(0.7, 0.3, 0.0) は次の日の天気の状態を表す。
[天気の遷移確率行列について]
ここで考えている天気の状態のモデルは1次マルコフ過程と呼ばれるものである。 「1次」とは、ある日の天気の確率が、前日の天気の状態だけで決まり、 以前の天気の状態とは無関係であることを意味する (それ以前の状態にも依存するなら、その個数に応じて2次、3次、...と呼ばれる)。
ある日の状態を○で表すこととしよう。天気が晴、曇、雨のいずれかに決まるとすれば、その日の状態はこのいずれかになる。そしてこの日の天気によって次の日の天気が確率的に決まることは下の図のように表される:
Weather transition map

左に1日目、真ん中に2日目、右に三日目の状態が示されている。前の日の状態から次の日の状態の遷移は矢印で示され、数はその確率を表している。そして、天気の遷移はこれは永遠に続く。 Weather transition map without time
もしも時間の推移を無視して天気の状態だけの移り変わりに注目すると、右図のように表すことができる。

いま、天気の状態をベクトル(注意:くどいようだが「列ベクトル」)で表すとしよう。(x,y,z)は、 晴れである確率がx, 曇りである確率がy, 雨である確率がzであるとする。 確率なので、0 ≦x,y,z≦1であり、x+y+z=1.0である。
状態遷移確率とはこの場合、天気の状態がどのように変化するかという確率である。 いま与えられた規則から、考える日の天気によって、次の日が晴れ、曇、雨となる確率が変わる。ここで確率pjkを天気状態がkであったときに次の日の天気状態がjとなる確率を表すとする。ここで、晴れ、曇、雨をそれぞれ1,2,3で表すとする。 すると、p21は晴れから曇りに変わる確率であり、上の規則から0.3となる。
遷移確率行列とは、状態遷移確率を要素に持つ行列M = {pjk}のことである。だから、今の問題ではMは次の行列として表される:
0.70.40.2
0.30.40.5
0.00.20.3

そして、ある日の天気の状態(ベクトル)とこの問題の「天気の遷移確率行列」M との積により、次の日の天気の状態(ベクトル)が求まる。例えばある日の天気が晴れとすると、この状態は(1.0, 0, 0)というベクトルで表される。 そのベクトルとMとの積(行列Mに対しベクトルを右から掛ける)は(0.7,0.3, 0.0)となり、これは次の日の天気の状態(確率)を表す。さらにこれとMとの積は(0.61, 0.33, 0.06)となり、その次の日の天気の状態を表す。Rで書くと次のようになる:

> tenki.0 <- c(1.0, 0, 0)             # ある日の天気---晴れ
> tenki.1 <- M %*% tenki.0            # 次の日の天気
     [,1] 
[1,]  0.7 
[2,]  0.3  
[3,]  0.0
> tenki.2 <- M %*% tenki.1            # その次の日の天気
     [,1]
[1,] 0.61
[2,] 0.33
[3,] 0.06

(2) 最初の日(1日目)を(a)晴、 (b)曇、(c)雨とそれぞれ定めた時、(a), (b), (c)のそれぞれにおいて「次の日(2日目)と3日目の天気の状態(確率)」を求めよ。 [答えを書いておく---計算方法を答えること]

天気の確率を(晴、曇、雨)と表す。したがって、晴の日は (1,0,0)となる。
(a)1日目が晴の場合、2日目の天気は (0.7, 0.3, 0), 3日目の天気は(0.61, 0.33, 0.06)
(b)1日目が曇の場合、2日目の天気は (0.4, 0.4, 0.2), 3日目の天気は(0.48, 0.38, 0.14)
(c)1日目が雨の場合、2日目の天気は (0.2, 0.5, 0.3), 3日目の天気は(0.40, 0.41, 0.19)

(3)Mの2乗, 4乗、8乗、16乗、256乗をそれぞれ求めよ。 ここでMの2乗とはM同士の行列の積、Mの4乗とはMを4回(行列として)掛け算した結果(8乗、16乗、256乗も同様)とする。また256が2の8乗であることを利用して、効率よく求める方法を工夫せよ。

(4) (3)の結果を用いて、1日目が晴、曇、雨、それぞれの場合、(8+1=)9日目と(256+1=)257日目、それぞれの天気の確率を求めよ。

(5) この町において、任意に日を選んだ時、その日が晴れ、曇、雨、それぞれである確率を求めよ。 ヒント: 天気の定常状態を考える。
[定常状態とは]

天気の遷移を無限回繰り返したとすると、天気の状態は(1)周期的に変化する、 もしくは(2)ある一定の状態に収束する、のいずれかになる。ここで後者の場合、 つまりある一定の状態に収束したものを「定常状態」と呼ぶ。 この定常状態をxで表すと、上の性質から、(数式で表すと)次のように表される: x = M ・ x
これを解いて、定常状態を求めることができる。

前の解説を見る        トップページに戻る        次の解説を見る