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


97

У мене є список працівників, і я повинен знати, в якому відділі вони найчастіше перебувають. Тривіально підрахувати ідентифікатор працівника до назви відділу, але складніше повернути назву таблиці, а не кількість підрахунків, з таблиці частот. Простий приклад нижче (імена стовпців = відділи, імена рядків = ідентифікатори співробітників).

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

наскільки великі ваші фактичні дані?
Арун

1
@Arun> dim (тест) [1] 26746 18
dmvianna

6
Цікавим узагальненням буде назва імен стовпців найбільших n значень на рядок
Hack-R

Відповіді:


99

Один варіант використання ваших даних (для подальшого використання, використовуйте 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внутрішньо. Однак ви можете не побачити різниці в продуктивності цих розмірів.
Арун

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 ..... чи знаєте ви, що було б еквівалентним рішенням ?
користувач1412

Привіт @ користувач1412. Дякую за ваше цікаве запитання. Зараз я не маю жодної ідеї, крім використання 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

A 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

1

Одним із варіантів 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)з іменами стовпців

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.