เลือกแถวแรกและแถวสุดท้ายจากข้อมูลที่จัดกลุ่ม


140

คำถาม

การใช้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

ฉันสามารถรวมสถิติทั้งสองนี้เป็นหนึ่งเดียวเพื่อเลือกการสังเกตทั้งด้านบนและด้านล่างได้หรือไม่?


คำตอบ:


243

อาจมีวิธีที่เร็วกว่านี้:

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

70
rownumber() %in% c(1, n())จะทำให้ไม่จำเป็นต้องเรียกใช้การสแกนเวกเตอร์สองครั้ง
MichaelChirico

13
@MichaelChirico ฉันสงสัยว่าคุณละเว้น_? เช่นfilter(row_number() %in% c(1, n()))
Eric Fail

110

เพื่อความสมบูรณ์: คุณสามารถส่ง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
@Tjebo แตกต่างจากตัวกรอง slice สามารถส่งคืนแถวเดียวกันได้หลายครั้งเช่นmtcars[1, ] %>% slice(c(1, n()))ในแง่นั้นการเลือกระหว่างพวกเขาขึ้นอยู่กับสิ่งที่คุณต้องการส่งคืน ฉันคาดหวังว่าการกำหนดเวลาจะใกล้เคียงกันเว้นแต่nจะมีขนาดใหญ่มาก (ซึ่งอาจเป็นที่ชื่นชอบของชิ้นส่วน) แต่ยังไม่ได้ทดสอบ
Frank

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ปรากฏสองครั้งเป็นเรื่องแปลกสำหรับฉัน
Frank

คุณสามารถตั้งค่าคีย์ในการsetDTโทร ดังนั้นการorderโทรไม่จำเป็นต้องที่นี่
Artem Klevtsov

1
@ArtemKlevtsov - คุณอาจไม่ต้องการตั้งค่าคีย์เสมอไป
SymbolixAU

2
หรือdf[order(stopSequence), .SD[c(1L,.N)], by = id]. ดูที่นี่
JWilliman

@JWilliman ว่าจะไม่จำเป็นต้องตรงidเหมือนกันเพราะมันจะไม่สั่งซื้อได้ที่ ผมคิดว่าdf[order(stopSequence), .SD[c(1L, .N)], keyby = id]ควรทำเคล็ดลับ (มีความแตกต่างเล็ก ๆ น้อย ๆ เพื่อแก้ปัญหาดังกล่าวข้างต้นว่าผลที่จะได้รับการkeyed
MichaelChirico

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()))
Frank

4
ไม่เห็นด้วย (และฉันชี้ไปที่ jeremycg เป็นคำตอบที่ดีกว่าในโพสต์) แต่การมีdoตัวอย่างที่นี่อาจช่วยผู้อื่นเมื่อsliceไม่ได้ผล (เช่นการดำเนินการที่ซับซ้อนมากขึ้นในกลุ่ม) และคุณ shld โพสต์ความคิดเห็นของคุณเป็นคำตอบ (ดีที่สุด)
hrbrmstr

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

5

ใช้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

เกณฑ์มาตรฐาน

นอกจากนี้ยังเร็วกว่าคำตอบที่ยอมรับในปัจจุบันมากเนื่องจากเราค้นหาค่าต่ำสุดและสูงสุดตามกลุ่มแทนที่จะจัดเรียงคอลัมน์ stopSequence ทั้งหมด

# 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 และ dplyr เราสามารถใช้จำนวนฟังก์ชันสรุปอะไรก็ได้ตามอำเภอใจกับคำสั่งเดียวกัน

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

ตัวอย่างเช่นคุณสามารถสนใจแถวที่มีค่า stopSequence สูงสุดเช่นกันและทำ:

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

0

ทางเลือกที่ฐาน R ที่แตกต่างกันจะเป็นครั้งแรก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)])))), ]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.