Tsukuba.R#5の発表資料

Rの基本データ構造をもっと理解しよう

自己紹介

  • 吉田康久
  • Tsukuba大学のM1

Tsukuba.Rの近況(?)その1

Tsukuba.Rの近況(?)その2

日本Ruby会議2009

今回の内容

  • 基本に戻って、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"

ベクトル

> 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)
20061200622006320064200652006682        73        92        80        99        48
> plot(access, main="各月のアクセス数", ylab="アクセス数", type="l", col="red", lwd=5)

Quartz 2 [*]

作り方

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))
20061200622006320064200652006682       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)

Quartz 2 [*]

累積和が与えられている状況から各月のアクセス数を計算したい

  • 差分を取っていけばいい
    • x[n] - x[n-1]、x[n-1] - x[n-2]、x[n-2] - x[n-3]
    • 階差数列
> head(diff(cumsum(access)))
20062200632006420065200662006773        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")

Quartz 2 [*]

Quartz 2 [*]

回答

  • 次との差分を見て、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")

行列

> 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"    

データフレーム

データフレームの要素へのアクセス方法

  • 大体は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とデータベース

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="発言回数の頻度の対数")

Quartz 2 [*]
Quartz 2 [*]

まとめ

  • Rの基本的なデータ構造のうち
    • ベクトル
    • 行列
    • データフレーム
  • と、それを便利に使える関数を紹介
  • state.x77のデータでアメリカの特徴を暴いてやった
  • twitterの発言数がべき乗則に従っている、ということを見てみた