注:これは2017/04/15に公開した「dplyr 0.6.0-rcを使ってみる」という記事を加筆修正したものです。
dplyr 0.6.0は5/11ごろにリリース予定でしたがなかなかリリースされず…
dplyr 0.6.0 is scheduled for release in 4 weeks: https://t.co/zlLoZy5pLv. Lots of 👍🎉 stuff inside. Please try it out and report 🐛🐞! #rstats
— Hadley Wickham (@hadleywickham) 2017年4月13日
約1カ月遅れで0.7.0として6/9についにリリースされました!
CRAN updates: dplyr https://t.co/y5W2NTKSXT #rstats
— CRAN Package Updates (@CRANberriesFeed) 2017年6月9日
かなりの変更が入っています。全部は見ていられないので、NEWSに載っている中から気になった変更点を拾ってみます。
New data, functions, and features
新規のデータセット
データセットが増えました。
starwars
: スターウォーズのキャラクターのデータ。リスト型の列がある。storms
: 台風のデータband_members
、band_instruments
、band_instruments2
: バンドメンバーのデータ。joinの説明に使うやつらしいです。
add_count()
、add_tally()
- New
add_count()
andadd_tally()
for adding ann
column within groups (#2078, @dgrtwo).
おさらいですが、count()
は指定した列のカテゴリごとの個数を出してくれます。
count(starwars, species, sort = TRUE) #> # A tibble: 38 × 2 #> species n #> <chr> <int> #> 1 Human 35 #> 2 Droid 5 #> 3 <NA> 5 #> 4 Gungan 3 #> 5 Kaminoan 2 #> 6 Mirialan 2 #> 7 Twi'lek 2 #> 8 Wookiee 2 #> 9 Zabrak 2 #> 10 Aleena 1 #> # ... with 28 more rows
しかし、このn
をもとのデータと紐づけて使いたいことがよくあります。例えば、n
が2以上の種族だけに絞り込みたい場合とか。
そういうとき、これまでは、一度グループ化して、n()
をとって、またグループ化を解除する、ということをする必要がありました。
sw <- select(starwars, name, species) sw %>% group_by(species) %>% mutate(n = n()) %>% ungroup()
add_count()
を使うとこれをあっさりやってくれます。
sw %>% add_count(species) #> # A tibble: 87 × 3 #> name species n #> <chr> <chr> <int> #> 1 Luke Skywalker Human 35 #> 2 C-3PO Droid 5 #> 3 R2-D2 Droid 5 #> 4 Darth Vader Human 35 #> 5 Leia Organa Human 35 #> 6 Owen Lars Human 35 #> 7 Beru Whitesun lars Human 35 #> 8 R5-D4 Droid 5 #> 9 Biggs Darklighter Human 35 #> 10 Obi-Wan Kenobi Human 35 #> # ... with 77 more rows
add_tally()
も似たような感じなので省略(多くの人はそもそもtally()
になじみがなさそう…)。
arrange()
の.by_group
引数
arrange()
for grouped data frames gains a.by_group
argument so you can choose to sort by groups if you want to (defaults toFALSE
) (#2318)
dplyrは0.5.0になったときに、arrange()
がグループ化を無視する挙動になっていました。
つまり、こういうことです。
set.seed(6) by_cyl <- mtcars %>% group_by(cyl) %>% select(cyl, wt) %>% # 各グループから3つづつ列を抽出 sample_n(3) by_cyl %>% arrange(desc(wt)) #> Source: local data frame [9 x 2] #> Groups: cyl [3] #> #> cyl wt #> <dbl> <dbl> #> 1 8 5.424 #> 2 8 3.570 #> 3 6 3.440 #> 4 6 3.440 #> 5 8 3.435 #> 6 6 3.215 #> 7 4 3.150 #> 8 4 2.465 #> 9 4 1.513
ちょっとわかりづらいですが、グループ化のキーであるcyl
列を無視してwt
列だけでソートされています。
cyl
列もソートに使うには、明示的に
by_cyl %>% arrange(cyl, desc(wt))
と書く必要がありました。0.6.0では、.by_group = TRUE
を指定すると、グループ化のキー列もソートに使ってくれます。
by_cyl %>% arrange(desc(wt), .by_group = TRUE) #> Source: local data frame [9 x 2] #> Groups: cyl [3] #> #> cyl wt #> <dbl> <dbl> #> 1 4 3.150 #> 2 4 2.465 #> 3 4 1.513 #> 4 6 3.440 #> 5 6 3.440 #> 6 6 3.215 #> 7 8 5.424 #> 8 8 3.570 #> 9 8 3.435
pull()
- New
pull()
generic for extracting a single column either by name or position (either from the left or the right). Thanks to @paulponcet for the idea (#2054).
これは、data.frameから1つの列をベクトルとして取り出す関数です。lplyrパッケージというリスト用のdplyrみたいなパッケージがあって、そこから取ってきた関数らしいです。
[[
とほぼ動きをします。
data_frame(1:3) %>% pull(1) #> [1] 1 2 3
[[
(とかそのエイリアスであるmagrittr::extract2()
)でよくない?という話ですが、[[
はデータベースをバックエンドに持つものに対してはうまく動作しません。
# 注:この例を実行するにはdbplyrパッケージが必要です dbplyr::memdb_frame(1:3) %>% `[[`(1) #> src: sqlite 3.11.1 [:memory:] #> tbls: akpfylaauw, kimorgsivb, sqlite_stat1, ykaaxhbrja
pull()
を使っておけばそのへんの面倒を見てくれます。
dbplyr::memdb_frame(1:3) %>% pull(1) #> [1] 1 2 3
インデックスではなくて列名を指定することもできます。
#> dbplyr::memdb_frame(x = 1:3) %>% pull(x) [1] 1 2 3
as_tibble
as_tibble()
is re-exported from tibble. This is the recommend way to create tibbles from existing data frames.tbl_df()
has been softly deprecated.tribble()
is now imported from tibble (#2336, @chrMongeau); this is now prefered toframe_data()
.
data.frameをtibbleに変換するのにas_tibble()
が推奨され、tbl_df()
がdeprecatedになりました。
同様に、ゼロからtibbleをつくるときに使うのも、tribble()
が推奨され、frame_data()
がdeprecatedになりました。
Deprecated and defunct
- dplyr no longer messages that you need dtplyr to work with data.table (#2489).
dplyrとdata.tableパッケージをいっしょに使うときはdtplyrパッケージを使ってね、という親切メッセージが出てたんですが、もういいだろうということででなくなります。
- Long deprecated
regroup()
has been removed.
なんですっけこの関数…?
- Deprecated
failwith()
. I’m not even sure why it was here.
purrr::possibly()
に当たるものらしいです。同じく使った記憶がない…
Databases
ほとんどのコードはこれまで通り動きますが(ほんとか…?)、2つ大きな変更があるとのことです。
- DB関連のコードはdbplyrパッケージという新しいパッケージに移されました*1。
src_*()
系の関数は不要になりました。DBIのコネクションを直接渡せば動きます。↓こんな感じです。
library(dplyr) con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") DBI::dbWriteTable(con, "mtcars", mtcars) mtcars2 <- tbl(con, "mtcars") mtcars2
ちなみに後者はKirill Muller氏ががんばってDBIを改善したおかげです。次のUTF-8関連の改善もこのひとの貢献です。かっこよすぎかよ…
UTF-8
これまでこのブログでもdplyrの文字コードまわりの問題を取り上げてきましたが、
全部なおっています。
ほんとに劇的に改善しています。ぜひ試してみてください。
ちなみに内部的には、列名をシンボルに変換せずに文字列のまま扱うという方針をとっています。
- Internally, column names are always represented as character vectors, and not as language symbols, to avoid encoding problems on Windows (#1950, #2387, #2388).
これに伴って挙動が変わっている関数もあるので、dplyrを使ってパッケージを開発されている方は注意した方がいいかもしれません。たとえばgroup_vars()
は文字列を返すようになっています。
group_vars(df) #> [1] "x" "y"
Colwise functions
rename()
,select()
,group_by()
,filter()
andtransmute()
now have scoped variants (verbs suffixed with_if()
,_at()
and_all()
). Likemutate_all()
,summarise_if()
, etc, these variants apply an operation to a selection of variables.
mutate()
やsummarise()
のようにrename()
、select()
、group_by()
、filter()
、transmute()
にも_if()
、_at()
、_all()
というサフィックスが付くバージョンの関数ができました。
Colwise functionsの使い方に関しては以下の記事に書いたので割愛します。新しくできたやつも基本的には同じような挙動のはずです。
- The scoped verbs taking predicates (
mutate_if()
,summarise_if()
, etc) now support S3 objects and lazy tables. (snip)
データベースとかに対しても使えるようになったよ、ということらしいです。
Tidyeval
これまでdplyrにはNSE版の関数とSE版の関数がありましたが、これからは新しいアプローチのNSEですべてを扱います。それが「tidyeval」と呼ばれる概念です。これについては長くなりそうなので別に書きますが、一点だけ。
This means that the underscored version of each main verb is no longer needed, and so these functions have been deprecated (but remain around for backward compatibility).
「underscored version of each main verb」というのはselect_()
とかmutate_()
とかの話です。これがdeprecatedになって、いずれ消されます。けっこうな混乱になる予感がします。備えましょう。
Verbs
Joins
[API]
xxx_join.tbl_df(na_matches = "never")
treats allNA
values as different from each other (and from any other value), so that they never match.
*_join()
系の関数は、NA
でマッチしなくなりました。これは世の中のデータベースがそういう挙動になっているためで、一貫性を持たせるためにそちらの挙動に寄せたようです。
その後の議論で、後方互換性を保つためna_matches = "na"
がデフォルトになりました。na_matches = "never"
を意図的に指定すると、以下のような挙動になります。
x <- tribble( ~name, ~value, "a", 1, "b", 2, "c", NA ) y <- tribble( ~name, ~value, "a", 1, "b", 100, "c", NA ) inner_join(x, y, na_matches = "never") #> Joining, by = c("name", "value") #> # A tibble: 1 × 2 #> name value #> <chr> <dbl> #> 1 a 1
"c", NA
の行が共通にありますが、マッチしていません。これがデフォルトの挙動で、na_matches = "na"
を付けるとマッチさせることができます。
inner_join(x, y, na_matches = "na") #> Joining, by = c("name", "value") #> # A tibble: 2 × 2 #> name value #> <chr> <dbl> #> 1 a 1 #> 2 c NA
Select
- For selecting variables, the first selector decides if it’s an inclusive selection (i.e., the initial column list is empty), or an exclusive selection (i.e., the initial column list contains all columns). This means that
select(mtcars, contains("am"), contains("FOO"), contains("vs"))
now returns again botham
andvs
columns like in dplyr 0.4.3 (#2275, #2289, @r2evans).
これはちょっと難しくてよくわからないけど、まあうまく動くようになったよ、ということらしいです。以下の議論を追うとわかりそうでした。
select()
(and the internal functionselect_vars()
) now support column names in addition to column positions. As a result, expressions likeselect(mtcars, "cyl")
are now allowed.
サラッと書いてますが、これまでselect()
とselect_()
と使い分けていたのがもうselect()
だけで大丈夫になった、ということみたいです。
つまり、以下はすべて同じ結果になります。tidyevalすらいらない感…
# 列名を変数名として指定 select(mtcars, cyl) # 列名を文字列として指定 select(mtcars, "cyl") # 列名を変数に入れてそれを指定 col <- "cyl" select(mtcars, col)
Other
recode()
,case_when()
andcoalesce()
now support splicing of arguments with rlang’s!!!
operator.
これもtidyevalの話なので詳しくは書きませんが、recode()
とかでも!!!
が使えるようになりました。便利そう。
mutate()
recycles list columns of length 1 (#2171).
mutate()
がリストもリサイクルしてくれるようになりました。つまり、こういう感じのことができます。
iris %>% mutate(a = list(data.frame(1:3)))
Combining and comparing
bind_rows()
とbind_cols()
の挙動がけっこう変わっている雰囲気なので、よく使っている人は注意しましょう。
- Breaking change:
bind_rows()
andcombine()
are more strict when coercing. Logical values are no longer coerced to integer and numeric. Date, POSIXct and other integer or double-based classes are no longer coerced to integer or double as there is chance of attributes or information being lost (#2209, @zeehio).
bind_rows()
やcombine()
が少し厳しくなりました。論理値型と数値型を一緒にしようとしたときや、日付・時刻型を数値型と一緒にしようとしたときはエラーになります。
bind_rows()
andbind_cols()
now accept vectors. They are treated as rows by the former and columns by the latter. Rows require inner names likec(col1 = 1, col2 = 2)
, while columns require outer names:col1 = c(1, 2)
. Lists are still treated as data frames but can be spliced explicitly with!!!
, e.g.bind_rows(!!! x)
(#1676).
bind_rows()
やbind_cols()
がベクトルを受け付けるようになりました。
x <- c(A = 1, B = 2) y <- c(A = 3, B = 4) bind_rows(x, y) #> # A tibble: 2 × 2 #> A B #> <dbl> <dbl> #> 1 1 2 #> 2 3 4
ただし、ベクトルのリストだとdata.frameとして扱われてしまうので、bind_rows()
であっても列方向にbindされてしまいます。
ll <- list( x = x, y = y ) bind_rows(ll) #> # A tibble: 2 × 2 #> x y #> <dbl> <dbl> #> 1 1 3 #> 2 2 4
これもtidyevalですが、こういうときは!!!
を使いましょう、とのことでした。
bind_rows(!!! ll) #> # A tibble: 2 × 2 #> A B #> <dbl> <dbl> #> 1 1 2 #> 2 3 4
- After a period of deprecation,
rbind_list()
andrbind_all()
have been removed from the package. Please usebind_rows()
instead.
rbind_list
とrbind_all
がついに消え去りました。もしまだコードの中で使っていたら注意してください。
Vector functions
細かいのがいろいろ、という感じです。
recode()
gains.dots
argument to support passing replacements as list (#2110, @jlegewie).
これも!!!
があれば要らない気もするけど、.dots
で置換のリストを渡せるようになったらしいです。
Other minor changes and bug fixes
その他
別パッケージになってしまったのでdplyrのNEWSには載ってきませんが、データベース関連の変更点はdbplyrのNEWSに載っています。
例えば、吐かれるクエリが結構きれいになっているというのもわりと注目すべき点です。
SQL joins have been improved:
というあたりを読んでみてください。
感想
久々のdplyrのリリース、内部的には大幅な変更がたくさんあるので混乱しそうな予感しかしませんが、改善された機能はどれもけっこうよさげに見えます。待ち遠しいですね。
まだまだバグもあるようなので、使ってみて変なことがあればr-wakalangやTwitterあたりで私に声をかけてください。調べたりissueを上げたりとかします。憧れのLionel=サンにツイートをもらえたのがうれしくて私はがんばります。
no worries! Please report any issues. Still some way to go for unrepresentable characters unfortunately (e.g. latin1 users on windows)
— lionel (@_lionelhenry) 2017年4月14日