각 행에 대해 가장 큰 값의 열 이름을 반환합니다.


100

저는 직원 명단이 있고 그들이 어떤 부서에 가장 자주 속해 있는지 알아야합니다. 부서 이름에 대해 직원 ID를 표로 만드는 것은 간단하지만 빈도 테이블에서 명단 수보다는 부서 이름을 반환하는 것이 더 까다 롭습니다. 아래의 간단한 예 (열 이름 = 부서, 행 이름 = 직원 ID).

DF <- matrix(sample(1:9,9),ncol=3,nrow=3)
DF <- as.data.frame.matrix(DF)
> DF
  V1 V2 V3
1  2  7  9
2  8  3  6
3  1  5  4

이제 어떻게 얻을 수 있습니까

> DF2
  RE
1 V3
2 V1
3 V2

실제 데이터는 얼마나 큽니까?
Arun

1
@Arun> 딤 (시험) [1] 18 26,746
dmvianna

6
흥미로운 일반화는 행당 가장 큰 n 개의 값 열 이름입니다
Hack-R

답변:


102

데이터를 사용하는 한 가지 옵션 (향후 참조 set.seed()를 위해 sample재현 가능한 예제를 만드는 데 사용 ) :

DF <- data.frame(V1=c(2,8,1),V2=c(7,3,5),V3=c(9,6,4))

colnames(DF)[apply(DF,1,which.max)]
[1] "V3" "V1" "V2"

사용하는 것보다 더 빠른 솔루션 apply은 다음과 max.col같습니다.

colnames(DF)[max.col(DF,ties.method="first")]
#[1] "V3" "V1" "V2"

... 또는 ties.method중 하나가 될 수있는 곳"random" "first""last"

물론 최대 값과 동일한 두 개의 열이있는 경우 문제가 발생합니다. 일부 행에 대한 결과가 두 개 이상이므로 해당 인스턴스에서 무엇을 하려는지 잘 모르겠습니다. 예 :

DF <- data.frame(V1=c(2,8,1),V2=c(7,3,5),V3=c(7,6,4))
apply(DF,1,function(x) which(x==max(x)))

[[1]]
V2 V3 
 2  3 

[[2]]
V1 
 1 

[[3]]
V2 
 2 

두 개의 동일한 열이 있으면 일반적으로 첫 번째 열을 선택합니다. 이것은 내 통계 분석을 화나게하지 않는 국경 사건입니다.
dmvianna jul.

1
@dmvianna-사용 which.max하면 괜찮을 것입니다.
thelatemail

순서가 유지된다고 가정하므로 직원 ID에 올바르게 정렬되는이 벡터로 새 열을 만들 수 있습니다. 그 맞습니까?
dmvianna 2013

apply는 변환 data.framematrix내부적으로. 그러나 이러한 차원에서 성능 차이를 보지 못할 수 있습니다.
Arun

2
@PankajKaundal는 - 어떻게 이것에 대해, 고유 값을 가정colnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
thelatemail

15

data.table솔루션에 관심이 있다면 여기에 있습니다. 첫 번째 최대 값에 대한 ID를 얻는 것을 선호하기 때문에 약간 까다 롭습니다. 마지막 최대 값을 원하면 훨씬 쉽습니다. 그럼에도 불구하고 그렇게 복잡하지 않고 빠릅니다!

여기에 치수 데이터 (26746 * 18)가 생성되었습니다.

데이터

set.seed(45)
DF <- data.frame(matrix(sample(10, 26746*18, TRUE), ncol=18))

data.table 대답:

require(data.table)
DT <- data.table(value=unlist(DF, use.names=FALSE), 
            colid = 1:nrow(DF), rowid = rep(names(DF), each=nrow(DF)))
setkey(DT, colid, value)
t1 <- DT[J(unique(colid), DT[J(unique(colid)), value, mult="last"]), rowid, mult="first"]

벤치마킹 :

# data.table solution
system.time({
DT <- data.table(value=unlist(DF, use.names=FALSE), 
            colid = 1:nrow(DF), rowid = rep(names(DF), each=nrow(DF)))
setkey(DT, colid, value)
t1 <- DT[J(unique(colid), DT[J(unique(colid)), value, mult="last"]), rowid, mult="first"]
})
#   user  system elapsed 
#  0.174   0.029   0.227 

# apply solution from @thelatemail
system.time(t2 <- colnames(DF)[apply(DF,1,which.max)])
#   user  system elapsed 
#  2.322   0.036   2.602 

identical(t1, t2)
# [1] TRUE

이러한 차원의 데이터에서 약 11 배 더 빠르며 data.table확장도 꽤 좋습니다.


편집 : 최대 ID 중 하나라도 괜찮다면 :

DT <- data.table(value=unlist(DF, use.names=FALSE), 
            colid = 1:nrow(DF), rowid = rep(names(DF), each=nrow(DF)))
setkey(DT, colid, value)
t1 <- DT[J(unique(colid)), rowid, mult="last"]

실제로 첫 번째 또는 마지막 최대 값인지 상관하지 않습니다. 먼저 단순성을 추구하지만 data.table 솔루션이 향후에 유용 할 것이라고 확신합니다. 감사합니다!
dmvianna 2013

11

한 가지 해결책은 모든 부서를 한 열에 넣고 다른 부서를 세고 고용주 ID (이 경우 행 번호)로 그룹화 한 다음 해당 부서로 필터링하는 것입니다. 최대 값. 이 접근 방식과의 관계를 처리하기위한 몇 가지 옵션이 있습니다.

library(tidyverse)

# sample data frame with a tie
df <- data_frame(V1=c(2,8,1),V2=c(7,3,5),V3=c(9,6,5))

# If you aren't worried about ties:  
df %>% 
  rownames_to_column('id') %>%  # creates an ID number
  gather(dept, cnt, V1:V3) %>% 
  group_by(id) %>% 
  slice(which.max(cnt)) 

# A tibble: 3 x 3
# Groups:   id [3]
  id    dept    cnt
  <chr> <chr> <dbl>
1 1     V3       9.
2 2     V1       8.
3 3     V2       5.


# If you're worried about keeping ties:
df %>% 
  rownames_to_column('id') %>%
  gather(dept, cnt, V1:V3) %>% 
  group_by(id) %>% 
  filter(cnt == max(cnt)) %>% # top_n(cnt, n = 1) also works
  arrange(id)

# A tibble: 4 x 3
# Groups:   id [3]
  id    dept    cnt
  <chr> <chr> <dbl>
1 1     V3       9.
2 2     V1       8.
3 3     V2       5.
4 3     V3       5.


# If you're worried about ties, but only want a certain department, you could use rank() and choose 'first' or 'last'
df %>% 
  rownames_to_column('id') %>%
  gather(dept, cnt, V1:V3) %>% 
  group_by(id) %>% 
  mutate(dept_rank  = rank(-cnt, ties.method = "first")) %>% # or 'last'
  filter(dept_rank == 1) %>% 
  select(-dept_rank) 

# A tibble: 3 x 3
# Groups:   id [3]
  id    dept    cnt
  <chr> <chr> <dbl>
1 2     V1       8.
2 3     V2       5.
3 1     V3       9.

# if you wanted to keep the original wide data frame
df %>% 
  rownames_to_column('id') %>%
  left_join(
    df %>% 
      rownames_to_column('id') %>%
      gather(max_dept, max_cnt, V1:V3) %>% 
      group_by(id) %>% 
      slice(which.max(max_cnt)), 
    by = 'id'
  )

# A tibble: 3 x 6
  id       V1    V2    V3 max_dept max_cnt
  <chr> <dbl> <dbl> <dbl> <chr>      <dbl>
1 1        2.    7.    9. V3            9.
2 2        8.    3.    6. V1            8.
3 3        1.    5.    5. V2            5.

11

위의 제안에 따라 다음 data.table솔루션이 매우 빠르게 작동했습니다.

library(data.table)

set.seed(45)
DT <- data.table(matrix(sample(10, 10^7, TRUE), ncol=10))

system.time(
  DT[, col_max := colnames(.SD)[max.col(.SD, ties.method = "first")]]
)
#>    user  system elapsed 
#>    0.15    0.06    0.21
DT[]
#>          V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 col_max
#>       1:  7  4  1  2  3  7  6  6  6   1      V1
#>       2:  4  6  9 10  6  2  7  7  1   3      V4
#>       3:  3  4  9  8  9  9  8  8  6   7      V3
#>       4:  4  8  8  9  7  5  9  2  7   1      V4
#>       5:  4  3  9 10  2  7  9  6  6   9      V4
#>      ---                                       
#>  999996:  4  6 10  5  4  7  3  8  2   8      V3
#>  999997:  8  7  6  6  3 10  2  3 10   1      V6
#>  999998:  2  3  2  7  4  7  5  2  7   3      V4
#>  999999:  8 10  3  2  3  4  5  1  1   4      V2
#> 1000000: 10  4  2  6  6  2  8  4  7   4      V1

또한 .SD다음에서 언급하여 고려해야 할 열을 항상 지정할 수있는 이점이 있습니다 .SDcols.

DT[, MAX2 := colnames(.SD)[max.col(.SD, ties.method="first")], .SDcols = c("V9", "V10")]

@lwshang이 제안한 것처럼 가장 작은 값의 열 이름이 필요한 경우 -.SD다음 을 사용해야합니다 .

DT[, col_min := colnames(.SD)[max.col(-.SD, ties.method = "first")]]

비슷한 요구 사항이 있었지만 각 행에 대해 최소값을 갖는 열 이름을 얻고 싶습니다 ..... 우리는 R에 min.col이없는 것 같습니다. ?
user1412

안녕하세요 @ user1412. 흥미로운 질문에 감사드립니다. 지금 당장은를 사용하는 것 외에 다른 생각이 없습니다 which.min: DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]또는 DT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]위의 더미 데이터에서. 이것은 동점을 고려하지 않고 첫 번째 최소값 만 반환합니다. 별도의 질문을 고려해보십시오. 나는 당신이 얻을 다른 답변이 궁금합니다.
Valentin

1
최소 열을 얻는 트릭은 다음과 같이 data.frame의 음수를 max.col로 보내는 것 colnames(.SD)[max.col(-.SD, ties.method="first")]입니다.
lwshang

6

dplyr솔루션 :

생각:

  • rowid를 열로 추가
  • 긴 형식으로 모양 변경
  • 각 그룹의 최대 필터

암호:

DF = data.frame(V1=c(2,8,1),V2=c(7,3,5),V3=c(9,6,4))
DF %>% 
  rownames_to_column() %>%
  gather(column, value, -rowname) %>%
  group_by(rowname) %>% 
  filter(rank(-value) == 1) 

결과:

# A tibble: 3 x 3
# Groups:   rowname [3]
  rowname column value
  <chr>   <chr>  <dbl>
1 2       V1         8
2 3       V2         5
3 1       V3         9

이 접근 방식은 상단 n열 을 얻기 위해 쉽게 확장 할 수 있습니다. 예 n=2:

DF %>% 
  rownames_to_column() %>%
  gather(column, value, -rowname) %>%
  group_by(rowname) %>% 
  mutate(rk = rank(-value)) %>%
  filter(rk <= 2) %>% 
  arrange(rowname, rk) 

결과:

# A tibble: 6 x 4
# Groups:   rowname [3]
  rowname column value    rk
  <chr>   <chr>  <dbl> <dbl>
1 1       V3         9     1
2 1       V2         7     2
3 2       V1         8     1
4 2       V3         6     2
5 3       V2         5     1
6 3       V3         4     2

1
이 접근법과 위의 sbha의 답변의 차이점에 대해 의견을 말씀해 주시겠습니까? 그들은 나에게 거의 똑같이 보입니다.
Gregor Thomas

2

간단한 for루프도 유용 할 수 있습니다.

> df<-data.frame(V1=c(2,8,1),V2=c(7,3,5),V3=c(9,6,4))
> df
  V1 V2 V3
1  2  7  9
2  8  3  6
3  1  5  4
> df2<-data.frame()
> for (i in 1:nrow(df)){
+   df2[i,1]<-colnames(df[which.max(df[i,])])
+ }
> df2
  V1
1 V3
2 V1
3 V2

2

한 가지 옵션 dplyr 1.0.0은 다음과 같습니다.

DF %>%
 rowwise() %>%
 mutate(row_max = names(.)[which.max(c_across(everything()))])

     V1    V2    V3 row_max
  <dbl> <dbl> <dbl> <chr>  
1     2     7     9 V3     
2     8     3     6 V1     
3     1     5     4 V2     

샘플 데이터 :

DF <- structure(list(V1 = c(2, 8, 1), V2 = c(7, 3, 5), V3 = c(9, 6, 
4)), class = "data.frame", row.names = c(NA, -3L))

0

다음은 data.table과 함께 작동하고 더 간단한 답변입니다. 이것은 data.table의 이름이 다음과 같다고 가정합니다 yourDF.

j1 <- max.col(yourDF[, .(V1, V2, V3, V4)], "first")
yourDF$newCol <- c("V1", "V2", "V3", "V4")[j1]

교체 ("V1", "V2", "V3", "V4")(V1, V2, V3, V4) 열 이름으로


열 값에 존재하는 경우 NA 값을 무시하는 방법에 도움이 될 수 있습니까?
Partha sarathi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.