पहचानकर्ता द्वारा समूहीकृत डेटा फ़्रेम की पहली पंक्ति प्राप्त करने के लिए R में तेज़ तरीके [बंद]


14

कभी-कभी मुझे पहचानकर्ता द्वारा समूहीकृत डेटा सेट की केवल पहली पंक्ति प्राप्त करने की आवश्यकता होती है, जैसे कि जब प्रति व्यक्ति कई अवलोकन होते हैं तो उम्र और लिंग प्राप्त करते हैं। R में ऐसा करने का एक तेज़ (या सबसे तेज़) तरीका क्या है? मैंने नीचे समुच्चय () का उपयोग किया और संदेह है कि बेहतर तरीके हैं। इस प्रश्न को पोस्ट करने से पहले मैंने Google पर थोड़ी खोज की, पाया और ddply की कोशिश की, और आश्चर्यचकित था कि यह बहुत धीमा था और मुझे अपने डेटासेट (400,000 पंक्तियों x 16 कॉल, 7,000 अद्वितीय आईडी) पर मेमोरी त्रुटियां दीं, जबकि समग्र () संस्करण यथोचित उपवास था।

(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पैकेज का उपयोग करने वाले सबसे तेज़ समाधान के लिए @ मट्टू डोवाले का उत्तर देखें ।


आपके सभी उत्तरों के लिए धन्यवाद। @Gavin (जो बदले में मेरे एग्रीगेट () कोड से अधिक तेज था), और ~ 7.5 के कारक पर @ डेटा का स्थिर समाधान, ~ 5 के कारक द्वारा सबसे तेज था। @Matt के द्वारा () समाधान के ऊपर। मेरे विचार में फेरबदल का समय नहीं था क्योंकि मैं इसे जल्दी से काम नहीं कर सकता था। मैं उस समाधान का अनुमान लगा रहा हूं जो @Chase ने दिया था वह सबसे तेज़ होगा और वास्तव में वह था जो मैं ढूंढ रहा था, लेकिन जब मैंने यह टिप्पणी लिखना शुरू किया, तो कोड काम नहीं कर रहा था (मुझे लगता है कि यह अब तय हो गया है!)।
लॉक

वास्तव में @C डेटा ~ 9 ओवर के कारक से तेज था। इसलिए मैंने अपना स्वीकृत उत्तर बदल दिया। सभी को धन्यवाद फिर से - नए उपकरणों का एक गुच्छा सीखा।
लॉकऑफ

क्षमा करें, मैंने अपना कोड ठीक कर लिया है। यहां एक कैविएट या ट्रिक एक मान को सम्‍मिलित करने के लिए है जो आपकी आईडी में से एक नहीं है diff()ताकि आप पहली आईडी को उठा सकें dx
चेस

जवाबों:


11

क्या आपका आईडी कॉलम वास्तव में एक कारक है? यदि यह वास्तव में संख्यात्मक है, तो मुझे लगता है कि आप 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()फ़ंक्शन में एक अतिरिक्त है।
मैट पार्कर

@Matt - अच्छी कॉल और अच्छी पकड़। मैं आज एक फ्लिप के लायक कोड को कॉपी / पेस्ट करने में सक्षम नहीं दिखाई देता।
पीछा

मैं लंदन साइकिल किराया योजना पर काम कर रहा हूं, और उपयोगकर्ताओं के बाइक किराए के पहले और आखिरी उदाहरणों को खोजने के लिए एक रास्ता खोजने की जरूरत है। 1 मिलियन उपयोगकर्ताओं के साथ, प्रति वर्ष 10 मिलियन ट्रिप और कई वर्षों का डेटा, मेरा "for" लूप 1 उपयोगकर्ता प्रति सेकंड कर रहा था। मैंने "बाय" समाधान की कोशिश की, और यह एक घंटे के बाद पूरा करने में विफल रहा। पहले तो मैं यह नहीं सोच पाया कि "चेस के समाधान के लिए मैट पार्कर का विकल्प" क्या कर रहा था, लेकिन अंत में पैसा गिरा, और यह सेकंडों में निष्पादित हो गया। इसलिए बड़े डेटासेट के साथ सुधार के बारे में बात मेरे अनुभव से साबित होती है।
जॉर्ज सिम्पसन

@GeorgeSimpson - यह देखकर खुशी हुई कि यह अभी भी संदर्भित है! data.tableसमाधान नीचे नीचे तो मैं (यह शायद स्वीकार किए जाते हैं जवाब यहाँ होना चाहिए) कि बाहर की जाँच चाहते हैं, तो मैं तुम्हें थे, सबसे तेजी से साबित करना चाहिए।
चेस

17

स्टीव के जवाब के बाद, 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 से तुलना करें। "मैट पार्कर के लिए चेस के समाधान का विकल्प" (जो अब तक का सबसे तेज़ लग रहा था):

> 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 गुना तेजी से, लेकिन यह 1 मिलियन पंक्तियों के नीचे एक छोटी सी मेज है। जैसे-जैसे आकार बढ़ता है, वैसे-वैसे फर्क पड़ता है।


वाह, मैंने वास्तव में कभी भी सराहना नहीं की कि "स्मार्ट" [.data.tableफ़ंक्शन कैसे प्राप्त कर सकता है ... मुझे लगता है कि मुझे एहसास नहीं हुआ कि आपने एक .SDऑब्जेक्ट नहीं बनाया है यदि आपको वास्तव में इसकी आवश्यकता नहीं है। अच्छा है!
बजे स्टीव लियानोग्लू

हाँ, यह वास्तव में तेज़ है! यहां तक ​​कि अगर आप dxt <- data.table(dx, key='ID')कॉल को system.time () में शामिल करते हैं, तो यह @ मैट समाधान से तेज है।
लॉकऑफ

मुझे लगता है कि अब यह पुराना हो गया है क्योंकि नए डेटा के साथ। डेटा संस्करणों SD[1L]को पूरी तरह से अनुकूलित किया गया था और वास्तव में @SteveLianoglou का उत्तर 5e7 पंक्तियों के लिए दोगुना होगा।
डेविड अरेनबर्ग

@DavidArenburg v1.9.8 नवंबर 2016 से, हाँ। इस उत्तर को सीधे संपादित करने के लिए स्वतंत्र महसूस करें, या शायद इस क्यू को सामुदायिक विकि या कुछ और होने की आवश्यकता है।
मैट डोले

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) स्टीव डेटाटेबल समाधान:

> 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 - किया गया, धन्यवाद, लेकिन मैंने यादृच्छिक रूप से नमूना नहीं लिया IDइसलिए परिणाम अन्य समाधानों के साथ तुलनात्मक थे।
मोनिका - जी। सिम्पसन

और समय @Matt पार्कर की टिप्पणी @ चेस के जवाब में संस्करण
मोनिका को पुनः स्थापित करें - जी। सिम्पसन

2
टाइमिंग करने के लिए धन्यवाद, गेविन - यह वास्तव में इन जैसे सवालों के लिए मददगार है।
मैट पार्कर

10

आप data.table पैकेज का उपयोग करने का प्रयास कर सकते हैं ।

आपके विशेष मामले के लिए, उल्टा यह है कि यह (पागलपन से) तेज है। पहली बार जब मुझे इसे पेश किया गया था, तो मैं सैकड़ों पंक्तियों के साथ डेटा.फ्रेम वस्तुओं पर काम कर रहा था। "सामान्य" aggregateया ddplyविधियों को पूरा करने के लिए ~ 1-2 मिनट लिया गया (यह हैडली द्वारा idata.frameमोजो को पेश करने से पहले ddply) था। का उपयोग करते हुए data.table, ऑपरेशन सचमुच सेकंड के एक मामले में किया गया था।

नकारात्मक पक्ष यह है कि यह इतनी तेजी से है क्योंकि यह "कुंजी कॉलम" द्वारा आपके डेटा का उपयोग करेगा। यह (यह सिर्फ एक डेटा.फ्रेम की तरह है) और अपने डेटा के सबसेट खोजने के लिए एक स्मार्ट खोज रणनीति का उपयोग करें। इससे पहले कि आप उस पर आँकड़े एकत्र करें, इससे पहले कि आप अपने डेटा को फिर से व्यवस्थित करें।

यह देखते हुए कि आप बस प्रत्येक समूह की पहली पंक्ति चाहते हैं - हो सकता है कि रीऑर्डरिंग गड़बड़ कर देगा कि कौन सी पंक्ति पहले है, यही कारण है कि यह आपकी स्थिति में उपयुक्त नहीं हो सकता है।

वैसे भी, आपको यह निर्धारित करना होगा कि 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

अपडेट करें: मैथ्यू डॉवेल (डेटाटेबल पैकेज के मुख्य डेवलपर) ने इस समस्या को हल करने के लिए डेटाटेबल का उपयोग करने के लिए एक बेहतर / स्मार्ट / / (बेहद) अधिक कुशल तरीका प्रदान किया है, क्योंकि यहां से एक उत्तर के रूप में ... निश्चित रूप से देखें ।


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, हालांकि।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.