« 読了:Mercer et al. (2018) ネットパネルで調査したとき母集団の構成にあわせてウェイティングすることがあるけど、あれって意味はあるのかどうか試してみました | メイン | 読了:Montero & Vilar (2014) RのTSclustパッケージで時系列クラスタリング »
2018年4月28日 (土)
R言語徹底解説
[a]
Hadley Wickham / 共立出版 / 2016-02-10
現勤務先への転職を機に、20年近く使い続けた我が愛しのSASに別れを告げ、泣く泣くRに乗り換えたのであった。たまに自宅でSAS University Editionを立ち上げると、もう懐かしくて仕方ないです。
とはいえRを使い始めてはやX年 (Xの値については考えたくない)、その間ほぼ毎日使っているわけで、こうしてだましだましその場をしのいでいるのもいかがなものか、やはりきちんと勉強したほうがよいのでは... と後ろめたい気分であった。先日ちょっと時間があいたので、刊行時に大枚はたいて買い込んだまま本棚に積んであったこの本に一通り目を通してみた。R界の神様Hadley Wickhamによる有名な解説書。
せっかくなので、読みながら「おおお」と思ったり「へええ」と思ったりしたところをメモしておく。
1. 導入
2. データ構造
- オブジェクトxがベクトルかどうかを判断したいとき、is.vector(x)はだめ。これは「xが名前以外の属性を持たない」ときにTRUEとなる関数なのである。そうそう、これに気が付いたときはあっけにとられた。なんでこんな名前にするかなあ。is.atomic(x) || is.list(x)を使うべし、とのこと。
- かつて因子型ベクトルは文字型ベクトルに比べてメモリ消費量が少なかったが、いまは変わらない。
- comment属性はprintされない。ほんとだー。
3. データ抽出
- (これは本のメモではなくて、本を読みながらふと試してみたんだけど) x <- 1:3; names(x) <- c("a", "b", "a"); としたとき、x["a"]はc(1,3)ではなく黙って1だけ返す。やっぱりそうだったか。名前で絞るのって怖い。
- x <- 1:3として、x[NA]はc(NA, NA, NA)だが、x[NA_real_]はNAである。なるほど。
- アトミックベクトルxに対してx[[1]]とすると、x[1]と違って名前が消える。おおお...
4. ボギャブラリ
5. コーディングスタイルガイド
6. 関数
- 関数のsrcref属性にソースコードが入っている。コメント付きで。
- 「レキシカルスコープ」の「レキシカル」は、英語のlexicalではなく、コンピュータサイエンス用語の"lexing"(字句解析)に由来している。へー。
- レキシカルスコープの4原則。(1)ネームマスキング。(2)名前から値を探す際のルールは値の型に関わらず同一だが、あきらかに関数っぽい名前の場合は例外で、関数しか探さない。(3)フレッシュスタート。関数が呼び出されるたびに新しい環境が生成される。(4)ダイナミックルックアップ。値が探されるのは関数が生成されたときではなく実行されたとき。
- リスト l に対して、sapply(l, function(x) x[2])はsapply(l, "[", 2)とも書ける。なるほど。
- 関数の引数は遅延評価されるから、その関数のなかで生成する変数でデフォルト引数を定義することさえできてしまう。良いやり方ではないけれど。
- 関数にデフォルト値を与えず、未指定かどうかmissing()で調べてどうにかするコードを書くくらいなら、デフォルト値をNULLにしておいてis.null()で調べたほうが良いのでは、とのこと。どれが必須変数でどれが省略可能な変数かわかりにくくなるから。
- 引数を強制評価するときに使うforce()は force <- function(x) xと定義されている。面白い...
- !is.null(x) || stop("a is null")と軽く書けるのは遅延評価のおかげ。そうですね。逆に言うと、いつもこういうとき||演算子を使わずstopifnot(!is.null(x))と書いているのは、気持ちの上で遅延評価に頼るのが怖いからだと気が付いた。
7. オブジェクト指向実践ガイド
- is.object()でオブジェクトが基本型かどうかがわかる。わかりにくい名前だ...
- S3クラスをすべて表示させる方法はない。
- S3オブジェクトのclass属性を変えることができる、変えるべきではないけれど、とのこと。そういわれてみれば、あまり気にしていなかったけど、オブジェクト指向という意味ではclass属性を変えられるのってすごく奇妙ですね。
(7.4 RC はスキップした)
8. 環境
この章がいちばん難しい。きちんと理解できたとは言い難い。
- 環境はフレームと親環境からなる。フレームとは名前と束縛を保存しているものである。ところがフレームという用語は別の意味で使われていることがある。たとえばparent.frame()は親フレームではなくて呼び出し環境にアクセスしている。
- リストの要素にNULLを付値すればその要素は消えるが、環境のなかのオブジェクトにNULLを付値してもNULLに束縛されるだけ。そういわれてみればそうだ。削除にはrm()を使う。
- かつては大きなデータを扱うために環境を使った。修正してもコピーが生じないから。しかしいまではリストを修正しても全要素をコピーしたりしないので、あんまり意味がなくなった。
9. デバッギング、条件ハンドリング、防御的プログラミング
- is.error <- function(x) inherits(x, "try-error"). なるほど、これは便利だ。いつもtry()の返値の判定で戸惑っていたのである。
- 防御的プログラミングの観点からは、subset()は使わないほうがよい(非標準評価しているから)。また[とsapply()には要注意(引数によって出力の型が変わるから)。[にはdrop=FALSEを付け、sapply()のかわりにvapply()を使うべし。
(9.3.2-9.3.4でtryCatch(), withCallingHandlers()について説明しているんだけど、面倒なのでスキップした。いずれ必要に迫られたときに読もう)
10. 関数型プログラミング
- クロージャ c が持っている値をみるためには、as.list(environment(c))とするか、pryr::unenclose(c)を使う。
- ある型のオブジェクトを食う関数をたくさん作ってリスト lf に入れ、それぞれにオブジェクトxを食わせて動かしまくりたいとする。lapply()を使う場合、結局無名関数か名前付き関数を作るしかない。lapply(lf, function(f) f(x))というように。ああ、やっぱりそうだったのか。 なにか特別なやり方があるのかと思っていた。
(10.5 ケーススタディ:数値積分 はスキップした)
11. 汎関数
- 関数を食ってベクトルを返す関数を汎関数(functional)という。たとえばlapply()がそう。
- mtcars[] <- lapply(mtcars, function(x) x/mean(x) ) と書くより、いったんmtmeans <- lapply(mtcars, mean)としてからmtmeans[] <- Map(`/`, mtcars, mtmeans)としたほうがわかりやすいでしょう、とのこと。そ、そうかなあ... 私はふだんMap()使わないのでびびりました。
- 行列aに対して a1 <- apply(a, 1, identity)として、identical(a, a1)はFALSEになる(a1はaの転置になっているから。apply(a, 2, identity)ならTRUEになる)。apply()のこういう性質のことを「べき等性がない」というのだそうだ。
- Reduce()関数。数値ベクトルのリストlがあり、全要素に共通する値を探したい。つまり、intersect(l[[1]], l[[2]]])をとって、それとl[[3]]とのintersectをとって...を繰り返したい。こういうとき、Reduce(intersect, l)と書ける。すげー。私だったらおそらく、数値が整数なら x <- table(unlist(lapply(l, unique)); as.integer(names(x[x == length(l)]))ってやっちゃう...
(11.7 関数族 はスキップした。ちょっと疲れちゃったもので...きちんと読んだらとても勉強になりそうな内容である)
12. 関数演算子
- たとえばdot_every<-function(f,n){なんとかかんとか}と定義したとして(fは関数, nは整数)、呼び出しはdot_every(hogehoge(hogehoge), 10)となるけど、dot_everyと10が離れてしまって読みにくくなる。こういうのをダグウッドサンドイッチというのだそうだ。アメリカのマンガに出てくる、すごく具材の多いサンドイッチのことらしい。知らんかった。というわけで、dot_every <- function(n, f){なんとかかんとか}と定義したほうが良い由。
- f <- function(a) g(a, b=1)と書く代わりに、pryr::partial()というのを使ってf <- partial(g, b=1)と書けるのだそうだ。こういうのを部分関数適用という由。この例では後者を使う意味がないけど、compose()と併用すると便利である由... うーん...
- splat <- function(f){force(f); function(args){ do.call(f, args) };}とすると、たとえばベクトルxに対してargs <- list(list(x), list(x, na.rm=T)); lapply(args, splat(mean))という風に使える、とのこと。なるほどねえ、これは確かに便利かも。
13. 非標準評価
- quote()は引数(表現式)を評価せずそのまま表現式として返す。substitute()は関数の中で使ったときのみ、変数を置換する。pryr::subs()はグローバル環境で使っても置換する。
- Hadley先生はlibrary()やvignette()といった関数が非標準評価を使っていること(つまり、library("ggplot2")ではなくlibrary(ggplot2)と書けること)に批判的。非標準評価を使うと参照透過でなくなるから(引数を値に変えても結果が同じであることが保証されなくなるから)。そのくらいのことなら""で囲めよ、たった2文字だろ、とのこと。
- Hadley先生いわく、非標準評価を使う関数をつくったらその標準評価版もつくりなさいとのこと。たしかに、以前のdplyrパッケージはそうなってましたね、最近なくなっちゃったけど。ってことは、考え方が変わったのかしらん。
14. 表現式
- 表現式(expression)の要素は、定数(constant), 名前(name), 呼び出し(call)。ほかにペアリストというのもあるけど過去の遺産。
- str()はnameをsymbol, callをlanguageと表記する。
(14.5 ペアリスト, 14.7 再帰関数を用いた抽象構文木の巡回 はスキップした)
15. ドメイン特化言語 (スキップした)
16. パフォーマンス
メモは特にないけど、全編にわたって目から鱗が落ちる内容であった。
17. コードの最適化
- コード高速化のために実験するときは、あらかじめ目標の実行速度を決めておくこと。ずるずるやってると時間の無駄になるから。はい、胸に刻みます...
- 10 %in% x よりany(x == 10)のほうがはるかに高速。
- sapply()よりvapply()のほうが速い。
- cut()はlabels=FALSEとすると速い。
- リスト l にas.data.frame(l)とするのは効率が悪い。個々の要素をデータフレームにしたうえでrbind()しているから。l の中身に自信があるなら、いきなりclass(l) <- "data.frame"; attr(l, "row.names") <- .set_row_names(length(l[[1]])); としちゃえば速い。とはいえ、このやり方を見つけるためにはHadleyさんでさえ時間をかけてソースコードを読んだ由。素人にできることではないな。
18. メモリ
19. Rcppパッケージを用いたハイパフォーマンスな関数 (スキップした)
20. RとC言語のインターフェイス (スキップした)
データ解析 - 読了:「R言語徹底解説」