Rの基本データ構造をもっと理解しよう
自己紹介
- 吉田康久
- id:syou6162
- syouでおk
- Tsukuba大学のM1
Tsukuba.Rの近況(?)その1
- Tsukuba.Rの活動が熊本でも知られているみたいで、ちょっと発表してきた
Tsukuba.Rの近況(?)その2
- RubyKaigiにスタッフとして参加
- クレジットのところにTsukuba.R
- id:wakuteka、id:mickey24、id:negaton、id:syou6162
今回の内容
- 基本に戻って、Rで重要なデータ構造のおさらいをしよう
- 前回(#4)はCでの拡張とかだったので。。。
- データ構造とそれに関連する便利な関数を絡めて紹介
- 慣れている人にも何か新しい発見があるように
- 話の中でいくつかのパッケージを軽く紹介
今回喋るデータ型
- Tsukuba.R#1から出ている人には聞き飽きたかもしれない
- ベクトル
- 行列
- データフレーム
- リスト
「Rで用意されているdata.frame型のデータで遊びたいけど、何があったっけ?」
- ということがあるかもしれない
- というかこの発表を準備している時の俺がそう思っt(ry
- というわけで作った
class_filter <- function(type){ Filter(function(x){ x <- strsplit(x," ")[[1]][1] any(eval(parse(text=paste("class(",x,")",sep=""))) == type) }, data()$results[,3]) }
実際にdata.frame型のデータにどういうものがあるか調べてみる
- 40個くらいあった
- こんだけあれば、なんか面白そうなデータがありそう…じゃないですか?
> head(class_filter("data.frame")) [1] "BOD" "CO2" "ChickWeight" "DNase" "Formaldehyde" [6] "Indometh" > length(class_filter("data.frame")) [1] 43 > class(BOD) [1] "data.frame"
ベクトル
- http://www.okada.jp.org/RWiki/?%A5%D9%A5%AF%A5%C8%A5%EBTips%C2%E7%C1%B4
- Ruby、Javaとかの配列
- Rでは、このベクトル単位でなにごとも進む
> x <- c(1, 2, 3) > y <- c("1", "2", "3") > x + 1 [1] 2 3 4 > paste("Tsukuba.R#", y, sep="") [1] "Tsukuba.R#1" "Tsukuba.R#2" "Tsukuba.R#3"
条件を満たすようなベクトルを返す
- TRUE、FALSEを指定してやるとTRUEのところのみ返ってくる
> (x <- seq(1, 49, by=2)) [1] 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 > x > 20 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE [13] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE [25] TRUE > x[x > 20] [1] 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49
「x[x > 20]とかうざったいなー」という人に
- Filter関数というそのままなのが用意されている
> Filter(function(x){x > 20}, x) [1] 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49
文字列が入ったベクトルの頻度を取る
> head(sample(letters, size = 100, replace=TRUE)) [1] "p" "k" "d" "a" "k" "o" > table(sample(letters, size = 100, replace=TRUE)) a b c d f g h i j k l m n o p q r s t u v w x y z 7 4 2 2 2 5 5 6 2 4 4 5 2 3 3 2 4 4 2 5 11 3 5 4 4
最大値はどれ?最大値を取るものはどれ?
> x <- runif(1:5) > x [1] 0.1066431 0.7239144 0.7062599 0.3634962 0.5834027 > max(x) [1] 0.7239144 > which.max(x) [1] 2
差分、累積を見る
- アクセス数みたいなデータを考える
- (正規乱数で)適当に作ったデータだけど
- 2006年から2008年の3年分
> head(access) 2006年1月 2006年2月 2006年3月 2006年4月 2006年5月 2006年6月 82 73 92 80 99 48 > plot(access, main="各月のアクセス数", ylab="アクセス数", type="l", col="red", lwd=5)
作り方
access <- structure(c(82, 73, 92, 80, 99, 48, 61, 69, 60, 85, 76, 29, 31, 47, 77, 82, 79, 58, 71, 53, 68, 103, 52, 86, 81, 102, 84, 60, 69, 39, 63, 48, 49, 52, 79, 74), .Names = c("2006年1月", "2006年2月", "2006年3月", "2006年4月", "2006年5月", "2006年6月", "2006年7月", "2006年8月", "2006年9月", "2006年10月", "2006年11月", "2006年12月", "2007年1月", "2007年2月", "2007年3月", "2007年4月", "2007年5月", "2007年6月", "2007年7月", "2007年8月", "2007年9月", "2007年10月", "2007年11月", "2007年12月", "2008年1月", "2008年2月", "2008年3月", "2008年4月", "2008年5月", "2008年6月", "2008年7月", "2008年8月", "2008年9月", "2008年10月", "2008年11月", "2008年12月")) # made by this # access <- round(rnorm(36, 70, 20)) # names(access) <- c(paste("2006年", paste(1:12, "月", sep=""), sep=""), paste("2007年", paste(1:12, "月", sep=""), sep=""), paste("2008年", paste(1:12, "月", sep=""), sep="")) # dput(access)
累積アクセス数の推移を知りたい
- 例えば何かトレンドを知りたいとき
- なるべくなら各月のplotと一緒にplotしたい
> head(cumsum(access)) 2006年1月 2006年2月 2006年3月 2006年4月 2006年5月 2006年6月 82 155 247 327 426 474 > par(mfrow=c(2, 1)) > plot(access, main="各月のアクセス数", ylab="アクセス数", type="l", col="red", lwd=5) > plot(cumsum(access), main="累積アクセス数", ylab="累積アクセス数", type="l", col="red", lwd=5)
累積和が与えられている状況から各月のアクセス数を計算したい
- 差分を取っていけばいい
- x[n] - x[n-1]、x[n-1] - x[n-2]、x[n-2] - x[n-3]
- 階差数列
> head(diff(cumsum(access))) 2006年2月 2006年3月 2006年4月 2006年5月 2006年6月 2006年7月 73 92 80 99 48 61
ベクトルでの練習問題
- 下のようなyがあったとして、yが初めて0.03より小さくなったところで縦線を引きたい
- hint : abline(v=50, col = "gray60")とかで縦線は引けます
> head(y <- (function(x){1 / x})(1:100)) [1] 1.0000000 0.5000000 0.3333333 0.2500000 0.2000000 0.1666667 > plot(y, type="l") > abline(a=0.03, b=0, col="red")
回答
- 次との差分を見て、1になったところが欲しいところ
- diff関数を使ってあげる
- TRUE、FALSE(0、1)からなるようなベクトルでTRUE(1)のところindexが欲しい
- which.max関数でやってくれる
> head(y < 0.03) [1] FALSE FALSE FALSE FALSE FALSE FALSE > head(diff(y < 0.03)) [1] 0 0 0 0 0 0 > which.max(diff(y < 0.03)) [1] 33 > > y[33] [1] 0.03030303 > y[34] [1] 0.02941176 > abline(v=34, col = "gray60")
行列
- http://www.okada.jp.org/RWiki/index.php?%B9%D4%CE%F3Tips%C2%E7%C1%B4
- やっぱり数学関係が多い
- 足し算、引き算、積、逆行列
- 固有値
- こういうのはもちろんできるよ、という感じなので、ほとんど飛ばす
> x <- matrix(1:16, nrow=4, ncol=4) > y <- matrix(16:1, nrow=4, ncol=4) > x [,1] [,2] [,3] [,4] [1,] 1 5 9 13 [2,] 2 6 10 14 [3,] 3 7 11 15 [4,] 4 8 12 16 > x + y [,1] [,2] [,3] [,4] [1,] 17 17 17 17 [2,] 17 17 17 17 [3,] 17 17 17 17 [4,] 17 17 17 17 > # それぞれの要素になってしまうので注意 > x * y [,1] [,2] [,3] [,4] [1,] 16 60 72 52 [2,] 30 66 70 42 [3,] 42 70 66 30 [4,] 52 72 60 16 > # 行列の積はこっち > x %*% y [,1] [,2] [,3] [,4] [1,] 386 274 162 50 [2,] 444 316 188 60 [3,] 502 358 214 70 [4,] 560 400 240 80 > > # 逆行列 > z <- matrix(runif(16), nrow=4, ncol=4) > solve(z) [,1] [,2] [,3] [,4] [1,] -0.3423768 -0.6409797 0.2672169 1.24280569 [2,] 1.1537641 0.8780418 -1.4235750 0.51096559 [3,] -2.0507903 0.5300546 1.7590120 -0.73961828 [4,] 2.0115158 -0.7563021 -0.3142132 0.08423786 > > #固有値分解 > eigen(z) $values [1] 2.1189506+0.0000000i -0.6160354+0.0000000i 0.3292044+0.2795147i [4] 0.3292044-0.2795147i $vectors [,1] [,2] [,3] [,4] [1,] 0.3835374+0i -0.6465767+0i -0.1000389-0.2326242i -0.1000389+0.2326242i [2,] 0.5563629+0i 0.0158142+0i 0.6549398+0.0000000i 0.6549398+0.0000000i [3,] 0.6152718+0i -0.2359183+0i -0.4915977+0.3738309i -0.4915977-0.3738309i [4,] 0.4059557+0i 0.7252800+0i -0.0066802-0.3542169i -0.0066802+0.3542169i > #固有値 > eigen(z)$val [1] 2.1189506+0.0000000i -0.6160354+0.0000000i 0.3292044+0.2795147i [4] 0.3292044-0.2795147i > # 固有ベクトル > eigen(z)$vec [,1] [,2] [,3] [,4] [1,] 0.3835374+0i -0.6465767+0i -0.1000389-0.2326242i -0.1000389+0.2326242i [2,] 0.5563629+0i 0.0158142+0i 0.6549398+0.0000000i 0.6549398+0.0000000i [3,] 0.6152718+0i -0.2359183+0i -0.4915977+0.3738309i -0.4915977-0.3738309i [4,] 0.4059557+0i 0.7252800+0i -0.0066802-0.3542169i -0.0066802+0.3542169i
matrixの要素へのアクセス方法、カラム名
- 他言語の多重配列っぽい感じでアクセスできる
- [,1]のような書き方だと1列目全部を返したりできる
> # AlabamaのPopulation > state.x77[1,1] [1] 3615 > # populationの一覧 > head(state.x77[,1]) Alabama Alaska Arizona Arkansas California Colorado 3615 365 2212 2110 21198 2541 > colnames(state.x77) [1] "Population" "Income" "Illiteracy" "Life Exp" "Murder" [6] "HS Grad" "Frost" "Area"
条件付けて抽出
- ベクトルと同じような感じでできる
> # Populationが1000以上のところ > head(state.x77[,1] > 10000) Alabama Alaska Arizona Arkansas California Colorado FALSE FALSE FALSE FALSE TRUE FALSE > state.x77[state.x77[,1] > 10000,] Population Income Illiteracy Life Exp Murder HS Grad Frost Area California 21198 5114 1.1 71.71 10.3 62.6 20 156361 Illinois 11197 5107 0.9 70.14 10.3 52.6 127 55748 New York 18076 4903 1.4 70.55 10.9 52.7 82 47831 Ohio 10735 4561 0.8 70.82 7.4 53.2 124 40975 Pennsylvania 11860 4449 1.0 70.43 6.1 50.2 126 44966 Texas 12237 4188 2.2 70.90 12.2 47.4 35 262134
「state.x77[state.x77[,1] > 10000,]とかうざったいなー」という人に
- 条件を満たすような部分行列を返すようなsubset関数というのがある
- vectorでのFilter関数と似たようなもの
> subset(state.x77, state.x77[,1] > 10000) Population Income Illiteracy Life Exp Murder HS Grad Frost Area California 21198 5114 1.1 71.71 10.3 62.6 20 156361 Illinois 11197 5107 0.9 70.14 10.3 52.6 127 55748 New York 18076 4903 1.4 70.55 10.9 52.7 82 47831 Ohio 10735 4561 0.8 70.82 7.4 53.2 124 40975 Pennsylvania 11860 4449 1.0 70.43 6.1 50.2 126 44966 Texas 12237 4188 2.2 70.90 12.2 47.4 35 262134
行列とapply
- state.x77のPopulationの全米での平均を知りたい
- mean(state.x77[,1])でできる
- state.x77のIncomeの全米での平均を知りたい
- mean(state.x77[,2])でできる
- state.x77のIlliteracyの全米(ry
- そんなのはExcelでやっててくださ(ry
applyかわいいよapply
- めんどくさいので、行ごと、列ごとに同じ関数を適用するようなものはないか
- →applyを使おう
> apply(state.x77,2,mean) Population Income Illiteracy Life Exp Murder HS Grad Frost 4246.4200 4435.8000 1.1700 70.8786 7.3780 53.1080 104.4600 Area 70735.8800
練習問題
- Area、Frost、HS Grad、Murder、Life Exp、Illiteracy、Income、Populationのそれぞれの部門での全米一位がどこか調べてみましょう
- こういうような出力結果が欲しい
[,1] [,2] [1,] "Population" "California" [2,] "Income" "Alaska" [3,] "Illiteracy" "Louisiana" [4,] "Life Exp" "Hawaii" [5,] "Murder" "Alabama" [6,] "HS Grad" "Utah" [7,] "Frost" "Nevada" [8,] "Area" "Alaska"
回答
> apply(state.x77,2,which.max) Population Income Illiteracy Life Exp Murder HS Grad Frost 5 2 18 11 1 44 28 Area 2 > colnames(state.x77) [1] "Population" "Income" "Illiteracy" "Life Exp" "Murder" [6] "HS Grad" "Frost" "Area" > head(row.names(state.x77)) [1] "Alabama" "Alaska" "Arizona" "Arkansas" "California" [6] "Colorado" > matrix(c(colnames(state.x77), + row.names(state.x77)[apply(state.x77,2,which.max)]), + ncol(state.x77),2) [,1] [,2] [1,] "Population" "California" [2,] "Income" "Alaska" [3,] "Illiteracy" "Louisiana" [4,] "Life Exp" "Hawaii" [5,] "Murder" "Alabama" [6,] "HS Grad" "Utah" [7,] "Frost" "Nevada" [8,] "Area" "Alaska"
データフレーム
- http://www.okada.jp.org/RWiki/index.php?%A5%C7%A1%BC%A5%BF%A5%D5%A5%EC%A1%BC%A5%E0Tips%C2%E7%C1%B4
- matrixに数値以外も許したような表形式のデータ型
- データベースを知っている人だったら、データベースで検索した結果のようなものを想像してもらえればよい
- 実際、今回はRからデータベースを触ってみる
データフレームの要素へのアクセス方法
- 大体はmatrixと同じ
- でも、d$nameのように「$」でアクセスできるのが異なる点
> # class_filter("data.frame")で探してきたswissというデータフレームについて見ていく > head(swiss) Fertility Agriculture Examination Education Catholic Courtelary 80.2 17.0 15 12 9.96 Delemont 83.1 45.1 6 9 84.84 Franches-Mnt 92.5 39.7 5 5 93.40 Moutier 85.8 36.5 12 7 33.77 Neuveville 76.9 43.5 17 15 5.16 Porrentruy 76.1 35.3 9 7 90.57 Infant.Mortality Courtelary 22.2 Delemont 22.2 Franches-Mnt 20.2 Moutier 20.3 Neuveville 20.6 Porrentruy 26.6 > head(swiss[,1]) [1] 80.2 83.1 92.5 85.8 76.9 76.1 > head(swiss$Fertility) [1] 80.2 83.1 92.5 85.8 76.9 76.1
データフレームでもapplyすることができる
- swissの各指標での平均
> apply(swiss, 2, mean) Fertility Agriculture Examination Education 70.14255 50.65957 16.48936 10.97872 Catholic Infant.Mortality 41.14383 19.94255
Rとデータベース
- http://www.okada.jp.org/RWiki/?%A5%C7%A1%BC%A5%BF%A5%D9%A1%BC%A5%B9%A4%C8R
- Rには色々なデータベースへのインターフェイスがある
- SQLite
- MySQL
- Oracle
- 今回はtwitterpodのsqliteがあったので、それを使ってみる
install.packages("RSQLite") library("RSQLite") m <- dbDriver("SQLite") con <- dbConnect(m, dbname = "/Users/syou6162/Library/Application Support/TwitterPod/TwitterPod2.sql")
使い方は簡単
- sqliteを使うように「select * …」と書くとデータフレームで結果を得ることができる
- # 7はtweetした内容なので、隠しておくw
> tail(dbGetQuery(con,"select * from ZTWITTER")[,-7]) Z_PK ZPROTECTED Z_OPT Z_ENT ZCREATED_AT 155204 155204 1 1 1 266408379 155205 155205 1 1 1 266408404 155206 155206 1 1 1 266408355 155207 155207 1 1 1 266408360 155208 155208 1 1 1 266408338 155209 155209 1 1 1 266408449 ZURL ZHAVE_URL 155204 http://d.hatena.ne.jp/ono_matope/ <NA> 155205 <NA> 155206 http://d.hatena.ne.jp/katsu8/ <NA> 155207 http://d.hatena.ne.jp/fijixfiji/ <NA> 155208 http://iddy.jp/profile/taro_reds/ <NA> 155209 <NA> ZPROFILE_IMAGE_URL 155204 http://s3.amazonaws.com/twitter_production/profile_images/58651603/tyoro_matope_normal.jpg 155205 http://s3.amazonaws.com/twitter_production/profile_images/32932722/ryo_southpark_normal.PNG 155206 http://s3.amazonaws.com/twitter_production/profile_images/71138787/img_normal.jpeg 155207 http://s3.amazonaws.com/twitter_production/profile_images/238014710/aaa_normal.jpg 155208 http://s3.amazonaws.com/twitter_production/profile_images/250564184/img.php_normal.jpg 155209 http://s3.amazonaws.com/twitter_production/profile_images/91961536/h_h_normal.PNG ZID ZSCREEN_NAME ZLABEL ZLOCATION 155204 2115458528 ono_matope <NA> Yokohama / Roppongi in Japan 155205 2115460730 ryo_grid <NA> Tsukuba 155206 2115456341 katsu8 <NA> Yokohama, Japan 155207 2115456804 fijixfiji <NA> KANSAI, Japan 155208 2115454892 taro_reds <NA> Setagaya, Tokyo, JAPAN 155209 2115464988 H_H <NA> 東京都 ZNAME 155204 小野マトペ 155205 ryo 155206 Katsuya Fujiwara 155207 fijixfiji 155208 たろー / えあじゅう休業中 155209 H. Hosaka > class(tail(dbGetQuery(con,"select * from ZTWITTER")[,-7])) [1] "data.frame"
いくつか見てみる
> # userの名前 > head(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME) [1] "みずち/Koutarou Chikuba" "mickey24_bot" [3] "mknst" "t33f" [5] "Yasuhisa Yoshida" "hogelog" > # dbにいくつ分の発言が入っているか > length(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME) [1] 155844 > # dbに何人分のuserが入っているか > length(unique(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME)) [1] 556
練習問題
- Amazonとかの売上はwikipedia:べき乗則というのに従っているらしい
- Twitterの発言数もそれに従っているとかなんとか
- 発言の八割は二割の人によって起こなわれている…?
- それを確かめてみよう
- まずは、発言の多い人top10をリストアップしてみよう
回答
> # 発言の多い人top10 > head(sort(table(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME), decreasing=TRUE), n=10) いぬたん! Yasuhisa Yoshida satomi.k Yu Tsuda 2579 2455 2428 2012 yag/やぐ kentaro みずち/竹馬光太郎 Hiroyuki Inoue 1876 1808 1803 1650 すむむ(すもも) 便所糞虫 1648 1644 hist(table(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME), nclass=30, xlab="発言回数", main="発言回数の頻度") # 頻度を対数にすると直線に近い形になる h <- hist(table(dbGetQuery(con,"select * from ZTWITTER")[,-7]$ZNAME), nclass=30) plot(h$mid, log(h$counts), xlab="発言回数", ylab="発言回数の頻度の対数", main="発言回数の頻度の対数")
まとめ
- Rの基本的なデータ構造のうち
- ベクトル
- 行列
- データフレーム
- と、それを便利に使える関数を紹介
- state.x77のデータでアメリカの特徴を暴いてやった
- twitterの発言数がべき乗則に従っている、ということを見てみた