はじめに
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() |
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.table
とdplyr
は併用可能なので、パイプを使って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つだけの場合、下記のように書くことで代入をすることなく、新たなメモリ領域を確保することもなくマージすることができます。つまりより高速に、よりメモリ効率よくマージができます。
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")
と指定することでできます。
dplyr
とdata.table
の速度比較
これまでdplyr
とdata.table
の書き方についてご紹介しましたが、それではどちらの方がマージが早いのでしょうか?
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))
system.time(res_dplyr <- left_join(dt.left, dt.right, by = "id"))
ユーザ システム 経過
1.389 0.198 1.621
system.time(res_merge <- merge(dt.left, dt.right, by = "id"))
ユーザ システム 経過
0.229 0.060 0.290
system.time(res_data.table <- dt.left[dt.right, on = "id"])
ユーザ システム 経過
0.167 0.032 0.209
## マージされるのは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記法(+代入)で良さそうです。
参考リンク
- data.tableの基本的な使い方: 大規模データの高速処理 ーdata.table、dplyrー
- data.table記法とmergeの比較: JOINing data in R using data.table
- data.table記法を使った効率的なマージ: Left join using data.table
- マージの速度比較: dplyrのjoinがmergeに比べて3倍速い