그룹별로 첫 번째 행 선택


85

이와 같은 데이터 프레임에서

test <- data.frame('id'= rep(1:5,2), 'string'= LETTERS[1:10])
test <- test[order(test$id), ]
rownames(test) <- 1:10

> test
    id string
 1   1      A
 2   1      F
 3   2      B
 4   2      G
 5   3      C
 6   3      H
 7   4      D
 8   4      I
 9   5      E
 10  5      J

각 ID / 문자열 쌍의 첫 번째 행으로 새 행을 만들고 싶습니다. sqldf가 그 안에 R 코드를 허용하면 쿼리는 다음과 같습니다.

res <- sqldf("select id, min(rownames(test)), string 
              from test 
              group by id, string")

> res
    id string
 1   1      A
 3   2      B
 5   3      C
 7   4      D
 9   5      E

다음과 같은 새 열을 만드는 데 부족한 솔루션이 있습니까?

test$row <- rownames(test)

min (row)으로 동일한 sqldf 쿼리를 실행합니까?



1
@Matthew, 내 질문은 오래되었습니다.
dmvianna 2014 년

2
귀하의 질문은 1 년 전이고 다른 질문은 4 년 전입니다. 이 질문의 중복이 너무 많습니다
Matthew

@Matthew 죄송합니다. 날짜를 잘못 읽었 나봐요.
dmvianna 2014 년

답변:


119

duplicated이 작업을 매우 빠르게 수행 하는 데 사용할 수 있습니다 .

test[!duplicated(test$id),]

속도 광을위한 벤치 마크 :

ju <- function() test[!duplicated(test$id),]
gs1 <- function() do.call(rbind, lapply(split(test, test$id), head, 1))
gs2 <- function() do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
jply <- function() ddply(test,.(id),function(x) head(x,1))
jdt <- function() {
  testd <- as.data.table(test)
  setkey(testd,id)
  # Initial solution (slow)
  # testd[,lapply(.SD,function(x) head(x,1)),by = key(testd)]
  # Faster options :
  testd[!duplicated(id)]               # (1)
  # testd[, .SD[1L], by=key(testd)]    # (2)
  # testd[J(unique(id)),mult="first"]  # (3)
  # testd[ testd[,.I[1L],by=id] ]      # (4) needs v1.8.3. Allows 2nd, 3rd etc
}

library(plyr)
library(data.table)
library(rbenchmark)

# sample data
set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]

benchmark(ju(), gs1(), gs2(), jply(), jdt(),
    replications=5, order="relative")[,1:6]
#     test replications elapsed relative user.self sys.self
# 1   ju()            5    0.03    1.000      0.03     0.00
# 5  jdt()            5    0.03    1.000      0.03     0.00
# 3  gs2()            5    3.49  116.333      2.87     0.58
# 2  gs1()            5    3.58  119.333      3.00     0.58
# 4 jply()            5    3.69  123.000      3.11     0.51

다시 시도해 보겠습니다.하지만 첫 번째 열의 경쟁자들과 더 많은 데이터와 더 많은 복제를 사용합니다.

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
benchmark(ju(), jdt(), order="relative")[,1:6]
#    test replications elapsed relative user.self sys.self
# 1  ju()          100    5.48    1.000      4.44     1.00
# 2 jdt()          100    6.92    1.263      5.70     1.15

우승자 : system.time (dat3 [! duplicated (dat3 $ id),]) 사용자 시스템 경과 0.07 0.00 0.07
dmvianna 2011

2
@dmvianna : 나는 그것을 설치하지 않았고 그것을 귀찮게 생각하지 않았습니다. :)
Joshua Ulrich 2011

내 data.table 코드가 가능한 한 효율적이라고 확신합니까? 나는 그 도구에서 최고의 성능을 발휘할 수있는 능력에 확신이 없습니다.
joran

2
또한 data.table을 벤치마킹하려는 경우 keying을 기본 호출 내에 id로 정렬해야합니다.
mnel 2011

1
@JoshuaUlrich 한 가지 더 질문 : 왜 첫 번째 문장이 필요한지, 즉 데이터가 이미 정렬되어 있다는 가정입니다. !duplicated(x)정렬되지 않은 경우에도 각 그룹의 첫 번째 iiuc를 찾습니다.
Matt Dowle 2012

36

나는 dplyr 접근 방식을 선호합니다.

group_by(id) 다음 중 하나

  • filter(row_number()==1) 또는
  • slice(1) 또는
  • slice_head(1) # (dplyr => 1.0)
  • top_n(n = -1)
    • top_n()내부적으로 순위 함수를 사용합니다. 음수는 순위의 맨 아래에서 선택합니다.

경우에 따라 group_by 뒤에 ID를 정렬해야 할 수 있습니다.

library(dplyr)

# using filter(), top_n() or slice()

m1 <-
test %>% 
  group_by(id) %>% 
  filter(row_number()==1)

m2 <-
test %>% 
  group_by(id) %>% 
  slice(1)

m3 <-
test %>% 
  group_by(id) %>% 
  top_n(n = -1)

세 가지 방법 모두 동일한 결과를 반환합니다.

# A tibble: 5 x 2
# Groups:   id [5]
     id string
  <int> <fct> 
1     1 A     
2     2 B     
3     3 C     
4     4 D     
5     5 E

2
외칠만한 가치가 slice있습니다. slice(x)에 대한 바로 가기입니다 filter(row_number() %in% x).
Gregor Thomas

매우 우아합니다. 이 작업을 수행하려면 왜 내 data.table를 a 로 변환해야하는지 아십니까 data.frame?
James Hirschorn

@JamesHirschorn 나는 모든 차이점에 대한 전문가가 아닙니다. 그러나에서 data.table상속 data.frame하므로 많은 경우에 dplyr 명령을 data.table. 예를 들어, 위의 예는 경우 작동 test입니다 data.table. 예를 들어, 참조 stackoverflow.com/questions/13618488/... 깊은 explanantion에 대한
Kresten

이것은 그것을 수행하는 깔끔한 방법이며 data.frame은 실제로 여기에서 조금씩입니다. 개인적으로 ggplot2는 비슷한 방식으로 빌드되기 때문에 항상 tibbles로 작업하는 것이 좋습니다.
Garini

17

는 어때

DT <- data.table(test)
setkey(DT, id)

DT[J(unique(id)), mult = "first"]

편집하다

data.tables키로 첫 번째 행을 반환 하는 고유 한 방법도 있습니다.

jdtu <- function() unique(DT)

test벤치 마크 외부에서 주문 하는 경우 벤치 마크에서 setkeydata.table변환을 제거 할 수도 있습니다 (세트 키는 기본적으로 ID별로 정렬하므로 order).

set.seed(21)
test <- data.frame(id=sample(1e3, 1e5, TRUE), string=sample(LETTERS, 1e5, TRUE))
test <- test[order(test$id), ]
DT <- data.table(DT, key = 'id')
ju <- function() test[!duplicated(test$id),]

jdt <- function() DT[J(unique(id)),mult = 'first']


 library(rbenchmark)
benchmark(ju(), jdt(), replications = 5)
##    test replications elapsed relative user.self sys.self 
## 2 jdt()            5    0.01        1      0.02        0        
## 1  ju()            5    0.05        5      0.05        0         

더 많은 데이터

** 독특한 방법으로 편집 **

set.seed(21)
test <- data.frame(id=sample(1e4, 1e6, TRUE), string=sample(LETTERS, 1e6, TRUE))
test <- test[order(test$id), ]
DT <- data.table(test, key = 'id')
       test replications elapsed relative user.self sys.self 
2  jdt()            5    0.09     2.25      0.09     0.00    
3 jdtu()            5    0.04     1.00      0.05     0.00      
1   ju()            5    0.22     5.50      0.19     0.03        

독특한 방법은 여기서 가장 빠릅니다.


4
키를 설정할 필요도 없습니다. unique(DT,by="id")직접 작동
마태 복음

참고로 현재의 data.table버전> = 1.9.8, 기본 by에 대한 인수 unique입니다 by = seq_along(x)대신 이전 기본의 (모든 열)by = key(x)
IceCreamToucan

12

간단한 ddply옵션 :

ddply(test,.(id),function(x) head(x,1))

속도가 문제인 경우 다음과 유사한 접근 방식을 취할 수 있습니다 data.table.

testd <- data.table(test)
setkey(testd,id)
testd[,.SD[1],by = key(testd)]

또는 이것은 상당히 빠를 수 있습니다.

testd[testd[, .I[1], by = key(testd]$V1]

놀랍게도 sqldf는 더 빠르게 처리합니다. 1.77 0.13 1.92 vs 10.53 0.00 10.79 with data.table
dmvianna

3
@dmvianna 나는 반드시 data.table을 계산하지 않을 것입니다. 저는 해당 도구에 대한 전문가가 아니므로 data.table 코드가이를 수행하는 가장 효율적인 방법이 아닐 수 있습니다.
joran

나는 이것을 너무 일찍 찬성했다. 큰 data.table에서 실행했을 때 엄청나게 느 렸고 작동하지 않았습니다. 이후 행 수가 동일했습니다.
James Hirschorn

@JamesHirachorn 오래 전에이 글을 썼는데 패키지가 많이 바뀌었고 data.table을 거의 사용하지 않습니다. 해당 패키지로이 작업을 수행하는 올바른 방법을 찾으면 더 나은 편집을 위해 자유롭게 제안하십시오.
joran

8

이제에 대해 dplyr고유 한 카운터를 추가합니다.

df %>%
    group_by(aa, bb) %>%
    summarise(first=head(value,1), count=n_distinct(value))

그룹을 만들고 그룹 내에서 요약합니다.

데이터가 숫자 인 경우 다음을 사용할 수 있습니다.
first(value)[도 있습니다.last(value) ]head(value, 1)

보다: http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html

완전한:

> df
Source: local data frame [16 x 3]

   aa bb value
1   1  1   GUT
2   1  1   PER
3   1  2   SUT
4   1  2   GUT
5   1  3   SUT
6   1  3   GUT
7   1  3   PER
8   2  1   221
9   2  1   224
10  2  1   239
11  2  2   217
12  2  2   221
13  2  2   224
14  3  1   GUT
15  3  1   HUL
16  3  1   GUT

> library(dplyr)
> df %>%
>   group_by(aa, bb) %>%
>   summarise(first=head(value,1), count=n_distinct(value))

Source: local data frame [6 x 4]
Groups: aa

  aa bb first count
1  1  1   GUT     2
2  1  2   SUT     2
3  1  3   SUT     3
4  2  1   221     3
5  2  2   217     3
6  3  1   GUT     2

이 답변은 상당히 오래되었습니다 dplyr. 포함 할 모든 단일 열에 대한 문을 작성할 필요가없는 더 나은 방법이 있습니다 (예를 들어 아래 atomman의 답변 참조) . Also I'm not sure what *"if data is numeric"* has anything to do with whether or not one would use first (value)`vs head(value)(또는 그냥 value[1])
Gregor Thomas

7

(1) SQLite에는 rowid의사 열이 내장되어 있으므로 다음과 같이 작동합니다.

sqldf("select min(rowid) rowid, id, string 
               from test 
               group by id")

기부:

  rowid id string
1     1  1      A
2     3  2      B
3     5  3      C
4     7  4      D
5     9  5      E

(2) 또한 sqldf자체에 row.names=인수가 있습니다.

sqldf("select min(cast(row_names as real)) row_names, id, string 
              from test 
              group by id", row.names = TRUE)

기부:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

(3) 위의 두 요소를 혼합하는 세 번째 대안이 더 좋을 수 있습니다.

sqldf("select min(rowid) row_names, id, string 
               from test 
               group by id", row.names = TRUE)

기부:

  id string
1  1      A
3  2      B
5  3      C
7  4      D
9  5      E

이 셋 모두를 사용하는 SQL에 SQLite는 확장에 의존하는 것을 주 min또는 max다른 열이 발생할 보장은 같은 행에서 선택된다. (보장되지 않을 수있는 다른 SQL 기반 데이터베이스에서)


감사! 여러 집계 함수를 사용하여 집계 단계에서 첫 번째 / 마지막 요소를 취하는 것이 일반화 될 수 있기 때문에 허용 된 답변 IMO보다 훨씬 낫습니다 (즉,이 변수의 첫 번째 항목을 가져오고 해당 변수를 합산하는 등).
Bridgeburners 2015 년

4

베이스 R 옵션은입니다 split()- lapply()- do.call(): 관용구

> do.call(rbind, lapply(split(test, test$id), head, 1))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

보다 직접적인 옵션은 다음과 lapply()같은 [기능입니다.

> do.call(rbind, lapply(split(test, test$id), `[`, 1, ))
  id string
1  1      A
2  2      B
3  3      C
4  4      D
5  5      E

호출 1, )끝의 쉼표 공백 은 첫 번째 행과 모든 열을 선택하도록 호출 하는 것과 동일하므로 필수적 입니다.lapply()[1, ]


사용자 시스템 91.84 6.02 101.10 경과 :이 개빈은 매우 느렸다
dmvianna에게

데이터 프레임과 관련된 모든 것이 있습니다. 그들의 효용에는 대가가 따릅니다. 따라서 예를 들어 data.table.
Gavin Simpson

내 수비와 R에서 당신은 질문에서 효율성에 대해 아무것도 언급하지 않았습니다. 자주 사용의 용이성 이다 기능은. 최소한 data.table을 지원하는 다음 버전까지 "느린"플라이의 인기를 확인하십시오.
Gavin Simpson 2012

1
나는 동의한다. 나는 당신을 모욕하려는 것이 아닙니다. 나는 여호수아 - 울리히의 방법 @라고하지만, 발견했다 모두 빠르고 편리합니다. : 7)
dmvianna

사과 할 필요도없고 모욕으로 받아들이지 않았습니다. 효율성에 대한 주장없이 제공되었다는 것을 지적한 것뿐입니다. 이 Stack Overflow Q & A는 귀하의 이익을위한 것이 아니라 유사한 문제가있어 귀하의 질문을 접한 다른 사용자를위한 것임을 기억하십시오 .
Gavin Simpson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.