web-dev-qa-db-ja.com

rbindlistがrbindよりも「良い」のはなぜですか?

data.tableのドキュメントを調べていますが、SOでの会話のいくつかから、rbindlistrbindよりも優れていることに気づきました。

rbindlistrbindよりも優れている理由と、どのシナリオでrbindlistrbindより優れているのかを知りたいのですが。

メモリ使用率の面で利点はありますか?

127
Chinmay Patil

rbindlistdo.call(rbind, list(...))の最適化されたバージョンであり、rbind.data.frameの使用時に低速であることが知られています


本当にエクセルはどこですか

rbindlistが輝く場所を示すいくつかの質問は

行ごとのdata.framesのリストの高速ベクトル化マージ

do.callとldplyを使用して、data.framesの長いリスト(最大100万)を単一のdata.frameに変換する際のトラブル

これらには、どれだけ速くできるかを示すベンチマークがあります。


rbind.data.frameが遅い理由

rbind.data.frameは多くのチェックを行い、名前で一致します。 (つまり、rbind.data.frameは列の順序が異なる可能性があり、名前で一致するという事実を考慮します)、rbindlistはこの種のチェックを行わず、位置によって結合します

例えば

do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
##    a b
## 1  1 2
## 2  2 3
## 3  2 1
## 4  3 2

rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

Rbindlistのその他の制限

を使用してfactorsに対処するのに苦労しましたが、バグが修正されました。

rbindlistには2つのdata.tablesがあり、一方にはファクタがあり、もう一方にはカラムの文字タイプがありますBug#265

列名の重複に問題がある

警告メッセージ:in rbindlist(allargs):強制によって導入されたNA:data.tableにバグがある可能性がありますか?Bug#2384


rbind.data.frameの行名はイライラすることがあります

rbindlistlistsdata.framesおよびdata.tablesを処理でき、行名のないdata.tableを返します

do.call(rbind, list(...))を使用して行名の混雑を取得できます

do.call内でrbindを使用するときに行の名前変更を回避する方法


メモリ効率

メモリに関してはrbindlistCに実装されているため、メモリ効率が高いため、setattrを使用して参照によって属性を設定します。

rbind.data.frameRで実装され、多くの割り当てを行い、attr<-(およびclass<-rownames<-を使用します。これらはすべて(内部的に)のコピーを作成します作成されたdata.frame。

147
mnel

v1.9.2rbindlistによってかなり進化し、次のような多くの機能が実装されました。

  • バインド中に列の最高のSEXPTYPEを選択-v1.9.2で実装 FR#2456 および Bug#4981 を閉じます。
  • factor列を適切に処理する-最初にv1.8.10で実装 バグ#265 を実装し、バインディングに拡張orderedv1.9.2も慎重に考慮して、 FR#4856 および Bug#5019 を閉じます。

さらに、v1.9.2で、rbind.data.tablefill引数を取得しました。これにより、Rに実装されている欠落した列を埋めることでバインドできます。

v1.9.3では、これらの既存の機能がさらに改善されています。

  • rbindlistは引数use.namesを取得します。デフォルトでは、下位互換性のためにFALSEです。
  • rbindlistも引数fillを取得します。これは、下位互換性のためにデフォルトでFALSEです。
  • これらの機能はすべてCで実装されており、機能を追加する際に速度を落とさないように慎重に記述されています。
  • rbindlistは名前で一致し、欠落している列を埋めることができるため、rbind.data.tablerbindlistを呼び出すだけです。唯一の違いは、下位互換性のために、use.names=TRUEのデフォルトでrbind.data.tableであることです。

rbind.data.frameは、主に(Cに移行することで)回避できるコピー(@mnelが同様に指摘している)により、かなり遅くなります。それだけが理由ではないと思います。また、rbind.data.frameの列名をチェック/照合するための実装は、data.frameごとに多くの列があり、バインドするそのようなdata.framesが多い場合に遅くなる可能性があります(以下のベンチマークを参照)。

ただし、rbindlistは特定の機能(因子レベルのチェックや名前の一致など)を欠いているため、rbind.data.frameよりも高速であることに非常に小さい(またはまったくない)重みがあります。これは、速度とメモリが最適化されたCで慎重に実装されたためです。

以下は、rbindlistuse.namesv1.9.3機能を使用して、列名で照合しながら効率的なバインディングを強調するベンチマークです。データセットは、サイズがそれぞれ10 * 500の10000個のdata.framesで構成されています。

NB:このベンチマークは、dplyrbind_rowsとの比較を含むように更新されました。

library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
    data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
    setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
    .Call("Csetlistelt", ll, i, foo())
}

system.time(ans1 <- rbindlist(ll))
#  user  system elapsed 
# 1.226   0.070   1.296 

system.time(ans2 <- rbindlist(ll, use.names=TRUE))
#  user  system elapsed 
# 2.635   0.129   2.772 

system.time(ans3 <- do.call("rbind", ll))
#   user  system elapsed 
# 36.932   1.628  38.594 

system.time(ans4 <- bind_rows(ll))
#   user  system elapsed 
# 48.754   0.384  49.224 

identical(ans2, setDT(ans3)) 
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE

名前を確認せずに列をバインドするのに必要な時間はわずか1.3秒でしたが、列名の確認とバインドには1.5秒しかかかりませんでした。基本ソリューションと比較すると、これはdplyrのバージョンよりも14倍、18倍高速です。

41
Arun