Bir tanımlayıcıya göre gruplandırılmış bir veri çerçevesinin ilk satırını almanın hızlı yolları [kapalı]


14

Bazen, kişi başına birden fazla gözlem olduğunda yaş ve cinsiyet alırken, bir tanımlayıcı tarafından gruplandırılmış bir veri kümesinin yalnızca ilk satırını almam gerekir. Bunu R'de yapmanın hızlı (veya en hızlı) yolu nedir? Aşağıdaki aggregate () yöntemini kullandım ve daha iyi yollar olduğundan şüpheleniyorum. Bu soruyu göndermeden önce, Google'da biraz arama yaptım, ddply'ı buldum ve denedim ve veri kümemde (400.000 satır x 16 sütun, 7.000 benzersiz kimlik) bellek hataları verdiğime şaşırdım, toplam () sürümü oldukça hızlıydı.

(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,])

GÜNCELLEME: En zarif yaklaşım olarak gördüğüm şeyler için Chase'in cevabına ve Matt Parker'ın yorumuna bakın. Paketi kullanan en hızlı çözüm için @Matthew Dowle'ın cevabına bakınız data.table.


Tüm cevaplarınız için teşekkürler. @Steve'in data.table çözümü, @Gavin'in toplam () çözümü (sırasıyla toplam () kodumdan daha hızlıydı) üzerinden veri setimdeki ~ 5 faktörü ve ~ 7.5 faktörü ile en hızlıydı @Matt'ın by () çözümü üzerinden. Yeniden şekillendirme fikrine zaman ayırmadım çünkü hızlı bir şekilde çalıştıramadım. @Chase verdi çözüm en hızlı olacağını tahmin ediyorum ve aslında ne aradığını oldu, ama bu yorumu yazmaya başladığımda, kod çalışma değildi (şimdi düzeltildiğini görüyorum!).
lockedoff

Aslında @Chase, data.table üzerinden ~ 9 faktörü ile daha hızlıydı, bu yüzden kabul edilen cevabımı değiştirdim. Herkese tekrar teşekkürler - bir sürü yeni araç öğrendim.
lockedoff

üzgünüm, kodumu düzelttim. Buradaki bir uyarı veya hile diff(), ilk kimliğinizi alabilmeniz için kimliklerinizden birinde olmayan bir değeri birleştirmektir dx.
Chase

Yanıtlar:


11

Kimlik sütununuz gerçekten bir faktör mü? Aslında sayısalsa, diffişlevi kendi yararınıza kullanabileceğinizi düşünüyorum . Sayısal olarak da zorlayabilirsiniz 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
Zeki! dx[c(TRUE, dx$ID[-1] != dx$ID[-length(dx$ID)], ]Sayısal olmayan veriler için de yapabilirsiniz - Karakter için 0.03, faktörler için 0.05 alırım. PS: İkinci sıfırdan sonra )ilk system.time()işlevinizde fazladan bir şey var .
Matt Parker

@Matt - iyi çağrı ve güzel yakalamak. Bugün bir flip değer kod kopyalamak / yapıştırmak mümkün görünmüyor.
Chase

Ben Londra Bisiklet Kiralama programı üzerinde çalışıyorum ve kullanıcıların bisiklet kiralama ilk ve son örnekleri bulmak için bir yol bulmak gerekiyordu. 1 milyon kullanıcı, yılda 10 milyon seyahat ve birkaç yıllık verilerle, "for" döngüm saniyede 1 kullanıcı yapıyor. "By" çözümünü denedim ve bir saat sonra tamamlanamadı. İlk başta "Matt Parker'ın Chase'in çözümüne alternatifi" nin ne yaptığını anlayamadım, ama sonunda kuruş düştü ve saniyeler içinde yürütüldü. Bu nedenle, daha büyük veri kümeleriyle iyileştirmenin daha da artmasıyla ilgili nokta, deneyimlerimle kanıtlandı.
George Simpson

@GeorgeSimpson - bunun hala referans olduğunu görmekten memnunum! Aşağıdaki data.tableçözüm en hızlısı olmalıdır, bu yüzden siz olsaydım bunu kontrol ederim (muhtemelen burada kabul edilen cevap olmalı).
Chase

17

Steve'in cevabını takiben, verilerde çok daha hızlı bir yol var. Tablo:

> # 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 

Yalnızca her grubun ilk satırına ihtiyacınız varsa, bu satıra doğrudan katılmak çok daha hızlıdır. .SD nesnesini neden yalnızca ilk satırını kullanmak için?

0.064 data.table ile karşılaştırın (Matt Parker'ın Chase'in çözümüne alternatifi) (şimdiye kadarki en hızlı gibi görünüyor):

> 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 

Yani ~ 5 kat daha hızlı, ama 1 milyondan az satırda küçük bir tablo. Boyut arttıkça, fark da artar.


Vay canına, [.data.tableişlevin nasıl "akıllı" olabileceğini hiç takdir etmedim ... Sanırım, .SDgerçekten ihtiyacınız yoksa bir nesne yaratmadığınızı fark etmedim . Güzel!
Steve Lianoglou

Evet, bu gerçekten hızlı! dxt <- data.table(dx, key='ID')System.time () çağrısına dahil olsanız bile , @ Matt'in çözümünden daha hızlıdır.
lockedoff

Sanırım bu daha yeni data.table sürümleri gibi şimdi modası geçmiş sanırım SD[1L]tamamen optimize edildi ve aslında @SteveLianoglou cevap 5e7 satırlar için iki kat daha hızlı olurdu.
David Arenburg

@DavidArenburg v1.9.8 Kasım 2016 itibarıyla, evet. Bu yanıtı doğrudan düzenlemekten çekinmeyin veya belki bu Q'nun topluluk wiki'si veya başka bir şey olması gerekir.
Matt Dowle

10

Birden fazla merge()adıma ihtiyacınız yoktur , yalnızca aggregate()her iki ilgi alanı değişkeni:

> 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

Karşılaştırma zamanlamaları:

1) Matt'in çözümü:

> 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'ın reshape2 çözümü:

> 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'in data.table çözümü:

> 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) Chase'in faktör değil sayısal kullanarak hızlı çözümü 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

ve 5) Matt Parker'ın IDChase'in sayısal çözümünden biraz daha hızlı olan karakter veya faktör için Chase'in çözümüne alternatifi 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

Oh, doğru, teşekkürler! Toplama için sözdizimini unuttunuz.
lockedoff

Chase'in çözümünü eklemek isterseniz, elimde şu var: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

@lockedoff - bitti, teşekkürler, ancak rastgele IDdiğer örneklerle karşılaştırılabilir sonuç s örnek vermedi .
Monica'yı eski durumuna getirin - G. Simpson

Ve @ Chase'in cevabındaki yorumlarda @Matt Parker'ın versiyonu
Monica'yı yeniden eski haline getirin - G. Simpson

2
Zamanlamaları yaptığınız için teşekkürler, Gavin - bu tür sorular için gerçekten yararlı.
Matt Parker

10

Data.table paketini kullanmayı deneyebilirsiniz .

Sizin özel durumunuz için, yukarı doğru (delicesine) hızlı olmasıdır. İlk tanıştığımda yüz binlerce satır içeren data.frame nesneleri üzerinde çalışıyordum. "Normal" aggregateya da ddplyyöntemler tamamlanmasını ~ 1-2 dakika alındı (Hadley tanıtılan önce bu idi idata.frameiçine mojosunu ddply). Kullanılması data.table, operasyon anlamıyla birkaç saniye içinde yapıldı.

Dezavantajı, onun çok hızlı olmasıdır çünkü "anahtar sütunlar" ile data.table (tıpkı bir data.frame gibi) başvurur ve verilerinizin alt kümelerini bulmak için akıllı bir arama stratejisi kullanır. Bu, üzerinde istatistik toplamadan önce verilerinizin yeniden sıralanmasına neden olur.

Her grubun ilk satırını isteyeceğiniz göz önüne alındığında - yeniden sıralama hangi satırın ilk sırada olduğunu bozacak, bu yüzden durumunuza uygun olmayabilir.

Her neyse, data.tableburada uygun olup olmadığına karar vermelisiniz , ancak sunduğunuz verilerle bu şekilde kullanacaksınız:

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

Güncelleme: Matthew Dowle (data.table paketinin ana geliştiricisi) verileri kullanmanın daha iyi / daha akıllı / (son derece) daha verimli bir yolunu sağladı. .


4

Yeniden şekillendirmeyi dene2

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

3

Deneyebilirsin

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)

plyrYine de bunun daha hızlı olacağı hakkında hiçbir fikrim yok .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.