Для каждой строки вернуть имя столбца наибольшего значения


100

У меня есть список сотрудников, и мне нужно знать, в каком отделе они находятся чаще всего. Сведение идентификатора сотрудника к названию отдела - тривиальная задача, но сложнее вернуть название отдела, а не количество учетных записей из частотной таблицы. Ниже приведен простой пример (имена столбцов = отделы, имена строк = идентификаторы сотрудников).

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> dim (test) [1] 26746 18
dmvianna

6
Интересное обобщение - имена столбцов с наибольшими n значениями в строке
Hack-R

Ответы:


103

Один из вариантов использования ваших данных (для дальнейшего использования используйте 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

1
@dmvianna - which.maxтогда можно использовать.
thelatemail

Я предполагаю, что порядок сохраняется, поэтому я могу создать новый столбец с этим вектором, который будет правильно согласован с идентификаторами сотрудников. Это верно?
dmvianna

applyпреобразует data.frameв matrixвнутренне. Однако вы можете не увидеть разницы в производительности по этим параметрам.
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решение, вот оно. Это немного сложно, поскольку вы предпочитаете получать идентификатор для первого максимума. Намного проще, если вы хотите получить последний максимум. Тем не менее, это не так уж сложно и быстро!

Здесь я сгенерировал данные ваших размеров (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неплохо масштабируется.


Изменить: если любой из максимальных идентификаторов в порядке, то:

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

11

Одним из решений может быть изменение формы даты с широкой на длинную, поместив все отделы в один столбец и счетчики в другой, сгруппировать по идентификатору работодателя (в данном случае по номеру строки), а затем отфильтровать по отделам с помощью максимальное значение. Есть несколько вариантов обработки связей с помощью этого подхода.

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

У меня было аналогичное требование, но я хочу, чтобы имя столбца имело минимальное значение для каждой строки ..... у нас, похоже, нет min.col в R ..... знаете ли вы, какое будет эквивалентное решение ?
user1412

Привет @ user1412. Спасибо за интересный вопрос. У меня сейчас нет никаких идей, кроме как использовать which.minв чем-то, что могло бы выглядеть так: DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]или DT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]в фиктивных данных выше. Это не учитывает связи и возвращает только первый минимум. Может быть, подумайте о том, чтобы задать отдельный вопрос. Мне также было бы любопытно, какие еще ответы вы получите.
Валентин

1
Трюк , чтобы получить минимальный столбец посылает негатив data.frame в max.col, как: colnames(.SD)[max.col(-.SD, ties.method="first")].
lwshang

6

dplyrРешение:

Идея:

  • добавить rowids как столбец
  • преобразовать в длинный формат
  • фильтр для макс в каждой группе

Код:

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 выше? Мне они кажутся примерно одинаковыми.
Грегор Томас

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.