LoginSignup
11
8

More than 5 years have passed since last update.

【dplyr vs data.table】data.tableを使ったデータのマージ

Last updated at Posted at 2018-04-23

はじめに

Rで機械学習等分析をやる上で欠かせないのがdata.frameですが、大規模(数万レコード〜)データを扱いだすと段々と苦しくなってきます。そこで登場するのがdata.tableです。ただ、data.frameに慣れた人にとっては、data.tableの独特な記法を難しく感じることもあるかと思います。基本的な使い方とともにあまり解説の見つからなかったデータマージのところを自分メモも含めて書いておきます。

基本的なdata.tableの使い方

data.tableの作成・型変換等

データの作成等においてはdata.frameとdata.tableの間で大きな違いはありません。ただ、データのインポートに関していうとfread()read.table()等と比較してとんでもなく高速に読み込むのでこれだけでも価値があります。

内容 data.frame data.table
データの作成 data.frame() data.table()
型の変換 as.data.frame() as.data.table()
データのインポート read.table() fread()
data.tableによるインポート
dt <- fread("./data.csv") # 基本的な使い方
df <- fread("./data.csv", data.table = F) # data.frameとして読込

もしdata.tableの使い方が今ひとつよくわからない、という場合はfread()data.table = Fというオプションをつけることでdata.frameとして読み込むことが可能です。ただ単に高速にインポートしたい場合はこれでいいでしょう。freadはcsvやtsv等、いろんなデータ形式をよしなにインポートしてくれますが、日本語が苦手などの弱点もあるので、詳しくはこの記事の最後につける各種リンクを参照ください。

data.tableの要素へのアクセス、抽出等

内容 data.frame data.table
要素へのアクセス(行) df[n,] dt[n]
要素へのアクセス(列) df[,n] dt[,n, with = F]
要素の抽出 df[df$x == 3,] dt[x == 3]

要素へのアクセスにおいて行を選択する場合、dt[1:3]というようにインデックスでアクセスできますが、その際にコンマはいりません(あってもOK)。注意すべきは列選択をする際にはwith = Fオプションが必要なことです。

data.table記法 (代入・列削除・列追加)

内容 data.frame data.table
列追加 df$new <- a dt[, new:= a]
列削除 df$old <- NULL dt[,old:=NULL]
値の変更 df$old <- a dt[,old:=a]

data.table記法の最大の特徴は上記の値の代入や列操作にあります。data.frameは代入によって追加・削除をするのに対して、data.table:=という代入子を使うことで直接データを書き換えられるところにあります。なれると病みつきになりそうです。

データのフィルタリング/集約 (dplyrとの比較)

データのフィルタリングや集約にはdplyrを使うことが多いかと思いますが、data.tableを使うことでより高速に、完結に書くこともできます。

内容 dplyr data.table
フィルタリング df %>% filter(条件式1 & 条件式2) dt[条件式1 & 条件式2]
集約 df %>% summarise(max = max(x)) dt[, max(x)]
グルーピング df %>% group_by(x) %>% summarise(max = max(y)) dt[,max(y), by = x]

dplyrでパイプを使ったフィルタリングや集約はとても便利ですが、data.tableを使うとより完結に書くこともできます。ここは慣れの部分でしょう。なおdata.tabledplyrは併用可能なので、パイプを使ってdata.talbeを処理することも可能です。

データのマージ (dplyrとの比較)

 ようやく本題です。dplyrを使うことでSQL的にjoinすることができますが、data.tableでも似たようなことができます。

内容 dplyr data.table::merge() data.table記法
Inner Join inner_join(left, right, by = "key") merge(left, right, by = "key", all = F) left[right, on = "key", nomatch = 0]
Left Join left_join(left, right, by = "key") merge(left, right, by = "key", all.x = T) right[left, on = "key"]
Right Join right_join(left, right, by = "key") merge(left, right, by = "key", all.y = T) left[right, on = "key"]
Full Join full_join(left, right, by = "key") merge(left, right, by = "key", all = T) -

data.tableにはmerge()が用意されており、オプション(all, all.x, all.y)を記述することで各種joinを実現しています。 keyはdplyr/data.tableともに設定しなければ、両方のデータに共通する列名を元によしなにマージしてくれます。1また、data.tableにおいてはキーを予めセットするsetkey()があり、これを予め設定しておくとonオプションは不要です。

さらにdata.tableに関していうと、inner/left/rightに限定されますが、data.table記法によってより簡単に書くことができます。2

data.table記法のトリッキーな使い方

ここでの議論の中で、メモリ効率を重視したよりよいマージ方法が提案されています。前項よりもさらに限定されますが、マージすべき列が1つだけの場合、下記のように書くことで代入をすることなく、新たなメモリ領域を確保することもなくマージすることができます。つまりより高速に、よりメモリ効率よくマージができます。

data.table記法を使ったマージ
left <- data.table(x = 1:6, y = 4:9)
right <- data.table(x = 1:4, z = 6:9)
left[right, on = "x", z:= z]

data.table記法によるマージは限定的3なので、merge()と併用してまで使うべきかどうかは可読性等を考えると微妙なところではありますが、覚えておいて損はないでしょう。4

複数キーを使ったマージ

上記の例ではbyやonで指定したキーは1つだけでしたが、複数の列をキーとしてマージすることもできます。具体的にはkey = c("x", "y")on = c("x", "y")と指定することでできます。

dplyrdata.tableの速度比較

これまでdplyrdata.tableの書き方についてご紹介しましたが、それではどちらの方がマージが早いのでしょうか?

data作成
dt.left <- data.table(id = seq(4e+06),
                cat.l = sample(letters[1:26], 4e+06, replace = T),
                val.l = runif(4e+06))
dt.right <- data.table(id = seq(4e+06),
                       cat.r = sample(letters[1:26], 4e+06, replace = T),
                       val.r = runif(4e+06))
dplyr
system.time(res_dplyr <- left_join(dt.left, dt.right, by = "id"))

   ユーザ   システム       経過  
     1.389      0.198      1.621 
data.table
system.time(res_merge <- merge(dt.left, dt.right, by = "id"))

   ユーザ   システム       経過  
     0.229      0.060      0.290 
data.table記法
system.time(res_data.table <- dt.left[dt.right, on = "id"])

   ユーザ   システム       経過  
     0.167      0.032      0.209 
data.table記法(参考)
## マージされるのはcat.rだけです
system.time(dt.left[dt.right, on = "id", cat.r:=cat.r])

   ユーザ   システム       経過  
     0.132      0.010      0.143 

単純なJoinに関してはdplyrと比較してdata.table::merge()やdata.table記法の方が圧倒的に早いことがわかりました。dplyr自体もbase::merge()と比較して高速なので5、いかにdata.tableが早いかがわかりますね。トリッキーな記法は早いことは早いですが、制約が多い割にそこまで早くもないので、data.table::merge()かdata.table記法(+代入)で良さそうです。

参考リンク


  1. 予期せぬマージを防ぐためにも愚直に書いたほうがいいでしょう。 

  2. 直感的にはleft joinの時にleftに来るdata.tableを右に書く(right[left])のが気持ちわるいですが 

  3. Inner/left/rightのみ 

  4. なおこの書き方だとleft/right joinが逆になります。つまりright[left, on = "x", y:=y]だとright joinになります。 

  5. http://d.hatena.ne.jp/dichika/20140104/p1 

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8