식별자로 그룹화 된 데이터 프레임의 첫 번째 행을 얻는 빠른 방법 R


14

때때로 개인별로 여러 개의 관측치가있을 때 연령과 성별을 검색 할 때와 같이 식별자로 그룹화 된 데이터 세트의 첫 번째 행만 가져와야합니다. R 에서이 작업을 수행하는 가장 빠른 방법은 무엇입니까? 아래에서 collect ()를 사용했으며 더 좋은 방법이 있다고 생각합니다. 이 질문을 게시하기 전에 Google에서 조금 검색하고 ddply를 찾아서 시도했으며 매우 느리고 내 데이터 세트 (400,000 행 x 16 열, 7,000 개의 고유 ID)에서 메모리 오류가 발생했다는 것에 놀랐습니다. 상당히 빠르다.

(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의 집계 () 솔루션 (결과적으로 내 집계 () 코드보다 빠름)에 대해 내 데이터 세트에서 ~ 5의 계수와 ~ 7.5의 계수로 가장 빠릅니다. @Matt의 by () 솔루션을 통해. 나는 빨리 아이디어를 얻을 수 없었기 때문에 재구성 아이디어에 시간을 투자하지 않았다. @Chase가 제공 한 솔루션이 가장 빠를 것이라고 생각하고 실제로 실제로 찾고 있었지만이 의견을 작성하기 시작했을 때 코드가 작동하지 않았습니다 (지금 수정되었습니다!).
잠금 해제

실제로 @Chase는 data.table보다 ~ 9 배 빨라 졌으므로 허용 된 답변을 변경했습니다. 다시 한 번 감사드립니다. 여러 가지 새로운 도구를 배웠습니다.
잠김

죄송합니다. 코드를 수정했습니다. 여기서주의해야 할 한 가지 요령은에서 ID 중 하나가 아닌 값을 연결하여 diff()에서 첫 번째 ID를 선택할 수 있도록하는 것입니다 dx.
Chase

답변:


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를 얻습니다. 추신 : )첫 번째 system.time()기능에는 두 번째 0 이후에 여분 의 것이 있습니다 .
매트 파커

@ 매트-좋은 전화와 좋은 캐치. 오늘 플립 가치가있는 코드를 복사 / 붙여 넣을 수없는 것 같습니다.
Chase

London Cycle Hire 제도를 연구 중이며 사용자 자전거 대여의 첫 번째 및 마지막 인스턴스를 찾는 방법을 찾아야했습니다. 백만 명의 사용자, 연간 천만 회의 및 수년간의 데이터를 가진 "for"루프는 초당 1 명의 사용자를 수행했습니다. "by"솔루션을 시도했지만 한 시간 후에 완료되지 않았습니다. 처음에는 "Chase의 솔루션에 대한 Matt Parker의 대안"이 무엇을하고 있었는지 알 수 없었지만 결국 페니가 떨어지고 몇 초 만에 실행됩니다. 따라서 더 큰 데이터 세트에서 개선이 커지는 것에 대한 요점은 내 경험에 의해 입증되었습니다.
George Simpson

@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 객체를 만들어 왜 첫 번째 행만 사용합니까?

data.table의 0.064를 "Chase 솔루션에 대한 Matt Parker의 대안"(지금까지 가장 빠른 것으로 보임)과 비교하십시오.

> 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 배 빠르지 만 백만 행 미만의 작은 테이블입니다. 크기가 커질수록 차이도 커집니다.


와우, 나는 [.data.table함수가 어떻게 "스마트" 할 수 있는지 정말 감사 하지 않았다 ... .SD당신이 정말로 그것을 필요로하지 않는다면 당신이 객체를 만들지 않았다는 것을 몰랐다 . 좋은 것!
Steve Lianoglou

예, 정말 빠릅니다! dxt <- data.table(dx, key='ID')system.time () 호출에 포함하더라도 @Matt의 솔루션보다 빠릅니다.
잠금 해제

나는 새로운 data.table 버전과 마찬가지로이 구식이라고 생각합니다. SD[1L]실제로 @SteveLianoglou 답변은 5e7 행에 비해 두 배 빠릅니다.
David Arenburg

@DavidArenburg 2016 년 11 월 v1.9.8부터. 예. 이 답변을 직접 수정 하거나이 Q가 커뮤니티 위키 또는 무언가 여야 할 수도 있습니다.
매트 Dowle

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 's 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) Chase의 솔루션에 대한 Matt Parker의 대안 ID은 Chase의 숫자보다 약간 빠릅니다 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

아, 고마워요! 집계 구문을 잊어 버렸습니다.
잠금 해제

Chase의 솔루션을 추가하고 싶다면 다음과 같이하십시오.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-감사합니다.하지만 무작위로 샘플을 샘플링하지 않았 ID으므로 결과가 다른 솔루션과 비교할 수있었습니다.
복원 모니카

그리고 @Chase의 답변에 대한 코멘트에서 @Matt Parker의 버전
Reinstate Monica-G. Simpson

2
타이밍을 해주셔서 감사합니다, Gavin-이런 질문에 정말 도움이됩니다.
매트 파커

9

data.table 패키지를 사용해보십시오 .

당신의 특별한 경우에, 단점은 그것이 (정말로) 빠르다는 것입니다. 처음 소개되었을 때 수십만 행의 data.frame 객체를 다루고있었습니다. "정상" aggregate또는 ddply방법을 완료하는 데 ~ 1-2 분이 소요되었습니다 (이것은 Hadley가 idata.frame모조를에 도입하기 전임 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 패키지의 주요 개발자)은 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.