dplyr 0.5.0を使ってみる

※この記事は4/9に書いた「dplyr 0.4.3.9000を使ってみる」という記事を加筆したものです


「1か月くらいしたら新しいdplyr出るよ」とHadleyが言ってました。

それから1か月が過ぎ、2か月が過ぎ、3か月が過ぎ、、、ようやくリリースされました! 長かった。。

Release dplyr 0.5.0 · hadley/dplyr · GitHub

変更点を見てみます。

Breaking changes

arrange()はグループを無視

arrange() once again ignores grouping (#1206)

group_by()に指定したグループがarrange()で無視されるようになりました。 このxもソートするキーに指定したい場合は、↓のような感じで明示的に指定する必要があります。

d <- data.frame(x = c("a", "a", "b", "b"), y = 1:4)
d %>%
  group_by(x) %>%
  arrange(x, y)

distinct()は引数に指定した列しか返さない

distinct() now only keeps the distinct variables. If you want to return all variables (using the first row for non-distinct values) use .keep_all = TRUE (#1110).

select()のヘルパー関数がエクスポートされた

The select helper functions starts_with(), ends_with() etc are now real exported functions. This means that you'll need to import those functions if you're using from a package where dplyr is not attached.

select()の中だけで動いていた関数がちゃんとエクスポートされるようになりました。このため、dplyr::select()単体で呼び出す時はstarts_with()にもdplyr::をつけないとエラーになったりするので注意。

deprecatedになった関数

The long deprecated chain(), chain_q() and %.% have been removed. Please use %>% instead.

id() has been deprecated. Please use group_indices() instead. (#808)

rbind_all() and rbind_list() are formally deprecated. Please use bind_rows() instead (#803).

Outdated benchmarking demos have been removed (#1487).

Code related to starting and signalling clusters has been moved out to multidplyr.

rbind_all()rbind_list()は正式にdeprecatedになりました。(以前はbind_rows()の中で使われていましたが、今はもう違う実装になっています)

New functions

coalesce()

coalesce() finds the first non-missing value from a set of vectors. (#1666, thanks to @krlmlr for initial implementation).

ベクトルを順番に見ていって、はじめに見つかったNAでない要素を返します。

x <- c(1, NA, NA, NA)
y <- c(2,  2, NA, NA)
z <- c(3,  3,  3, NA)

coalesce(x, y, z)
#> [1]  1  2  3 NA

と書くと具体的な利用シーンがよく分かりませんが、これはNAを置き換えるという用途が念頭にあるようです。 こんな感じでいちばん最後に長さ1のベクトルを指定すると、その値でNAをうめることができます。

coalesce(x, 0)
#> [1] 1 0 0 0

case_when()

case_when() is a general vectorised if + else if (#631).

これはベクトル化されたswitch()みたいなやつです。書き方がちょっと独特ですが、表現式 ~ 値を並べていくというスタイルです。

一つ注意点としては、パイプの中ではうまく動きません。(参考:Equivalent of SQL CASE · Issue #631 · hadley/dplyr · GitHub

mtcars %>%
  mutate(
    size = case_when(
      cyl > 6 ~ "big",
      TRUE ~ "small"
    ))
Error: object 'cyl' not found

.$をつければアクセスできるんですが、ちょっと分かりづらい。。

    size = case_when(
      .$cyl > 6 ~ "big",
      TRUE ~ "small"
    )

ただ、簡単なやつは下に出てくるif_else()とかrecode()でできます。この2つはパイプの中で使えるので、case_when()が必要になる場合は少ないかも。

if_else()

if_else() is a vectorised if statement: it's a stricter (type-safe), faster, and more predictable version of ifelse(). In SQL it is translated to a CASE statement.

これほしかったやつ!!

ifelse()がつらいのは以下の2点です。

if_else()はこのいずれもが解消されていてしかも速いということなので、使わない理由はないでしょう。

na_if()

na_if() makes it easy to replace a certain value with an NA (#1707). In SQL it is translated to NULL_IF.

これはcoalesce()の逆です。ある値をNAと置き換える関数です。たとえば元データでNA"N/A"となっている場合は、

sweets <- c("geppei", "manju", "N/A", "sakuramochi", "N/A")
na_if(sweets, "N/A")
#> [1] "geppei"      "manju"       NA            "sakuramochi" NA  

のようにすれば"N/A"NAで置き換えられます。

near()

near(x, y) is a helper for abs(x - y) < tol (#1607).

これはall.equal()と同じやつだと思います。計算誤差の許容範囲なら同じと判定する関数です。

2 == sqrt(2)^2
#> [1] FALSE
near(2, sqrt(2)^2)
#> [1] TRUE

recode()

recode() is vectorised equivalent to switch() (#1710).

これもほしかったやつです。switch()は長さ1のものにしか使えませんでしたが、recode()はベクトルに対して使うことができます。

x <- c("a", "b", "a", "b", "a", "c")
recode(x, a = "あ", .default = "うん")
#> [1] "あ"   "うん" "あ"   "うん" "あ"   "うん"

union_all()

union_all() method. Maps to UNION ALL for SQL sources, bind_rows() for data frames/tbl_dfs, and combine() for vectors (#1045).

バックエンドの種類によって適切な関数を使い分けてくれるラッパーみたいです。

summarisemutate関数のシリーズに_all(),_if(),_at()が入った

A new family of functions replace summarise_each() and mutate_each() (which will thus be deprecated in a future release)

select_if() lets you select columns with a predicate function. Only compatible with local sources.

これはけっこう大きな変更です。

たとえば、これまでのmutate_each()では、

iris %>%
  group_by(Species) %>%
  summarise_each(funs(mean), starts_with("S"))

と書いていたものが、

iris %>%
  group_by(Species) %>%
  summarise_at(vars(starts_with("S")), funs(mean))

と書くようになります。なんでこんなめんどくさい記法になったかというと、対象カラムの選択をもっと柔軟にできるようにするためなんですが、 その便利さについて語るにはここではちょっと狭いのでまた別の記事に書きます(たぶん)。

↓個人的には、こういうIssueを立ててたりしたので待望の機能だったりします。

Local backends

dtplyr

data.table用のパッケージらしいです。私はdata.table使わないのでよく分かりません…

Tibble

tbl_df用のパッケージらしいです。詳しくはtibble 1.0.0 | RStudio Blogを読めとのこと。

いくつかtbl_dfをいじるときに便利そうな関数が用意されています。

  • all_equal(): すべての要素が同じかチェックする関数。
  • lst(): リストをつくる関数。data_frame()と同じような感じ。
  • add_row(): 行を1つ追加する関数。↓のような感じで使える。
df <- data_frame(x = 1:3, y = 3:1)
add_row(df, x = 4, y = 0)
#> Source: local data frame [4 x 2]
#> 
#>       x     y
#>   <dbl> <dbl>
#> 1     1     3
#> 2     2     2
#> 3     3     1
#> 4     4     0

tbl_cube

2次元以上のデータをうまいこと扱うためのクラスらしいです。

Remote backends

SQLite

SQL translation

(この辺よく分からないので割愛します…)

Internals

SQLへの変換の仕方がけっこう変わってるっぽいんですが、元を知らないのでよく分かりませんでした。リリースノートにもわりと詳し目に書いてありますが、いずれ改めて解説が出てくる気がするので、それを待つことにします。

Minor improvements and bug fixes

空の値や欠損値などの取り扱いがいい感じに改良されていたりします。

感想

けっこう便利関数が増えている。いいことですね。ガンガン使っていきましょう。

lst()とかtbl_cube()とかいう関数ができてることから察するに、dplyrはdata.frameとは異なるようなデータも扱う方向に進もうとしているようにも見えます。 野心的すぎて事故らなければいいですが。。とはいえdata.frame以外も扱えると色々処理の幅が広がりそうなので期待したいです。

内部の話はもうちょいわかるようになりたいです。カラテが足りない…