그룹화 된 데이터에서 첫 번째 및 마지막 행을 선택하십시오.


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())벡터 스캔을 두 번 실행할 필요가 없습니다.
MichaelChirico

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
@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 2012

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]한다 그 결과 위의 솔루션에 약간의 차이로 (트릭을 할해야 key에드
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
이것은 단지 사용에 비해 overcomplicated 것 같다 slice처럼df %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
프랭크

4
동의하지 않는 (그리고 나는 jeremycg의 게시물 에서 더 나은 답변이라고 지적 했지만) do여기 에 예제가 있으면 slice작동하지 않을 때 다른 사람들을 도울 수 있습니다 (예 : 그룹에서 더 복잡한 작업). 그리고 귀하는 귀하의 의견을 답변으로 게시해야합니다 (최상의 답변).
hrbrmstr

6

지정된 질문을 알고 dplyr있습니다. 그러나 다른 사람들이 이미 다른 패키지를 사용하여 솔루션을 게시 했으므로 다른 패키지도 사용하기로 결정했습니다.

기본 패키지 :

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

data.table :

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

기준

전체 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()

예를 들어 max 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.