समूहीकृत डेटा से पहली और अंतिम पंक्ति चुनें


137

सवाल

उपयोग करते हुए dplyr, मैं एक कथन में समूहीकृत डेटा की शीर्ष और नीचे की टिप्पणियों / पंक्तियों का चयन कैसे करूं?

डेटा और उदाहरण

एक डेटा फ्रेम दिया

df <- data.frame(id=c(1,1,1,2,2,2,3,3,3), 
                 stopId=c("a","b","c","a","b","c","a","b","c"), 
                 stopSequence=c(1,2,3,3,1,4,3,1,2))

उपयोग करने वाले प्रत्येक समूह से मैं शीर्ष और नीचे के अवलोकन प्राप्त कर सकता हूं slice, लेकिन दो अलग-अलग मूर्तियों का उपयोग करके:

firstStop <- df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  slice(1) %>%
  ungroup

lastStop <- df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  slice(n()) %>%
  ungroup

क्या मैं इन दो स्टेटमैनेट्स को एक में जोड़ सकता हूं जो शीर्ष और नीचे दोनों टिप्पणियों का चयन करता है ?


जवाबों:


232

शायद एक तेज़ तरीका है:

df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  filter(row_number()==1 | row_number()==n())

66
rownumber() %in% c(1, n())दो बार वेक्टर स्कैन चलाने की आवश्यकता को समाप्त करेगा
माइकलचिरिको

13
@MichaelChirico मुझे संदेह है कि आपने छोड़ा था _? यानीfilter(row_number() %in% c(1, n()))
एरिक फेल

107

पूर्णता के लिए: आप sliceसूचकांकों का एक सदिश पास कर सकते हैं :

df %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))

जो देता है

  id stopId stopSequence
1  1      a            1
2  1      c            3
3  2      b            1
4  2      c            4
5  3      b            1
6  3      a            3

इससे भी तेज हो सकता है filter- यह परीक्षण नहीं किया है, लेकिन यहां
Tjebo

1
@TJbo फ़िल्टर के विपरीत, स्लाइस एक ही पंक्ति को कई बार वापस कर सकता है, उदाहरण के लिए mtcars[1, ] %>% slice(c(1, n()))इस तरह से उनके बीच का चुनाव इस बात पर निर्भर करता है कि आप क्या चाहते हैं। मुझे उम्मीद है कि जब तक nबहुत बड़ा नहीं होगा (जहां स्लाइस का पक्ष लिया जा सकता है) करीब है, लेकिन या तो परीक्षण नहीं किया है।
फ्रैंक

15

नहीं dplyr, लेकिन इसका उपयोग करना अधिक प्रत्यक्ष है data.table:

library(data.table)
setDT(df)
df[ df[order(id, stopSequence), .I[c(1L,.N)], by=id]$V1 ]
#    id stopId stopSequence
# 1:  1      a            1
# 2:  1      c            3
# 3:  2      b            1
# 4:  2      c            4
# 5:  3      b            1
# 6:  3      a            3

अधिक विस्तृत विवरण:

# 1) get row numbers of first/last observations from each group
#    * basically, we sort the table by id/stopSequence, then,
#      grouping by id, name the row numbers of the first/last
#      observations for each id; since this operation produces
#      a data.table
#    * .I is data.table shorthand for the row number
#    * here, to be maximally explicit, I've named the variable V1
#      as row_num to give other readers of my code a clearer
#      understanding of what operation is producing what variable
first_last = df[order(id, stopSequence), .(row_num = .I[c(1L,.N)]), by=id]
idx = first_last$row_num

# 2) extract rows by number
df[idx]

मूल बातें कवर करने के लिए गेटिंग स्टार्ट विकी की जांच करना सुनिश्चित करेंdata.table


1
या df[ df[order(stopSequence), .I[c(1,.N)], keyby=id]$V1 ]। देखकर idदो बार दिखाई देते मुझे अजीब है।
फ्रैंक

आप setDTकॉल में चाबियाँ सेट कर सकते हैं। इसलिए orderयहां कॉल की जरूरत नहीं है।
आर्टेम कुलेव्सोव

1
@ArtemKlevtsov - आप हमेशा कुंजी सेट नहीं करना चाह सकते हैं, हालांकि।
सिंबलिक्सएयू

2
या df[order(stopSequence), .SD[c(1L,.N)], by = id]यहाँ
JWilliman

@ JWilliman जरूरी नहीं कि बिल्कुल वैसा ही हो, क्योंकि यह फिर से चालू नहीं होगा id। मुझे लगता df[order(stopSequence), .SD[c(1L, .N)], keyby = id]है कि ट्रिक को करना चाहिए (उपरोक्त समाधान के लिए मामूली अंतर के साथ परिणाम keyएड होगा
माइकलक्रिको

8

कुछ इस तरह:

library(dplyr)

df <- data.frame(id=c(1,1,1,2,2,2,3,3,3),
                 stopId=c("a","b","c","a","b","c","a","b","c"),
                 stopSequence=c(1,2,3,3,1,4,3,1,2))

first_last <- function(x) {
  bind_rows(slice(x, 1), slice(x, n()))
}

df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  do(first_last(.)) %>%
  ungroup

## Source: local data frame [6 x 3]
## 
##   id stopId stopSequence
## 1  1      a            1
## 2  1      c            3
## 3  2      b            1
## 4  2      c            4
## 5  3      b            1
## 6  3      a            3

साथ doआप काफी समूह पर कार्रवाई के किसी भी संख्या प्रदर्शन कर सकते हैं लेकिन @ jeremycg का जवाब जिस तरह से अधिक सिर्फ इस कार्य के लिए उपयुक्त है।


1
एक फ़ंक्शन लिखने पर विचार नहीं किया गया था - निश्चित रूप से कुछ और अधिक जटिल करने का एक अच्छा तरीका।
tospig

1
यह सिर्फ इस्तेमाल करने की तुलना में अधूरा लगता है slice, जैसेdf %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
फ्रैंक

4
असहमति नहीं (और मैंने पोस्ट में एक बेहतर जवाब के रूप में जेरेमीकग की ओर इशारा किया ) लेकिन doयहां एक उदाहरण होने से दूसरों की मदद हो सकती है जब sliceकाम नहीं होगा (यानी एक समूह पर अधिक जटिल संचालन)। और, आप अपनी टिप्पणी को उत्तर के रूप में पोस्ट करते हैं (यह सबसे अच्छा है)।
घंटाघर

6

मुझे निर्दिष्ट प्रश्न पता है dplyr। लेकिन, चूंकि अन्य ने पहले से ही अन्य पैकेजों का उपयोग करके समाधान पोस्ट किया है, इसलिए मैंने अन्य पैकेजों का भी उपयोग करने का निर्णय लिया है:

आधार पैकेज:

df <- df[with(df, order(id, stopSequence, stopId)), ]
merge(df[!duplicated(df$id), ], 
      df[!duplicated(df$id, fromLast = TRUE), ], 
      all = TRUE)

विवरण सारणी:

df <-  setDT(df)
df[order(id, stopSequence)][, .SD[c(1,.N)], by=id]

sqldf:

library(sqldf)
min <- sqldf("SELECT id, stopId, min(stopSequence) AS StopSequence
      FROM df GROUP BY id 
      ORDER BY id, StopSequence, stopId")
max <- sqldf("SELECT id, stopId, max(stopSequence) AS StopSequence
      FROM df GROUP BY id 
      ORDER BY id, StopSequence, stopId")
sqldf("SELECT * FROM min
      UNION
      SELECT * FROM max")

एक प्रश्न में:

sqldf("SELECT * 
        FROM (SELECT id, stopId, min(stopSequence) AS StopSequence
              FROM df GROUP BY id 
              ORDER BY id, StopSequence, stopId)
        UNION
        SELECT *
        FROM (SELECT id, stopId, max(stopSequence) AS StopSequence
              FROM df GROUP BY id 
              ORDER BY id, StopSequence, stopId)")

आउटपुट:

  id stopId StopSequence
1  1      a            1
2  1      c            3
3  2      b            1
4  2      c            4
5  3      a            3
6  3      b            1

3

का उपयोग कर which.minऔर which.max:

library(dplyr, warn.conflicts = F)
df %>% 
  group_by(id) %>% 
  slice(c(which.min(stopSequence), which.max(stopSequence)))

#> # A tibble: 6 x 3
#> # Groups:   id [3]
#>      id stopId stopSequence
#>   <dbl> <fct>         <dbl>
#> 1     1 a                 1
#> 2     1 c                 3
#> 3     2 b                 1
#> 4     2 c                 4
#> 5     3 b                 1
#> 6     3 a                 3

बेंचमार्क

यह वर्तमान स्वीकृत उत्तर की तुलना में बहुत तेज़ है क्योंकि हम समूचे स्टॉप सेंस कॉलम को छाँटने के बजाय समूह द्वारा न्यूनतम और अधिकतम मान पाते हैं।

# create a 100k times longer data frame
df2 <- bind_rows(replicate(1e5, df, F)) 
bench::mark(
  mm =df2 %>% 
    group_by(id) %>% 
    slice(c(which.min(stopSequence), which.max(stopSequence))),
  jeremy = df2 %>%
    group_by(id) %>%
    arrange(stopSequence) %>%
    filter(row_number()==1 | row_number()==n()))
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
#> # A tibble: 2 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 mm           22.6ms     27ms     34.9     14.2MB     21.3
#> 2 jeremy      254.3ms    273ms      3.66    58.4MB     11.0

2

का उपयोग कर data.table:

# convert to data.table
setDT(df) 
# order, group, filter
df[order(stopSequence)][, .SD[c(1, .N)], by = id]

   id stopId stopSequence
1:  1      a            1
2:  1      c            3
3:  2      b            1
4:  2      c            4
5:  3      b            1
6:  3      a            3

1

लंगोटी और दुस्साहसी बयान के साथ एक और दृष्टिकोण। हम एक ही कथन के लिए जो भी सारांश कार्य करते हैं, उसकी एक मनमानी संख्या लागू कर सकते हैं:

lapply(c(first, last), 
       function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>% 
bind_rows()

उदाहरण के लिए आप अधिकतम रोक के साथ पंक्तियों में दिलचस्पी ले सकते हैं।

lapply(c(first, last, max("stopSequence")), 
       function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>%
bind_rows()

0

एक अलग आधार आर विकल्प पहले होगा orderद्वारा idऔर stopSequence, splitउन पर आधारित idहै और हर के लिए idहम केवल प्रथम और अंतिम सूचकांक का चयन करें और dataframe उन सूचकांकों का उपयोग कर सबसेट होते हैं।

df[sapply(with(df, split(order(id, stopSequence), id)), function(x) 
                   c(x[1], x[length(x)])), ]


#  id stopId stopSequence
#1  1      a            1
#3  1      c            3
#5  2      b            1
#6  2      c            4
#8  3      b            1
#7  3      a            3

या इसी तरह का उपयोग कर by

df[unlist(with(df, by(order(id, stopSequence), id, function(x) 
                   c(x[1], x[length(x)])))), ]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.