समूह द्वारा पहली पंक्ति का चयन करें


85

इस तरह एक डेटाफ्रेम से

test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10

> test
    id string
 1   1      A
 2   1      F
 3   2      B
 4   2      G
 5   3      C
 6   3      H
 7   4      D
 8   4      I
 9   5      E
 10  5      J

मैं प्रत्येक आईडी / स्ट्रिंग जोड़ी की पहली पंक्ति के साथ एक नया बनाना चाहता हूं। यदि sqldf ने R कोड को इसके भीतर स्वीकार किया, तो क्वेरी इस तरह दिख सकती है:

res <- sqldf("select id, min(rownames(test)), string 
              from test 
              group by id, string")

> res
    id string
 1   1      A
 3   2      B
 5   3      C
 7   4      D
 9   5      E

क्या एक नया स्तंभ बनाने की तरह एक समाधान कम है

test$row <- rownames(test)

और मिनट (पंक्ति) के साथ एक ही sqldf क्वेरी चल रही है?



1
@ मैथ्यू, मेरा सवाल पुराना है।
dmvianna

2
आपका प्रश्न 1 वर्ष पुराना है, और अन्य प्रश्न 4 वर्ष पुराना है, नहीं? इस सवाल के बहुत सारे डुप्लिकेट हैं
मैथ्यू

@ मैथ्यू सॉरी, मुझे तारीखों को गलत समझना चाहिए था।
dmvianna

जवाबों:


119

आप duplicatedइसे बहुत जल्दी करने के लिए उपयोग कर सकते हैं ।

test[!duplicated(test$id),]

बेंचमार्क, गति शैतान के लिए:

ju <- function() test[!duplicated(test$id),]
gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1))
gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
jply <- function() ddply(test,.(id),function(x) head(x,1))
jdt <- function() {
  testd <- as.data.table(test)
  setkey(testd,id)
  # Initial solution (slow)
  # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
  # Faster options :
  testd[!duplicated(id)]               # (1)
  # testd[, .SD[1L], by=key(testd)]    # (2)
  # testd[J(unique(id)),mult="first"]  # (3)
  # testd[ testd[,.I[1L],by=id] ]      # (4) needs v1.8.3. Allows 2nd, 3rd etc
}

library(plyr)
library(data.table)
library(rbenchmark)

# sample data
set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]

benchmark(ju(), gs1(), gs2(), jply(), jdt(),
    replications=5, order="relative")[,1:6]
#     test replications elapsed relative user.self sys.self
# 1   ju()            5    0.03    1.000      0.03     0.00
# 5  jdt()            5    0.03    1.000      0.03     0.00
# 3  gs2()            5    3.49  116.333      2.87     0.58
# 2  gs1()            5    3.58  119.333      3.00     0.58
# 4 jply()            5    3.69  123.000      3.11     0.51

आइए फिर से कोशिश करें, लेकिन पहली गर्मी से सिर्फ दावेदार और अधिक डेटा और अधिक प्रतिकृति के साथ।

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
benchmark(ju(), jdt(), order="relative")[,1:6]
#    test replications elapsed relative user.self sys.self
# 1  ju()          100    5.48    1.000      4.44     1.00
# 2 jdt()          100    6.92    1.263      5.70     1.15

विजेता: system.time (dat3 [डुप्लिकेटेड (dat3 $ id),]) उपयोगकर्ता प्रणाली 0.07 0.00 0.07 बीत गई
dmvianna

2
@ मदरइना: मैंने इसे स्थापित नहीं किया है और इसके साथ परेशान होने का मन नहीं है। :)
जोशुआ उलरिच

क्या हमें यकीन है कि मेरा डेटा.टेबल कोड यथासंभव कुशल है? मुझे उस उपकरण से सर्वश्रेष्ठ प्रदर्शन निकालने की अपनी क्षमता पर भरोसा नहीं है।
जोरन j

2
इसके अलावा, मुझे लगता है, यदि आप data.table को बेंचमार्क करने जा रहे हैं, तो कुंजीयन में आपको आधार कॉल के भीतर आईडी द्वारा ऑर्डरिंग को शामिल करना चाहिए।
एम.एन.

1
@JoshuaUlrich एक और सवाल: पहले वाक्य की आवश्यकता क्यों है, यह मानकर कि डेटा पहले से ही सॉर्ट किया गया है। !duplicated(x)भले ही यह सॉर्ट न किया गया हो, प्रत्येक समूह के पहले समूह को ढूंढता है।
मैट डोले

36

मैं सपने देखने के पक्ष में हूं।

group_by(id) या तो इसके बाद

  • filter(row_number()==1) या
  • slice(1) या
  • slice_head(1) # (dplyr => 1.0)
  • top_n(n = -1)
    • top_n()आंतरिक रूप से रैंक फ़ंक्शन का उपयोग करता है। रैंक के नीचे से नकारात्मक चयन करता है।

कुछ उदाहरणों में समूह की व्यवस्था के बाद आईडी की व्यवस्था करना आवश्यक हो सकता है।

library(dplyr)

# using filter(), top_n() or slice()

m1 <-
test %>% 
  group_by(id) %>% 
  filter(row_number()==1)

m2 <-
test %>% 
  group_by(id) %>% 
  slice(1)

m3 <-
test %>% 
  group_by(id) %>% 
  top_n(n = -1)

सभी तीन विधियाँ समान परिणाम देती हैं

# A tibble: 5 x 2
# Groups:   id [5]
     id string
  <int> <fct> 
1     1 A     
2     2 B     
3     3 C     
4     4 D     
5     5 E

2
के sliceरूप में अच्छी तरह से एक चिल्ला आउट देने के लायक । slice(x)के लिए एक शॉर्टकट है filter(row_number() %in% x)
ग्रेगर थॉमस

बहुत खूबसूरत। क्या आप जानते हैं कि मुझे काम करने के लिए अपने data.tableको क्यों बदलना होगा data.frame?
जेम्स हिर्सचोर्न

@JamesHirschorn मैं सभी मतभेदों का विशेषज्ञ नहीं हूं। लेकिन कई मामलों में आपको data.tableविरासत में मिली data.framedplyr कमांड का उपयोग कर सकते हैं data.table। उदाहरण के ऊपर उदाहरण भी काम करता है अगर testdata.table। उदाहरण के लिए देखें stackoverflow.com/questions/13618488/… एक गहरी व्याख्या के लिए
Kresten

यह ऐसा करने का एक सुव्यवस्थित तरीका है और जैसा कि आप डेटा को देखते हैं। वास्तव में यहाँ एक खिंचाव है। मैं व्यक्तिगत रूप से आपको हमेशा टिब्बल्स के साथ काम करने की सलाह देता हूं क्योंकि ggplot2 एक समान तरीके से बनाया गया है।
गरिनी

17

व्हाट अबाउट

DT <- data.table(test)
setkey(DT, id)

DT[J(unique(id)), mult = "first"]

संपादित करें

एक अनूठी विधि भी है data.tablesजिसके लिए कुंजी द्वारा पहली पंक्ति वापस आ जाएगी

jdtu <- function() unique(DT)

मुझे लगता है, अगर आप testबेंचमार्क के बाहर ऑर्डर कर रहे हैं , तो आप बेंचमार्क से रूपांतरण setkeyऔर data.tableरूपांतरण को हटा सकते हैं (जैसा कि मूल रूप से सेट आईडी के अनुसार होता है, उतना ही order)।

set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]
DT <- data.table(DT, key = 'id')
ju <- function() test[!duplicated(test$id),]

jdt <- function() DT[J(unique(id)),mult = 'first']


 library(rbenchmark)
benchmark(ju(), jdt(), replications = 5)
##    test replications elapsed relative user.self sys.self 
## 2 jdt()            5    0.01        1      0.02        0        
## 1  ju()            5    0.05        5      0.05        0         

और अधिक डेटा के साथ

** अनूठी विधि के साथ संपादित करें **

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
DT <- data.table(test, key = 'id')
       test replications elapsed relative user.self sys.self 
2  jdt()            5    0.09     2.25      0.09     0.00    
3 jdtu()            5    0.04     1.00      0.05     0.00      
1   ju()            5    0.22     5.50      0.19     0.03        

अनोखी विधि यहां सबसे तेज है।


4
आपको कुंजी सेट करने की भी आवश्यकता नहीं है। unique(DT,by="id")सीधे काम करता है
मैथ्यू

data.tableसंस्करण के रूप में FYI करें = = 1.9.8, पिछले डिफ़ॉल्ट के बजाय (सभी कॉलम) के byलिए डिफ़ॉल्ट तर्क uniqueहैby = seq_along(x)by = key(x)
IceCreamToucan

12

एक सरल ddplyविकल्प:

ddply(test,.(id),function(x) head(x,1))

यदि गति एक समस्या है, तो एक समान दृष्टिकोण लिया जा सकता है data.table:

testd <- data.table(test)
setkey(testd,id)
testd[,.SD[1],by = key(testd)]

या यह काफी तेज हो सकता है:

testd[testd[, .I[1], by = key(testd]$V1]

हैरानी की बात है, sqldf इसे तेजी से करता है: 1.77 0.13 1.92 बनाम 10.53 0.00 10.79 data.table के साथ
dmvianna

3
@dmvianna मैं आवश्यक रूप से data.table की गणना नहीं करूंगा। मैं उस टूल का विशेषज्ञ नहीं हूं, इसलिए मेरा डेटाटेबल कोड उस बारे में जाने का सबसे कारगर तरीका नहीं हो सकता है।
Joran

मैंने इसे समय से पहले उखाड़ा। जब मैंने इसे एक बड़े data.table पर चलाया, तो यह हास्यास्पद रूप से धीमा था और यह काम नहीं किया: पंक्तियों की संख्या एक ही बाद थी।
जेम्स हिर्सचोर्न 3

@JamesHirachorn मैंने इसे बहुत पहले लिखा था, पैकेज बहुत बदल गया है, और मैं शायद ही डेटा का उपयोग करता हूं। यदि आपको उस पैकेज के साथ ऐसा करने का सही तरीका लगता है, तो इसे बेहतर बनाने के लिए एक संपादन का सुझाव देने के लिए स्वतंत्र महसूस करें।
जोरन

8

अब, dplyrएक अलग काउंटर को जोड़ने के लिए ।

df %>%
    group_by(aa, bb) %>%
    summarise(first=head(value,1), count=n_distinct(value))

आप समूह बनाते हैं, उन्हें समूहों में संक्षिप्त करते हैं।

यदि डेटा संख्यात्मक है, तो आप इसका उपयोग कर सकते हैं:
first(value)[के last(value)स्थान पर] भी हैhead(value, 1)

देखें: http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html

पूर्ण:

> df
Source: local data frame [16 x 3]

   aa bb value
1   1  1   GUT
2   1  1   PER
3   1  2   SUT
4   1  2   GUT
5   1  3   SUT
6   1  3   GUT
7   1  3   PER
8   2  1   221
9   2  1   224
10  2  1   239
11  2  2   217
12  2  2   221
13  2  2   224
14  3  1   GUT
15  3  1   HUL
16  3  1   GUT

> library(dplyr)
> df %>%
>   group_by(aa, bb) %>%
>   summarise(first=head(value,1), count=n_distinct(value))

Source: local data frame [6 x 4]
Groups: aa

  aa bb first count
1  1  1   GUT     2
2  1  2   SUT     2
3  1  3   SUT     3
4  2  1   221     3
5  2  2   217     3
6  3  1   GUT     2

यह उत्तर काफी दिनांकित है - इसके साथ बेहतर तरीके हैं dplyrकि इसमें शामिल होने के लिए हर एक कॉलम के लिए एक बयान लिखने की आवश्यकता नहीं है (नीचे परमाणु का उत्तर देखें, उदाहरण के लिए) . Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would use पहले (मूल्य) `बनाम head(value)(या सिर्फ value[1])
ग्रेगर थॉमस

7

(1) SQLite में एक rowidछद्म स्तंभ है, इसलिए यह काम करता है:

sqldf("select min(rowid) rowid, id, string 
               from test 
               group by id")

दे रही है:

  rowid id string
1     1  1      A
2     3  2      B
3     5  3      C
4     7  4      D
5     9  5      E

(२) sqldfस्वयं भी एक row.names=तर्क है:

sqldf("select min(cast(row_names as real)) row_names, id, string 
              from test 
              group by id", row.names = TRUE)

दे रही है:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

(3) एक तीसरा विकल्प जो उपरोक्त दोनों के तत्वों को मिलाता है, और भी बेहतर हो सकता है:

sqldf("select min(rowid) row_names, id, string 
               from test 
               group by id", row.names = TRUE)

दे रही है:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

ध्यान दें कि ये तीनों SQL के लिए SQLite एक्सटेंशन पर निर्भर करते हैं, जहां minया जिसके उपयोग की maxगारंटी दी जाती है, उसी पंक्ति से चुने गए अन्य कॉलम में परिणाम होता है। (अन्य SQL- आधारित डेटाबेस में जो गारंटी नहीं हो सकता है।)


धन्यवाद! यह स्वीकार किए गए उत्तर IMO से बहुत बेहतर है क्योंकि यह कई समुच्चय कार्यों का उपयोग करते हुए एक कुल कदम में पहला / अंतिम तत्व लेने के लिए सामान्य है (जैसे कि इस चर का पहला ले, उस चर, आदि)।
पुलबर्नर्स

4

आधार R विकल्प है split()- lapply()- do.call()मुहावरा:

> do.call(rbind, lapply(split(test, test$id), head, 1))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

एक और अधिक प्रत्यक्ष विकल्प है समारोह:lapply()[

> do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

कॉल 1, )के अंत में कॉमा-स्पेस आवश्यक है क्योंकि यह पहली पंक्ति और सभी कॉलमों को चुनने के लिए कॉल करने के बराबर है ।lapply()[1, ]


यह बहुत धीमा था, गेविन: उपयोगकर्ता प्रणाली 91.84 6.02 101.10
dmvianna

डेटा फ़्रेम में कुछ भी शामिल होगा। उनकी उपयोगिता एक मूल्य पर आती है। उदाहरण के लिए, डेटाटेबल।
गैविन सिम्पसन

मेरे बचाव में, और आर के, आपने प्रश्न में दक्षता के बारे में कुछ भी उल्लेख नहीं किया है। अक्सर उपयोग में आसानी एक सुविधा है। प्लाई की लोकप्रियता का गवाह है, जो "धीमा" भी है, कम से कम अगले संस्करण तक जिसमें डेटाटेबल समर्थन है।
गैविन सिम्पसन

1
मैं सहमत हूँ। मेरा मतलब आपका अपमान करना नहीं था। मैंने पाया, हालांकि, कि @ जोशुआ-उलरिच का तरीका तेज और आसान दोनों था । : 7)
dmvianna

माफी मांगने की जरूरत नहीं है और मैंने इसे अपमान के रूप में नहीं लिया। केवल इंगित कर रहा था कि यह दक्षता के किसी भी दावे के बिना पेश किया गया था। याद रखें यह स्टैक ओवरफ्लो Q & A केवल आपके लाभ के लिए नहीं है, बल्कि अन्य उपयोगकर्ताओं के लिए भी है जो आपके प्रश्न के समान आते हैं, क्योंकि उनके पास एक समान समस्या है।
गैविन सिम्पसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.