をプログラムとして見たときに注意・検討すべきところを学んでおきたい、ということで
を読んでいく。
前回
2. ベクトル
において基本になるデータ型はベクトルである。のベクトルにおいて重要な概念は以下の3つにある:
リサイクル | 特定の状況下においてベクトルの長さを自動的に伸長させる。 |
---|---|
フィルタリング | ベクトルの部分集合を抽出する。 |
ベクトル化 | ベクトルに対して成分(要素)ごとに関数を適用させる。 |
2.1 スカラー、ベクトル、配列、行列
が他の言語に比較して特徴的な事の1つは、スカラーに相当するデータ型(ではモードと呼ぶ。)を持たないことである。すなわちはスカラーを成分数がであるようなベクトルとして扱う。
ベクトルのすべての成分は同じモードでなければならない。もしあるベクトルのモードを知りたければ関数を用いればよい。
のベクトルインデックスはから始める。
2.1.1. ベクトル要素の追加と削除
のベクトルは要素の挿入や削除ができない。ベクトルの大きさは作成時に決まり要素の追加や削除にはベクトルを再度割り当てる。
# 例:ベクトルxについて3番目の要素と4番目の要素の間にスカラーを入れたい x <- c(88,5,12,13) # 4つの要素を持つベクトルとして定義 x <- c(x[1:3],168,x[4]) x
上記の例の2行目のように、まずベクトルの1番目から3番目および、の4番目の成分を持つような要素数がのベクトルを新たに定義し、それをに割り当てるのである。このようにはポインタであり、が新たに作成したベクトルを指すようにすることで再割り当てを実現しているのである。このため、場合によってはのパフォーマンス向上を制約しかねない。
2.1.2 ベクトルの長さの取得
においてベクトルの長さを取得するには関数を用いる。
たとえばあるベクトルについて特定の値が格納されている最初のインデックスが欲しい場合には文にの要素を直接ループさせては失敗する。そうした場合にはでループを回す方が確実である。
またエラー処理などでがになる可能性を考慮するにも有用である。
2.1.3 ベクトルとしての配列と行列
において配列と行列もクラス属性が付与されたベクトルである。そのため配列と行列にもベクトルに関する説明が当てはまる。
2.2 宣言の必要性
では変数を明示的に予め宣言する必要はない。他方でベクトルの特定要素を参照するには事前にそのベクトルを宣言しなければならない。これはベクトルの個々の要素を読み書きするのに内部で動作している関数が、その対象変数がベクトルであることを事前に知らないとうまく対応できないからである。たとえば
z <- 3 #これは正しい y[1] <- 3 # これはyを宣言していなければエラー ##### y <- 1:13 y[1] <- 2 # これはOK
割り当てをするのにモードによる制約はない。
x <- c(1,5,13) x <- "abc" # これは許容される
2.3 リサイクル
2つのベクトルに長さが同じでないといけない演算を適用すると、はリサイクルを行う。リサイクルとは、長い方の長さに一致するまで短い方のベクトルがその要素を繰り返すことである。
c(1,2,3) + c(1,2,3,4) # この演算はc(1,2,3,1) +c(1,2,3,4) = c(2,4,6,5)になるはずである x <- matrix(1:6, nrow = 3, ncol =2)) x + c(1,2) #行方向から列方向にベクトルが繰り返される
2.4 一般的なベクトル操作
2.4.1 ベクトルの算術演算と論理演算
は関数型言語であるから、すべての演算子は関数である。算術演算子は関数で、これらはベクトルに対しては要素ごとに行われる。
"+"(2,3) # 2 + 3のこと 5 + c(1,2) # スカラーは要素数が1のベクトルであったから、リサイクルが起きて、前者はc(5,5)として後者の各成分に5が足された値が返ってくる # 算術演算子は要素ごとに行われる x <- c(1,2,4) x * c(5,0,-1) #要素ごとに積を取る x / c(5,4,-1) # c(0.2, 0.5, -4.0) x %% c(5,4,-1) # c(1,2,0)
2.4.2 ベクトルのインデックス付け
で最も扱う演算子にベクトルのインデックス付けがある。あるベクトルの特定のインデックス要素を選ぶこと、すなわちといった形式でサブベクトルを作成できる。関数を組み合わせたりすることで利便性が向上する。
y <- 1:10 y[1:5] # 1,2,3,4,5 v <- 3:4 y[3:4] # 3,4 y[c(3,3,3)] # 3,3,3と重複は許容される y[c(5,2,1)] # 5,2,1と順番を変えることも可能 y[-1] # 2,3,4,5,6,7,8,9,10 と負値は指定した要素を除くことを意味する y[1]==y[-c(2:9)] #TRUE y[-length(y)] # 最終要素以外をすべて取得する
2.4.3 演算子を用いたベクトルの生成
ベクトルを生成するのに便利な関数がいくつか存在する。
5:8 # 5から8までのすべての整数を要素にそのまま昇順で持つベクトルを返す 5:1 # 5から1までのすべての整数を要素にそのまま降順で持つベクトルを返す i <- 5 # 以下は異なる値を返すので注意 1:i-1 # c(1,2,3,4,5)の各要素から1が引かれたベクトルになる 1:(i-1) # c(1,2,3,4)
なおにおける演算子の優先順位はで閲覧できる。
2.4.4 seq()
は:を汎用化したもので、等差数列を作成する。等差は小数でも問題ない。また文のイテレーターを明示的に書きたい場合に、もしがだったときの挙動がより望ましい。
x <- NULL for(i in 1:length(x)){ print(1) # 2回表示される=ループが回っている } for(i in seq(x)){ print(1) # 一度も表示されない=ループが回っていない }
2.4.5 rep()
を用いると、指定した成分数のすべての成分の値が等しいベクトルを作成できる。
rep(3,3) # c(3,3,3) rep(c(1,2,3),3) # c(1,2,3,1,2,3,1,2,3)
2.5 all()とany()
は引数のすべてもしくは少なくとも1つが真()であるか否かを返す。
x <- rep(1,5) all(x>0) # TRUE x[1] <- -1 all(x>0) # FALSE y <- c(rep(-1,5),1) all(y <-1) # FALSE any(y < -1) # TRUE
2.5.1 例:連続値を見つける
が並ぶ文字列におけるの連続を見つけるときに、なるべく効率的に行いたい。
素朴には以下のような関数が考えられる。
### 0,1のみからなる文字列の中で連続した1についてその連続数を塊分だけ取って返す findruns <- function(x, k){ n <- length(x) runs <- NULL for(i in 1:(n-k+1)){ if(all(x[i:(i+k-1)]==1)){ runs <- c(runs,i) } } return(runs) }
これはを積み上げる点が望ましくない。ベクトルの割り当ては時間が掛かる処理で、を割り当てる度にスクリプトの実行速度が下がる。そこでを長さのベクトルとして予め宣言すればよい。
### 改良版 findruns1 <- function(x, k){ n <- length(x) runs <- vector(length = n) count <- 0 for(i in 1:(n-k+1)){ if(all(x[i:(i+k-1)]==1)){ count <- count + 1 runs[count] <- i } } if(count > 0){ runs <- runs[1:count] }else{ runs <- NULL } return(runs) }
2.5.2 例:離散値時系列の予測問題
雨が降る()、降らない()として記録した時系列データを基に将来の降雨を予測することを考える。ここでは直近日間の降雨数(の数)が以上の場合に雨が降ると予測することとする。問題は適当なを選択することにある。
ここでは訓練データとして日間間の天気データを基にたとえばとしたときの精度を考えて、最適なを考える。そこで具体的な関数としてたとえば以下が考えられる:
pred_a <- function(x,k){ n <- length(x) k2 <- k/2 pred <- vector(length = n-k) for(i in 1:(n-k)){ if(sum(x[i:(i+(k-1))]) >= k2){ pred[i] <- 1 }else{ pred[i] <- 1 } } return(mean(abs(pred - x[(k+1):n]))) }
上記のスクリプトは簡潔である一方、遅くなる。それはループのベクトル化で解決し得る一方で、関数が何度も呼ばれることが障害になる。そこでループのそれぞれの反復において以前の合計を更新する方式を取ることで解消できる。
pred_b <- function(x,k){ n <- length(x) k2 <- k/2 pred <- vector(length = n-k) sm <- sum(x[1:k]) if(sm >= k2){ pred[1] <- 1 }else{ pred[1] <- 0 } if(n-k >= 2){ for(i in 2:(n-k)){ sm <- sm + x[i+k-1] -x[i-1] if(sm >= k2){ pred[i] <- 1 }else{ pred[i] <- 0 } } } return(mean(abs(pred - x[(k+1):n]))) } pred_c <- function(x,k){ n <- length(x) k2 <- k/2 pred <- vector(length = n-k) csx <- c(0,cumsum(x)) for(i in 1:(n-k)){ if(csx[i+k] - csx[i] >= k2){ pred[i] <- 1 }else{ pred[i] <- 0 } } return(mean(abs(pred - x[(k+1):n]))) }
2.6 ベクトル化された演算
ベクトルのすべての要素に適用したい関数()がある場合、そのものに対して()を呼び出しさえすればよい。これは①コードを簡潔にする、②パフォーマンスを大幅に向上させる、という点で非常に望ましい。
2.6.1 ベクトルの入出力
######################## ### ベクトルへの演算 ### ######################## ## u <- c(5,2,8) v <- c(1,3,9) u > v ## w <- function(x) return(x + 1) w(u) ## y <- c(12,5,13) #以下は等価: 4はリサイクルされている y + 4 "+"(y,4)
2.6.2 ベクトル入力と行列出力
#################################### ### 行列を出力するベクトル値関数 ### #################################### z12 <- function(z) return(c(z,z^2)) sapply(1:8, z12)
2.7 NA値とNULL値
におけるに相当するものがには2つある。一方が欠損値(レコードは存在するもののその値が不明なもの)を表すで、もう一方がそもそも値が存在しないことを表すである。
2.7.1 NAの使用
の多くの統計関数では、欠損値()を飛ばすように指示できる。はデフォルトでは飛ばさない一方で、は自動的に飛ばす。
2.7.2 NULLの使用
は無いものとして扱われる。は欠損を表し、無いわけではない。
################## ### NULLの使用 ### ################## # NULLの1つの使い方 z <- NULL for(i in 1:10){ if(i %% 2 ==0){ z <- c(z,i) } } z # しかし以下の方が自然 seq(2,10,2) 2 * 1:5 # NAではこれはできない w <- NA for(i in 1:10){ if(i %% 2 ==0){ w <- c(w,i) } } w
2.8 フィルタリング
におけるベクトルのもう1つの機能としてフィルタリングがある。すなわち「ある条件を満たすベクトル尿素を抽出する」ことができる。
2.8.1 フィルタリング・インデックスの作成
ブール値を持つベクトルをインデックスとして渡すことでフィルタリングすることができる。
################################## ### フィルタリングインデックス ### ################################## z <- c(5,2,-3,8) # インデックスを作ってみる flg <- z^2>8 z[flg] # flgは以下と等価で「ベクトル」に関数を適用している ">"(z^2,8) # 割り当てもインデックスを用いて簡単にできる z[z<3] <- 0
2.8.2 subset()関数を使ったフィルタリング
()関数を用いてもフィルタリングできる。を自動的に省いてくれる点が普通にインデックスを作成するよりも便利である。
### subset()を用いたフィルタリング # NAを削除する手間が省ける x <- c(6, 1:3, NA, 12) x[x>5] subset(x, x>5)
2.8.3 which()関数を使ったフィルタリング
()関数を用いてもフィルタリングできる。
# which()で特定の値の位置を返すこともできる z <- c(5,2,-3,8) which(z^2>8) z[which(z^2>8)]
またベクトル内のある条件が最初に出現する位置を見つけることもできる。
first1a <- function(x){ for(i in 1:length(x)){ if(x[i]==1)break } return(i) } first1b <- function(x)return(which(x==1)[1])
後者の方がコンパクトではあるものの、ベクトルxすべての要素内にあるすべてのを探し無駄が多くなり得るため、一長一短である。
2.9 ベクトル化されたif-then-else
にはベクトル化された()関数がある。第一引数にブール値ベクトル、第二および第三引数にベクトルを充てる。
ベクトル化されているため、単純なif-then-else構文よりも高速さが期待できる。
################ ### ifelse() ### ################ x <- 1:10 ifelse(x%%2==0,5,12)
2.10 ベクトル等価性の検査
では個々の成分の一致性が診断されるため、工夫が必要になる。もしくは単に()を用いる。
x <- 1:3 y <- c(1,3,4) x==y all(x==y) # all()を用いることでベクトルの一致性を診断できるようになる identical(x,y)
ただし()は型までも一致するかを判断する。そのため、
x <- 1:3 y <- c(1,2,3) identical(x,y)
を実行するとを返す。
2.11 ベクトルの要素名
ベクトルの要素には()を用いて名前を付けることや呼び出したりできる。ベクトルから名前を削除するにはを割り当てればよい。
2.12 c()に関する詳細
連結関数()に渡す引数のモードが異なっている場合にはどのモードも含むことができるモードに変換する。
########### ### c() ### ########### c(5,2,"abc") c(5,2,list(a = 1, b = 4)) # c()では、2階層オブジェクトを作らない c(5,2,c(1,5,6))