Pilih baris pertama dan terakhir dari data yang dikelompokkan


140

Pertanyaan

Menggunakan dplyr, bagaimana cara memilih observasi / baris atas dan bawah dari data yang dikelompokkan dalam satu pernyataan?

Data & Contoh

Diberikan bingkai data

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

Saya bisa mendapatkan pengamatan atas dan bawah dari setiap kelompok menggunakan slice, tetapi menggunakan dua pernyataan terpisah:

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

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

Dapatkah saya menggabungkan dua statmenets ini menjadi salah satu yang menyeleksi kedua atas dan bawah pengamatan?


Jawaban:


243

Mungkin ada cara yang lebih cepat:

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

70
rownumber() %in% c(1, n())akan meniadakan kebutuhan untuk menjalankan pemindaian vektor dua kali
MichaelChirico

13
@MichaelChirico Saya curiga Anda menghilangkan _? yaitufilter(row_number() %in% c(1, n()))
Eric Fail

110

Hanya untuk kelengkapan: Anda dapat memberikan slicevektor indeks:

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

pemberian yang mana

  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

bahkan mungkin lebih cepat dari filter- belum menguji ini, tapi lihat di sini
Tjebo

1
@Tjebo Tidak seperti filter, slice dapat mengembalikan baris yang sama beberapa kali, misalnya mtcars[1, ] %>% slice(c(1, n()))jadi pilihan di antara mereka bergantung pada apa yang ingin Anda kembalikan. Saya berharap waktunya akan dekat kecuali nsangat besar (di mana irisan mungkin disukai), tetapi belum diuji juga.
Frank

15

Tidak dplyr, tapi jauh lebih langsung menggunakan 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

Penjelasan lebih detail:

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

Pastikan untuk memeriksa wiki Memulai untuk mengetahui data.tabledasar - dasarnya


1
Atau df[ df[order(stopSequence), .I[c(1,.N)], keyby=id]$V1 ]. Melihat idmuncul dua kali itu aneh bagiku.
Frank

Anda dapat mengatur kunci dalam setDTpanggilan. Jadi ordertidak perlu menelepon ke sini.
Artem Klevtsov

1
@ArtemKlevtsov - Anda mungkin tidak selalu ingin menyetel kuncinya.
SymbolixAU

2
Atau df[order(stopSequence), .SD[c(1L,.N)], by = id]. Lihat di sini
JWilliman

@JWilliman itu belum tentu sama persis , karena tidak akan menyusun ulang id. Saya pikir df[order(stopSequence), .SD[c(1L, .N)], keyby = id]harus melakukan trik (dengan perbedaan kecil pada solusi di atas sehingga hasilnya akan keydiedit
MichaelChirico

8

Sesuatu seperti:

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

Dengan doAnda cukup banyak dapat melakukan sejumlah operasi pada grup tetapi jawaban @ jeremycg jauh lebih tepat hanya untuk tugas ini.


1
Tidak mempertimbangkan menulis fungsi - tentu saja cara yang baik untuk melakukan sesuatu yang lebih kompleks.
tospig

1
Ini tampaknya terlalu rumit dibandingkan dengan hanya menggunakan slice, sepertidf %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
Frank

4
Tidak tidak setuju (dan saya menunjuk ke jeremycg sebagai jawaban yang lebih baik di posting) tetapi memiliki docontoh di sini dapat membantu orang lain ketika slicetidak berfungsi (yaitu operasi yang lebih kompleks pada grup). Dan, Anda harus memposting komentar Anda sebagai jawaban (itu yang terbaik).
hrbrmstr

6

Saya tahu pertanyaan itu ditentukan dplyr. Tetapi, karena orang lain sudah memposting solusi menggunakan paket lain, saya memutuskan untuk mencoba menggunakan paket lain juga:

Paket dasar:

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

tabel data:

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")

Dalam satu kueri:

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)")

Keluaran:

  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

menggunakan which.mindan 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

patokan

Ini juga jauh lebih cepat daripada jawaban yang diterima saat ini karena kita menemukan nilai min dan maks berdasarkan grup, daripada mengurutkan seluruh kolom 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

Menggunakan 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

Pendekatan lain dengan pernyataan lapply dan dplyr. Kita dapat menerapkan nomor acak dari fungsi ringkasan apa pun ke pernyataan yang sama:

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

Misalnya, Anda dapat tertarik pada baris dengan nilai stopSequence maks dan melakukan:

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

0

Alternatif R dasar yang berbeda adalah yang pertama orderoleh iddan stopSequence, splitmereka berdasarkan iddan untuk setiap idkita hanya memilih indeks pertama dan terakhir dan subset kerangka data menggunakan indeks tersebut.

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

Atau penggunaan serupa by

df[unlist(with(df, by(order(id, stopSequence), id, function(x) 
                   c(x[1], x[length(x)])))), ]
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.