Rで識別子によってグループ化されたデータフレームの最初の行を取得する高速な方法[終了]


14

個人ごとに複数の観測があるときに年齢と性別を取得するときのように、データセットの最初の行のみを識別子でグループ化する必要がある場合があります。Rでこれを行うための高速(または最速)の方法は何ですか?下のaggregate()を使用しましたが、もっと良い方法があると思います。この質問を投稿する前に、Googleで少し検索し、ddplyを見つけて試しましたが、非常に遅く、データセット(400,000行×16列、7,000の一意のID)でメモリエラーが発生したことに驚きましたが、aggregate()バージョンかなり速かった。

(dx <- data.frame(ID = factor(c(1,1,2,2,3,3)), AGE = c(30,30,40,40,35,35), FEM = factor(c(1,1,0,0,1,1))))
# ID AGE FEM
#  1  30   1
#  1  30   1
#  2  40   0
#  2  40   0
#  3  35   1
#  3  35   1
ag <- data.frame(ID=levels(dx$ID))
ag <- merge(ag, aggregate(AGE ~ ID, data=dx, function(x) x[1]), "ID")
ag <- merge(ag, aggregate(FEM ~ ID, data=dx, function(x) x[1]), "ID")
ag
# ID AGE FEM
#  1  30   1
#  2  40   0
#  3  35   1
#same result:
library(plyr)
ddply(.data = dx, .var = c("ID"), .fun = function(x) x[1,])

更新:チェイスの答えとマットパーカーのコメントを参照してください。data.tableパッケージを使用する最速のソリューションについては、@ Matthew Dowleの回答を参照してください。


あなたのすべての答えをありがとう。@Steveのdata.tableソリューションは、@ Gavinのaggregate()ソリューション(私のaggregate()コードよりも高速でした)よりも私のデータセットで約5倍、そして約7.5倍の速さで最速でした@Mattのby()ソリューション上。すぐに機能させることができなかったので、私は再構築のアイデアの時間を決めませんでした。@Chaseが提供した解決策は最速であり、実際に私が探していたものだったと思いますが、このコメントを書き始めたとき、コードは機能していませんでした(現在修正されています!)
ロックオフ

実際、@ Chaseはdata.tableよりも9倍高速だったので、受け入れられる答えを変更しました。皆に感謝します-たくさんの新しいツールを学びました。
ロックオフ

申し訳ありませんが、コードを修正しました。ここでの1つの注意点またはトリックは、でIDの1つではない値を連結しdiff()て、で最初のIDを取得できるようにすることですdx
チェイス

回答:


10

ID列は本当に要因ですか?実際に数値である場合は、このdiff関数をあなたの利益のために使用できると思います。で数値に強制することもできますas.numeric()

dx <- data.frame(
    ID = sort(sample(1:7000, 400000, TRUE))
    , AGE = sample(18:65, 400000, TRUE)
    , FEM = sample(0:1, 400000, TRUE)
)

dx[ diff(c(0,dx$ID)) != 0, ]

1
賢い!dx[c(TRUE, dx$ID[-1] != dx$ID[-length(dx$ID)], ]数値以外のデータに対してもできます-文字に対して0.03、因子に対して0.05を取得します。PS:)最初のsystem.time()関数には、2番目のゼロの後に余分なものがあります。
マットパーカー

@Matt-良いコールと素晴らしいキャッチ。今日、フリップの価値があるコードをコピー/貼り付けできないようです。
チェイス

私はLondon Cycle Hireスキームに取り組んでおり、ユーザーの自転車レンタルの最初と最後のインスタンスを見つける方法を見つける必要がありました。100万人のユーザー、年間1,000万回の旅行、数年のデータがあるため、「for」ループは毎秒1ユーザーを実行していました。「by」ソリューションを試しましたが、1時間後に完了しませんでした。最初は「Matt ParkerのChaseのソリューションの代替」が何をしていたのか理解できませんでしたが、ついにペニーは落ち、数秒で実行されます。したがって、データセットが大きくなるほど改善が大きくなるという点は、私の経験によって証明されています。
ジョージシンプソン

@GeorgeSimpson-これがまだ参照されているのを見てうれしいです!data.table以下の解決策が最速であることが証明されるはずなので、私があなただったらそれをチェックします(おそらくここで受け入れられた答えであるべきです)。
チェイス

17

Steveの返事に続いて、data.tableにははるかに速い方法があります。

> # Preamble
> dx <- data.frame(
+     ID = sort(sample(1:7000, 400000, TRUE))
+     , AGE = sample(18:65, 400000, TRUE)
+     , FEM = sample(0:1, 400000, TRUE)
+ )
> dxt <- data.table(dx, key='ID')

> # fast self join
> system.time(ans2<-dxt[J(unique(ID)),mult="first"])
 user  system elapsed 
0.048   0.016   0.064

> # slower using .SD
> system.time(ans1<-dxt[, .SD[1], by=ID])
  user  system elapsed 
14.209   0.012  14.281 

> mapply(identical,ans1,ans2)  # ans1 is keyed but ans2 isn't, otherwise identical
  ID  AGE  FEM 
TRUE TRUE TRUE 

各グループの最初の行だけが必要な場合は、その行に直接参加する方がはるかに高速です。毎回.SDオブジェクトを作成し、その最初の行のみを使用するのはなぜですか?

0.064のdata.tableを「Matt ParkerによるChaseのソリューションの代替」と比較します(これは、これまでで最速のようです)。

> system.time(ans3<-dxt[c(TRUE, dxt$ID[-1] != dxt$ID[-length(dxt$ID)]), ])
 user  system elapsed 
0.284   0.028   0.310 
> identical(ans1,ans3)
[1] TRUE 

約5倍高速ですが、100万行未満の小さなテーブルです。サイズが大きくなると、違いも大きくなります。


うわー、私は[.data.table関数がどのように「スマート」になるかを本当に評価しませんでした...私は.SDあなたが本当にそれを必要としないならオブジェクトを作成しなかったことに気づかなかったと思います。良いですね!
スティーブLianoglou

はい、それは確かに速いです!dxt <- data.table(dx, key='ID')system.time()の呼び出しに含めても、@ Mattのソリューションよりも高速です。
ロックオフ

新しいdata.tableバージョンSD[1L]は完全に最適化されており、実際には@SteveLianoglouの回答は5e7行に対して2倍高速になるため、これは今では時代遅れだと思います。
デビッドアレンバーグ

@DavidArenburg 2016年11月v1.9.8から、はい。この回答を直接編集してください。または、このQがコミュニティWikiなどである必要があります。
マットダウル

10

複数のmerge()ステップは必要ありません。aggregate()両方の目的の変数だけが必要です。

> aggregate(dx[, -1], by = list(ID = dx$ID), head, 1)
  ID AGE FEM
1  1  30   1
2  2  40   0
3  3  35   1

> system.time(replicate(1000, aggregate(dx[, -1], by = list(ID = dx$ID), 
+                                       head, 1)))
   user  system elapsed 
  2.531   0.007   2.547 
> system.time(replicate(1000, {ag <- data.frame(ID=levels(dx$ID))
+ ag <- merge(ag, aggregate(AGE ~ ID, data=dx, function(x) x[1]), "ID")
+ ag <- merge(ag, aggregate(FEM ~ ID, data=dx, function(x) x[1]), "ID")
+ }))
   user  system elapsed 
  9.264   0.009   9.301

比較のタイミング:

1)マットのソリューション:

> system.time(replicate(1000, {
+ agg <- by(dx, dx$ID, FUN = function(x) x[1, ])
+ # Which returns a list that you can then convert into a data.frame thusly:
+ do.call(rbind, agg)
+ }))
   user  system elapsed 
  3.759   0.007   3.785

2)Zachのreshape2ソリューション:

> system.time(replicate(1000, {
+ dx <- melt(dx,id=c('ID','FEM'))
+ dcast(dx,ID+FEM~variable,fun.aggregate=mean)
+ }))
   user  system elapsed 
 12.804   0.032  13.019

3)Steveのdata.tableソリューション:

> system.time(replicate(1000, {
+ dxt <- data.table(dx, key='ID')
+ dxt[, .SD[1,], by=ID]
+ }))
   user  system elapsed 
  5.484   0.020   5.608 
> dxt <- data.table(dx, key='ID') ## one time step
> system.time(replicate(1000, {
+ dxt[, .SD[1,], by=ID] ## try this one line on own
+ }))
   user  system elapsed 
  3.743   0.006   3.784

4)ファクターではなく数値を使用したチェイスの高速ソリューションID

> dx2 <- within(dx, ID <- as.numeric(ID))
> system.time(replicate(1000, {
+ dy <- dx[order(dx$ID),]
+ dy[ diff(c(0,dy$ID)) != 0, ]
+ }))
   user  system elapsed 
  0.663   0.000   0.663

および5)キャラクターまたはファクターについてのチェイスのソリューションに対するマットパーカーの代替手段ID。これはチェイスの数値よりもわずかに高速ですID

> system.time(replicate(1000, {
+ dx[c(TRUE, dx$ID[-1] != dx$ID[-length(dx$ID)]), ]
+ }))
   user  system elapsed 
  0.513   0.000   0.516

ああ、そう、ありがとう!集約のためのその構文を忘れました。
ロックオフ

チェイスのソリューションを追加したい場合、ここに私が得たものがあります:dx$ID <- sample(as.numeric(dx$ID)) #assuming IDs arent presorted system.time(replicate(1000, { dy <- dx[order(dx$ID),] dy[ diff(c(0,dy$ID)) != 0, ] })) user system elapsed 0.58 0.00 0.58
ロックオフ

@lockedoff-完了、ありがとう、しかし、私はランダムにIDsをサンプリングしなかったので、結果は他のソリューションに匹敵しました。
モニカの復活-G.シンプソン

そしてチェースの答え@コメントの時間@Mattパーカーのバージョンに
復活モニカ- G.シンプソン

2
タイミングをとってくれてありがとう、ギャビン-それはこれらのような質問に本当に役立ちます。
マットパーカー

9

data.tableパッケージを使用してみてください。

あなたの特定の場合の利点は、(非常に)高速であることです。初めて紹介されたとき、数十万行のdata.frameオブジェクトを扱っていました。「通常」aggregateまたはddplyメソッドは、完了するまでに1〜2分かかりました(これは、Hadleyがにidata.framemojoを導入する前でしたddply)。を使用してdata.table、操作は文字通り数秒で完了しました。

欠点は、「キー列」によってdata.table(data.frameのようなもの)を再利用し、スマート検索戦略を使用してデータのサブセットを見つけるため、非常に高速です。これにより、統計を収集する前にデータの順序が変更されます。

各グループの最初の行だけが必要な場合、並べ替えが最初の行を混乱させる可能性があるため、状況によっては適切でない場合があります。

とにかく、data.tableここで適切かどうかを判断する必要がありますが、提示したデータでこれを使用する方法は次のとおりです。

install.packages('data.table') ## if yo udon't have it already
library(data.table)
dxt <- data.table(dx, key='ID')
dxt[, .SD[1,], by=ID]
     ID AGE FEM
[1,]  1  30   1
[2,]  2  40   0
[3,]  3  35   1

更新: Matthew Dowle(data.tableパッケージのメイン開発者)は、ここでの回答の1つとしてこの問題を解決するためにdata.tableを使用するより良い/スマート/(非常に)より効率的な方法を提供しています... 。


4

reshape2をお試しください

library(reshape2)
dx <- melt(dx,id=c('ID','FEM'))
dcast(dx,ID+FEM~variable,fun.aggregate=mean)

3

試すことができます

agg <- by(dx, dx$ID, FUN = function(x) x[1, ])
# Which returns a list that you can then convert into a data.frame thusly:
do.call(rbind, agg)

plyrただし、これよりも高速であるかどうかはわかりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.