dplyr 0.3の新機能を使ってみる

dplyr 0.3が次の金曜にCRANにくるらしいです。

色々新機能があるとのことなので、ひとまず使ってみました。こういうのRPubsにあげるべきなのかなとか思いつつ、よく分からないのでひとまずブログに。

インストール

(この部分は、10/4以降はたぶん不要。install.packages()で入れられるはず)

library(devtools)
install_github("hadley/lazyeval")
install_github("hadley/dplyr")

# バージョン確認
packageVersion("dplyr")

lazyevalもCRANにはまだ来てないみたいだったので、githubからインストールします。
最後のバージョン確認が0.3になってればOK。

新しい関数/改良された関数

NEWS.mdに載っている新しい関数を使ってみます。 引用文はNEWS.mdからです。


between()

between() vector function efficiently determines if numeric values fall in a range, and is translated to special form for SQL (#503).

x以上y以下の値、というのをすっきり書けます。

今までだと、

iris %>% filter(Sepal.Length >= 5, Sepal.Length <= 6)

と書く必要があったのが、

iris %>% filter(between(Sepal.Length, 5, 6))

と書けます。
ただし、境界値を含めたくない場合はbetween()ではむりみたいで、今まで通り

iris %>% filter(Sepal.Length > 5, Sepal.Length < 6)

と書く必要があります。なんかオプションほしいところですね。


count()

count() makes it even easier to do (weighted) counts (#358).

wtというオプションが付いて、重み付きのカウントができるようになりました。

たとえば、「Sepal.Lengthが5.5以上のものは重み2でカウントする」というのは、
(あんまり意味のある例を思いつけなかったので適当です。すみません。。)

iris %>%
  mutate(w = ifelse(Sepal.Length >= 5.5, 2, 1)) %>%
  count(Species, wt = w)

という感じに書けます。


data_frame()

data_frame() by @kevinushey is a nicer way of creating data frames. It never coerces column types (no more stringsAsFactors = FALSE!), never munges column names, and never adds row names. You can use previously defined columns to compute new columns (#376).

余計なことしないdata.frame()、という感じみたいです。help(data_frame)を見ると、具体的には

  1. 無理やり変換しない(characterを勝手にfactorにしたりとか)
  2. row.namesをつけない
  3. カラム名を勝手に変えない(...に勝手に変えるたりとか)
  4. 長さ1のもの以外リサイクルしない
  5. 遅延評価する
  6. 出力にtbl_df(いい感じの出力にしてくれるdata.frameのラッパー)をつける

とあります。(ただ、4.はdata.frameでも同じな気がします。よくわからない。。)

全部やるとちょっと長くなるので、1.と、5.についてやってみます。

1. 無理やり変換しない

data.frame()だとcharacterを勝手にfactorにしてしまいます。 これに気づかずに分析を進めて、変な結果になった後に気づいて、いまいましげにstringsAsFactors = FALSEをつける、というのはもはやRあるあるですね...。

> str(data.frame(letters))
'data.frame':   26 obs. of  1 variable:
 $ letters: Factor w/ 26 levels "a","b","c","d",..: 1 2 3 4 5 6 7 8 9 10 ...

これがdata_frame()だとなんとcharacterのままです!(なんと、というか、正直これが標準の動作になってほしい)

> str(data_frame(letters))
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   26 obs. of  1 variable:
 $ letters: chr  "a" "b" "c" "d" ...

5. 遅延評価する

data.frame()だと、要素が関数を実行する前に存在していないといけません。
なので、こんなことはできません。

> data.frame(x = runif(5), y = x * 2)
Error in data.frame(x = runif(5), y = x * 2) : object 'x' not found

data_frame()は、引数を順番に評価していくので、その引数の前にある引数も使うことができます。

> data_frame(x = runif(5), y = x * 2)
Source: local data frame [5 x 2]

           x         y
1  0.7582229 1.5164458
2  0.4691972 0.9383944
3  0.7108713 1.4217426
4  0.8305894 1.6611788
5  0.8680413 1.7360827

distinct()

distinct() returns distinct (unique) rows of a tbl (#97). Supply additional variables to return the first row for each unique combination of variables.

data.frameunique()みたいな関数です。SQLっぽい。
引数で指定したカラムの値の組み合わせを重複を除外して返します。指定していないカラムについては、それぞれ固有の組み合わせのはじめの行の値を返します。

> iris %>% distinct(Species)
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          5.1         3.5          1.4         0.2     setosa
2          7.0         3.2          4.7         1.4 versicolor
3          6.3         3.3          6.0         2.5  virginica

ちなみに、関数の中で使う用のdistinct_()というのもあるようですが、使い方がよくわかりませんでした。.dot引数で、Non-Standard Evaluationをコントロールできる点がdistinct()との違いです。

参考:dplyr/nse.Rmd at master · hadley/dplyr · GitHub


intersect() union() setdiff()

Set operations, intersect(), union() and setdiff() now have methods for data frames, data tables and SQL database tables (#93). They pass their arguments down to the base functions, which will ensure they raise errors if you pass in two many arguments.

baseパッケージに入っている積集合や和集合を求める関数を、data.framedata.tableなどにも適用できるように上書きしています。たぶんsetequal()もそうなはず。

例えば、baseintersectdata.frameには使うとエラーが出ます。

> a <- data_frame(1:10, 11:20)
> a1 <- a[1:7, ]
> a2 <- a[4:10, ]
> base::intersect(a1, a2)
Error in .subset2(x, i, exact = exact) : subscript out of bounds

でもdplyr版の方は使えます。

> dplyr::intersect(a1, a2)
Source: local data frame [4 x 2]

  1:10 11:20
1    4    14
2    5    15
3    6    16
4    7    17

left_join() inner_join() semi_join() anti_join()

Joins (e.g. left_join(), inner_join(), semi_join(), anti_join()) now allow you to join on different variables in x and y tables by supplying a named vector to by. For example, by = c("a" = "b") joins x.a to y.b.

*_join()は以前からありましたが、以前まではJOINのキーに使えるのは同じカラム名のものだけでした。今回から、vectorを渡すことで違うカラム名のものを使えます。


n_groups()

n_groups() function tells you how many groups in a tbl. It returns 1 for ungrouped data. (#477)

グループ数を求める関数です。

> iris %>% group_by(Species) %>% n_groups()
[1] 3

あと、group_size()という、各グループのレコード数を出す関数もあるみたいです。

> iris %>% group_by(Species) %>% group_size()
[1] 50 50 50

transmute()

transmute() works like mutate() but drops all variables that you didn't explicitly refer to (#302).

mutate()の、指定したカラムしか残さないバージョンです。

ご存知のように、mutate()はカラムを増やすだけで元のカラムも残します。

> iris %>% mutate(Sepal.Ratio = Sepal.Length / Sepal.Width) %>% head(3)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Sepal.Ratio
1          5.1         3.5          1.4         0.2  setosa    1.457143
2          4.9         3.0          1.4         0.2  setosa    1.633333
3          4.7         3.2          1.3         0.2  setosa    1.468750

transmute()だと、指定したやつしか残さないので、select()を使う手間が省けて便利です。

> iris %>% transmute(Sepal.Ratio = Sepal.Length / Sepal.Width, Species) %>% head(3)
  Sepal.Ratio Species
1    1.457143  setosa
2    1.633333  setosa
3    1.468750  setosa

rename()

rename() makes it easy to rename variables - it works similarly to select() but it preserves columns that you didn't otherwise touch.

select()の、指定したカラム以外も残すバージョンです。

select()は、指定したカラムしか残しません。あるカラムの名前を変える目的でselect()を使うときは、もとのカラムを含めるためにeverything()とかを指定する必要がありました。

> select(iris, petal_length = Petal.Length) %>% head(3)
  petal_length
1          1.4
2          1.4
3          1.3
> select(iris, petal_length = Petal.Length, everything()) %>% head(3)
  petal_length Sepal.Length Sepal.Width Petal.Width Species
1          1.4          5.1         3.5         0.2  setosa
2          1.4          4.9         3.0         0.2  setosa
3          1.3          4.7         3.2         0.2  setosa

rename()は指定されたカラム以外もすべて引き継ぎます。

> rename(iris, petal_length = Petal.Length) %>% head(3)
  Sepal.Length Sepal.Width petal_length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa

slice()

slice() allows you to selecting rows by position (#226). It includes positive integers, drops negative integers and you can use expression like n().

data.frameなどを、行数指定でスライスできます。

> a <- data_frame(label = paste0("test",1:10), value = 1:10)
> slice(a, 1L)
Source: local data frame [1 x 2]

  label value
1 test1     1

一番最後の行数の指定は、数字以外にn()のような表現が使えます。

> slice(a, n())
Source: local data frame [1 x 2]

  label value
1 test9     9

廃止された関数

%.%以外の話はよくわからないのでノーコメントで。

%.% chain

%.% has been deprecated: please use %>% instead. chain() is defunct. (#518)

%.%がdeprecatedに、chainが廃止になります。ついに...

感想

けっこういろいろ増えた

ちょっと試してみようという軽い気持ちでこの記事書き始めたんですが、意外と長くなって疲れました。。0.2の%>%do()ほど大きな変化はなかった印象ですが、増えた関数がいっぱいありますね。

CRANにくるのが楽しみです。

まだバグあるのかも。

追記(2014/10/02):
slice()のバグはIssue立てたら一瞬で直ってました。

なんかちょこちょこ動作が怪しい気がします。
たとえば、slice()複数行指定はslice(a, 5L:n())のような感じでできると書いてますが、やってみると同じ行ばかりが表示されます。。 これはひょっとすると手元の環境の問題なのかもなので、間違いあればご指摘ください。

> slice(a, 8L:n())
Source: local data frame [3 x 2]

  label value
1 test8     8
2 test8     8
3 test8     8