Rの基本的なデータ構造と制御構造について学ぶ
今回の学習項目:
> 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の答え]
> 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("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.numeric("17")+16 [1] 33 > as.logical(1) [1] TRUE
> plus("17","16") [1] 33[課題1-2の答え]
plus <- function(x,y) { 関数本体 }ここで関数本体は、xとyをそれぞれ数値型に変換したものを足せば良い。したがって、 以下が答えとなろう:
plus <- function(x,y) { as.numeric(x)+as.numeric(y) }
> sumAll(c("1","3","5","7")) [1] 16[課題1-3の答え]
sumAll <- function (z) { sum( as.numeric(z) ) }
この問題について、過去にはかなりの人が次のように書いて失敗してました。
> plus <- function(x,y){ return(as.numeric("x","y")) } > plus("17","16") [1] NA # おかしな結果…これには二つの間違いが含まれています。 まず、
if (条件式) { 実行内容 }例: timeの値が0であるときに 、Time is up!と表示するプログラム(コード)。 ここで、catは、画面に文字列などを出力する関数である。
> time <- 1 > if (time == 0) { cat("Time is up!") } # 何も表示されない > time <- 0 > if (time == 0) { cat("Time is up!") } Time is up!
if (条件式) { 条件式が真の時に実行する内容 } else { 条件式が偽の時に実行する内容 }例: ageの値が20未満なら「未成年だからアルコールはダメ」 そうでなければ「お酒はほどほどに..」と表示するプログラム(コード)。
if (age < 20) { cat("未成年だからアルコールはダメ\n") } else cat("お酒はほどほどに...\n")[if文の書き方について]
> if (age < 20) cat("未成年だからアルコールはダメ\n") > else cat("お酒はほどほどに...\n") エラー: 予想外の 'else' です ( " else" の)これがエラーを引き起こすのは、「if文が一行で終わってしまっている」と判断されてしまうためである(その証拠に、elseの入力の前に > という入力プロンプトが出ている。継続行ならば + が表示されるはずである)。そこでこの例では波括弧 } を「書かない」ことで次につながっていることを示している。
if (age < 20) cat("未成年だからアルコールはダメ\n") else cat("お酒はほどほどに...\n")
変数名 <- if (条件式) 真のときの値 else 偽の時の値例: examの値が60以上なら"Pass"を、そうでなければ"Fail"をgradeの値にセットする。
grade <- if (exam >= 60) "Pass" else "Fail"
> type(3) 数値型 > type(TRUE) 論理型 > type("abc") 文字型 > type(Inf) 数値型
factorial <- function(n) { if (n == 0) return(1) return(n*factorial(n-1)) }
type <- function(x) { if (class(x) == "character") return("文字型") else { if (class(x) == "logical") return("論理型") else { if (class(x) == "numeric") return("数値型") else return("未知の型") } } }
前提: 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 (変数 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の値となり、
この繰返し文は入れ子にすることができる。次は九九の表を出力するプログラムであり、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
> 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言語の
kuku <- function(x,y) { for (i in x:y) { for (j in x:y) { cat(sprintf("%5d",i*j)) } cat("\n") } }
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") # 繰り返しが終了した理由により出力を変える
以下は実行例である:
> r <- c(1,3,5,9,11,17,8,7,21,13) > search(r,7) "Found" > search(r,100) "Not found"
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の答え]
look.for <- function(items, target) { for (x in items) { # xにitemsの要素が次々セットされる if (x == target) return("Found") # 見つかった } return("Not found") # これが実行されるのは見つからなかった場合 }
forやwhileにより何らかのプログラムを繰り返している時に、途中で繰り返しを打ち切りたいとか、繰り返すべき以降の実行を打ち切って次の繰り返しに移行したい、ということがある。前者は関数ならば
繰り返しの打ち切りには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なお上のプログラムでは
繰り返しの中で実行を打ち切り、次の繰り返しに移行するには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次元なら、行列の集まりとみなせる) ・リスト いろいろなデータの集まり(型が混在してよく、ベクトルや行列なども要素となる) ・データフレーム 任意の型のベクトルの表形式の集まり(データ分析のための関数が扱う基本型)
次の例は、
> 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列の行列であることが確認できる。 また要素が縦(列方向)から埋まっていくことがわかる。 また行列の型はベクトルのように要素の型ではなく、
ここで、「要素が縦(列方向)から埋まっていく」のは、行列が、複数個の列ベクトルから構成されていることを表していると考えられる。実際に、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
なお、横(行方向)から値を埋めた行列を作ることもできる。それには、次のように
> 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(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これ以外にも、固有値・固有ベクトルを求める関数
行列の要素のアクセス方法を次の例で示す。
> ( 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これ以外にも、行ごとの平均をベクトルで返す関数
データフレームについては別項で紹介する。
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
> 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の関数を組み合わせて簡単にやる方法もあるが、ここでは次のアルゴリズムによって行うものとする:
> 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
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) }
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
> N <- 99 # 学籍番号がT214099 なので N=99 > set.seed(N) > x <- runif(25,1,10) # 最小値1,最大値10の乱数を25個生成しベクトルを作る
(3) この逆行列を求め、変数zの値とせよ。もしも「特異のためエラー」 になったとしたら、再度x <- runif(25,1,10) として、(2)からやり直すこと。
(4) yとzの「行列の積」を求め、単位行列である、 もしくは単位行列に十分近いことを確認せよ (単位行列とはどんなものか忘れていませんよね?)。
M <- matrix(c(...), 3, 3) # ...の部分を適切なもので埋めよなお、この町のある日の天気の状態を「晴れ、曇、雨」の確率からなるベクト ルで表すとする。例えば、
左に1日目、真ん中に2日目、右に三日目の状態が示されている。前の日の状態から次の日の状態の遷移は矢印で示され、数はその確率を表している。そして、天気の遷移はこれは永遠に続く。
もしも時間の推移を無視して天気の状態だけの移り変わりに注目すると、右図のように表すことができる。
いま、天気の状態をベクトル(注意:くどいようだが「列ベクトル」)で表すとしよう。
状態遷移確率とはこの場合、天気の状態がどのように変化するかという確率である。
いま与えられた規則から、考える日の天気によって、次の日が晴れ、曇、雨となる確率が変わる。ここで確率pjkを天気状態がkであったときに次の日の天気状態がjとなる確率を表すとする。ここで、晴れ、曇、雨をそれぞれ1,2,3で表すとする。
すると、p21は晴れから曇りに変わる確率であり、上の規則から0.3となる。
遷移確率行列とは、状態遷移確率を要素に持つ行列M = {pjk}のことである。だから、今の問題ではMは次の行列として表される:
0.7 | 0.4 | 0.2 |
0.3 | 0.4 | 0.5 |
0.0 | 0.2 | 0.3 |
> 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日目の天気の状態(確率)」を求めよ。 [答えを書いておく---計算方法を答えること]
(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) この町において、任意に日を選んだ時、その日が晴れ、曇、雨、それぞれである確率を求めよ。
ヒント: 天気の定常状態を考える。
[定常状態とは]