Chọn hàng đầu tiên và cuối cùng từ dữ liệu được nhóm


137

Câu hỏi

Sử dụng dplyr, làm cách nào để chọn các quan sát / hàng dữ liệu được nhóm trên cùng và dưới cùng trong một câu lệnh?

Dữ liệu & ví dụ

Đưa ra một khung dữ liệu

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

Tôi có thể nhận được các quan sát trên và dưới từ mỗi nhóm bằng slicecách sử dụng hai cách riêng biệt:

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

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

Tôi có thể kết hợp hai statmenet này thành một trong đó chọn cả quan sát trên và dưới không?


Câu trả lời:


232

Có lẽ có một cách nhanh hơn:

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

66
rownumber() %in% c(1, n())sẽ làm giảm nhu cầu chạy quét vector hai lần
MichaelChirico

13
@MichaelChirico Tôi nghi ngờ bạn đã bỏ qua một _? tức làfilter(row_number() %in% c(1, n()))
Eric Fail

106

Chỉ để hoàn thiện: Bạn có thể vượt qua slicemột vectơ chỉ số:

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

cái nào cho

  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

thậm chí có thể nhanh hơn filter- chưa thử nghiệm điều này, nhưng xem tại đây
Tjebo

1
@Tjebo Không giống như bộ lọc, lát có thể trả về cùng một hàng nhiều lần, ví dụ như mtcars[1, ] %>% slice(c(1, n()))vậy, theo nghĩa đó, sự lựa chọn giữa chúng phụ thuộc vào những gì bạn muốn trả về. Tôi hy vọng thời gian sẽ kết thúc trừ khi nrất lớn (trong đó lát cắt có thể được ưa thích), nhưng cũng không được thử nghiệm.
Frank

15

Không dplyr, nhưng nó sử dụng trực tiếp hơn nhiều 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

Giải thích chi tiết hơn:

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

Hãy chắc chắn kiểm tra wiki Bắt đầu để có được những data.tableđiều cơ bản


1
Hoặc df[ df[order(stopSequence), .I[c(1,.N)], keyby=id]$V1 ]. Nhìn thấy idxuất hiện hai lần là lạ đối với tôi.
Frank

Bạn có thể đặt các phím trong setDTcuộc gọi. Vì vậy, một ordercuộc gọi không cần ở đây.
Artem Klevtsov

1
@ArtemKlevtsov - mặc dù bạn không phải lúc nào cũng muốn đặt các phím.
SymbolixAU

2
Hoặc df[order(stopSequence), .SD[c(1L,.N)], by = id]. Xem tại đây
JWilliman

@JWilliman rằng sẽ không nhất thiết phải chính xác như nhau, vì nó sẽ không sắp xếp lại về id. Tôi nghĩ df[order(stopSequence), .SD[c(1L, .N)], keyby = id]nên thực hiện thủ thuật (với sự khác biệt nhỏ với giải pháp ở trên rằng kết quả sẽ là keyed
MichaelChirico

8

Cái gì đó như:

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

Với dobạn có thể thực hiện khá nhiều hoạt động trong nhóm nhưng câu trả lời của @ jeremycg là cách phù hợp hơn cho nhiệm vụ này.


1
Không được coi là viết một chức năng - chắc chắn là một cách tốt để làm một cái gì đó phức tạp hơn.
tospig 21/07/2015

1
Điều này có vẻ quá phức tạp so với việc chỉ sử dụng slice, nhưdf %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
Frank

4
Không đồng ý (và tôi đã chỉ ra jeremycg là một câu trả lời tốt hơn trong bài viết) nhưng có một doví dụ ở đây có thể giúp đỡ người khác khi slicekhông làm việc (nghĩa là các hoạt động phức tạp hơn trong một nhóm). Và, bạn sẽ gửi bình luận của bạn như một câu trả lời (đó là câu trả lời hay nhất).
hrbrmstr

6

Tôi biết câu hỏi được chỉ định dplyr. Nhưng, vì các giải pháp khác đã đăng các giải pháp sử dụng các gói khác, tôi cũng quyết định sử dụng các gói khác:

Gói cơ sở:

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

bảng dữ liệu:

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

Trong một truy vấn:

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

Đầu ra:

  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

sử dụng which.minwhich.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

điểm chuẩn

Nó cũng nhanh hơn nhiều so với câu trả lời được chấp nhận hiện tại vì chúng tôi tìm thấy giá trị tối thiểu và tối đa theo nhóm, thay vì sắp xếp toàn bộ cột 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

Sử dụng 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

Một cách tiếp cận khác với lapply và một tuyên bố dplyr. Chúng ta có thể áp dụng một số lượng tùy ý của bất kỳ hàm tóm tắt nào cho cùng một câu lệnh:

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

Ví dụ, bạn có thể quan tâm đến các hàng có giá trị stopSequence tối đa và thực hiện:

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

0

Một cơ sở R thay thế khác nhau sẽ được đầu orderbởi idstopSequence, splithọ dựa trên idvà cho mỗi idchúng tôi chỉ chọn những chỉ số đầu tiên và cuối cùng và tập hợp con các dataframe sử dụng những chỉ số.

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

Hoặc tương tự bằng cách sử dụng by

df[unlist(with(df, by(order(id, stopSequence), id, function(x) 
                   c(x[1], x[length(x)])))), ]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.