R言語上級ハンドブック
R言語上級ハンドブックを一通り読了しました.
- 作者: 荒引健,石田基広,高橋康介,二階堂愛,林真広
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2013/09/25
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (9件) を見る
本書は,Rで効率的・効果的にデータ解析や処理を行うための中級〜上級のTips集です.著者はR界隈で著名な方々ばかりで,高度なトピックが平易に解説されています.内容も,実行パフォーマンス(処理速度,メモリ使用量等),Hadoopや他言語との連携,グラフィクスなど多岐に渡ります.また,knitrやslidifyによるレポート・プレゼン資料作成,shinyによるWebアプリ作成など,比較的新しい話題についても収録されています.
Rで分からないことをStack Overflowなどで調べているユーザにとって,本書は座右の書となることでしょう.
関数のメモ化,連長圧縮,Deducerパッケージなど,勉強になることが多かったのですが,特に印象に残った以下の3点についてメモします.
パッケージが提供する関数の仕様の変更("079 既存の関数を書き換える","018 パッケージ内のオブジェクトを変更する")
Rのパッケージを使用していると,関数の仕様を変更したいケースが多々発生します.これまではソースをコピー → 仕様を変更 → source関数で読み込む,という方法を取っていました.しかし,この方法はパッケージで公開されていない関数などを呼び出している場合に名前空間を明示しないとエラーが発生するなどの問題が起きて,予想以上に手間がかかっていました.
本書では,既存の関数を書き換えるためにいくつかの方法が示されていますが,ここではmethodsパッケージのinsertSource関数を用いる方法を試してみます.insertSource関数は,ファイルで定義された関数を読み込んで,パッケージの関数を変更します.例として,baseパッケージのpaste0関数を書き換えてみましょう.
> # デフォルトでは与えられた文字列の集合をセパレータを""として結合 > paste0("a", "b") [1] "ab" > # 新しいpaste0関数の定義を書き込むファイル > filename <- tempfile() > # 新しい定義のファイルへの書き込み(セパレータを改行コードに設定) > writeLines("paste0 <- function(...) paste(list(...), collapse=\"\n\")", filename) > # 関数の書き換え > insertSource(filename, package="base", force=FALSE) 関数でないオブジェクトは現在挿入できません(トレース不能です): .packageName 変更された関数が trace() を通じて挿入されます: paste0 > # 書き換えが成功したことの確認 > paste0("a", "b") [1] "a\nb" > # 念のため,関数の定義が変更されたことの確認 > paste0 Object of class "functionWithTrace", from source function (...) paste(list(...), collapse = "\n") <environment: namespace:base> ## (to see original from package, look at object@original)
このTipsを実践することにより,パッケージの関数の仕様を書き換える手間が減らせそうです.
data.tableパッケージを用いた高速検索・グループ化処理("034 「data.table」パッケージでデータフレームの拡張を行う")
前々から調べようと思って手つかずになっていたパッケージの一つが,このdata.tableでした.
本書では,data.tableパッケージの概要について,『「data.frame」の機能の大部分を継承しつつ,キーの設定,バイナリサーチを用いた高速な検索,グループ化による処理などをサポートしています』と説明されています.
ここでは,data.tableパッケージのvignetteの一つである"Introduction to the data.table package in R"から抜粋する形で,パッケージの概要について眺めてみます.
> library(data.table) > > 1. オブジェクトの生成とdata.frameからの変換 > > # data.tableオブジェクトの作成(data.frameと全く同様) > set.seed(123) > DT <- data.table(x=c("b","b","b","a","a"),v=rnorm(5)) > DT x v 1: b -0.56047565 2: b -0.23017749 3: b 1.55870831 4: a 0.07050839 5: a 0.12928774 > # data.frameオブジェクトからdata.tableオブジェクトへの変換 > CARS <- data.table(cars) > head(CARS) speed dist 1: 4 2 2: 4 10 3: 7 4 4: 7 22 5: 8 16 6: 9 10 > # メモリ上に保持されたdata.tableオブジェクトのリストの確認(どれもKEYは未設定) > tables() NAME NROW MB COLS KEY [1,] CARS 50 1 speed,dist [2,] DT 5 1 x,v Total: 2MB > > # 2. キー > # data.tableオブジェクトの特定の行へのアクセス > DT[2, ] x v 1: b -0.2301775 > DT[DT$x=="b",] x v 1: b -0.5604756 2: b -0.2301775 3: b 1.5587083 > cat(try(DT["b",],silent=TRUE)) # 行名が付与されていないので,これはできない Error in `[.data.table`(DT, "b", ) : When i is a data.table (or character vector), x must be keyed (i.e. sorted, and, marked as sorted) so data.table knows which columns to join to and take advantage of x being sorted. Call setkey(x,...) first, see ?setkey. > # キーとなる列名を設定(列"x"の値でレコードがソートされる) > setkey(DT,x) > DT x v 1: a 0.07050839 2: a 0.12928774 3: b -0.56047565 4: b -0.23017749 5: b 1.55870831 > # data.tableオブジェクトのリストにもキーを確認 > tables() NAME NROW MB COLS KEY [1,] CARS 50 1 speed,dist [2,] DT 5 1 x,v x Total: 2MB > DT["b",] # DT["b"]でも可 x v 1: b -0.5604756 2: b -0.2301775 3: b 1.5587083 > # 特定のキーの値に該当するレコードが複数存在する場合,mult引数で最初or最後のレコードを抽出可能 > DT["b",mult="first"] x v 1: b -0.5604756 > DT["b",mult="last"] x v 1: b 1.558708 > > # 以下,キー設定の効果の検証(data.frame vs. data.table) > set.seed(123) > grpsize <- ceiling(1e7/26^2) # レコード数: 10^6, グループ数: 676 > grpsize [1] 14793 > # data.frameの作成 > tt <- system.time(DF <- data.frame( + x=rep(LETTERS, each=26*grpsize), + y=rep(letters, each=grpsize), + v=runif(grpsize*26^2), + stringsAsFactors=FALSE) + ) > tt ユーザ システム 経過 1.148 3.068 4.224 > head(DF, 3) x y v 1 A a 0.9568333 2 A a 0.4533342 3 A a 0.6775706 > tail(DF, 3) x y v 10000066 Z z 0.8490227 10000067 Z z 0.6784250 10000068 Z z 0.9932539 > dim(DF) [1] 10000068 3 > # data.frameから特定の条件を満たしたレコードを抽出 > tt <- system.time(ans1 <- DF[DF$x=="R" & DF$y=="h", ]) > tt ユーザ システム 経過 0.552 0.168 0.724 > head(ans1, 3) x y v 6642058 R h 0.4380854 6642059 R h 0.9481632 6642060 R h 0.4784464 > > # data.tableへの変換 > DT <- data.table(DF) > # キーの設定 > setkey(DT,x,y) > # data.tableから特定の条件を満たすレコードの抽出 > ss <- system.time(ans2 <- DT[J("R","h")]) > # data.tableはバイナリサーチを用いて高速に検索(360倍以上) > ss ユーザ システム 経過 0.004 0.000 0.002 > > 3. 高速なグループ化 > # 列名"V"の値の総和 > DT[, sum(v)] [1] 4999521 > # 列名"x"のカテゴリごとの列名"V"の値の総和 > DT.sum.by.x <- DT[,sum(v),by=x] > head(DT.sum.by.x) x V1 1: A 192322.6 2: B 192110.5 3: C 192099.1 4: D 192189.3 5: E 192504.4 6: F 192246.1 > tail(DT.sum.by.x) x V1 1: U 192135.4 2: V 192365.8 3: W 192560.9 4: X 192111.3 5: Y 192238.0 6: Z 192299.6
このパッケージには上記以外のvignetteも用意されているので,目を通したいところです.
- "FAQs about the data.table package in R"
- "Timings of common tasks using the data.table package in R",
また,どのように実装されているか気になるので,余裕があるときに中身を調べたいと思います.
TAGSファイルの作成("078 TAGSファイルを作成する")
Rを使用してしばらく経ちパッケージの関数で分からないことがあると,ソースコードを見にいくことが増えてきます.そのようなとき,まず始めに関数の依存関係を調べていましたが,R部分のコードについてはdebug/debugonce関数などを用いて地道に行っていました.
しかし,TAGSファイルを作成すれば,このような作業の負荷が軽減できそうです.
例として,先のdata.tableパッケージのソースコードに対してTAGSファイルを作成してみます.data.tableパッケージのソースコードをCRANから取得して解凍すると,次のディレクトリ構成となっています.
$ ls DESCRIPTION MD5 NAMESPACE NEWS R inst man src tests vignettes
以下のコマンドを実行することにより,ディレクトリの直下に"TAGS"という名前のファイルが作成されます.
$ R CMD rtags Tagging R/C/Rd files under /home/sfchaos/Documents/applitest/R/data.table/data.table; writing to TAGS (overwriting)... etags: no input files specified. Try `etags --help' for a complete list of options. etags: no input files specified. Try `etags --help' for a complete list of options. etags: no input files specified. Try `etags --help' for a complete list of options. etags: no input files specified. Try `etags --help' for a complete list of options. Done $ ls # TAGSファイルが作成されている DESCRIPTION MD5 NAMESPACE NEWS R TAGS inst man src tests vignettes $ head TAGS /home/sfchaos/Documents/applitest/R/data.table/data.table/R/IDateTime.R,775 aas.IDate6,206 aas.IDate.default8,258 aas.IDate.Date11,326 aas.IDate.IDate15,423 aas.Date.IDate17,461 mmean.IDate21,544 rround.IDate37,1028 aas.ITime51,1646
上記の"R CMD tags"コマンドではRのファイルはRディレクトリの直下に存在するものだけしか対象になりません.このような問題点を解決するための手段として,本書ではrtags関数を用いる方法についても説明されています.