Untuk setiap baris, kembalikan nama kolom dengan nilai terbesar

100

Saya memiliki daftar karyawan, dan saya perlu tahu di departemen mana mereka paling sering berada. Tidaklah mudah untuk membuat tabulasi ID karyawan dengan nama departemen, tetapi lebih sulit untuk mengembalikan nama departemen, daripada jumlah jumlah daftar, dari tabel frekuensi. Contoh sederhana di bawah ini (nama kolom = departemen, nama baris = id karyawan).

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

Sekarang bagaimana cara mendapatkannya

> DF2
  RE
1 V3
2 V1
3 V2
dmvianna
sumber
seberapa besar data Anda yang sebenarnya?
Arun
1
@Arun> redup (tes) [1] 26746 18
dmvianna
6
Sebuah generalisasi yang menarik akan menjadi nama kolom n nilai terbesar per baris
Hack-R

Jawaban:

103

Satu opsi menggunakan data Anda (untuk referensi di masa mendatang, gunakan set.seed()untuk membuat contoh menggunakan dapat sampledireproduksi):

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"

Solusi yang lebih cepat daripada menggunakan applymungkin max.col:

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

... di mana ties.methodbisa salah satu "random" "first"atau"last"

Ini tentu saja menyebabkan masalah jika Anda memiliki dua kolom yang sama dengan maksimum. Saya tidak yakin apa yang ingin Anda lakukan dalam hal itu karena Anda akan mendapatkan lebih dari satu hasil untuk beberapa baris. Misalnya:

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 
thelatemail
sumber
Jika saya memiliki dua kolom yang sama saya biasanya hanya memilih yang pertama. Ini adalah kasus perbatasan yang tidak mengganggu analisis statistik saya.
dmvianna
1
@dmvianna - menggunakan which.maxakan baik-baik saja.
thelatemail
Saya berasumsi pesanan dipertahankan, jadi saya bisa membuat kolom baru dengan vektor ini yang akan disejajarkan dengan benar ke ID karyawan. Apakah itu benar?
dmvianna
applymengonversi data.framemenjadi secara matrixinternal. Anda mungkin tidak melihat perbedaan kinerja pada dimensi ini.
Arun
2
@PankajKaundal - mengasumsikan nilai-nilai yang berbeda, bagaimana dengan inicolnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
thelatemail
15

Jika Anda tertarik dengan data.tablesolusinya, inilah satu. Agak rumit karena Anda lebih suka mendapatkan id untuk maksimum pertama. Jauh lebih mudah jika Anda lebih suka yang terakhir. Namun demikian, ini tidak terlalu rumit dan cepat!

Di sini saya telah menghasilkan data dimensi Anda (26746 * 18).

Data

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

data.table menjawab:

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

Pembandingan:

# 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

Ini sekitar 11 kali lebih cepat pada data dimensi ini, dan juga data.tableberskala cukup baik.


Edit: jika salah satu dari id maksimum tidak apa-apa, maka:

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"]
Arun
sumber
Saya sebenarnya tidak peduli apakah itu maksimum pertama atau terakhir. Saya mencari kesederhanaan dulu, tetapi saya yakin solusi data.table akan berguna di masa depan, terima kasih!
dmvianna
11

Salah satu solusinya adalah membentuk ulang tanggal dari lebar menjadi panjang dengan meletakkan semua departemen dalam satu kolom dan menghitung di kolom lain, mengelompokkan menurut id pemberi kerja (dalam hal ini, nomor baris), dan kemudian memfilter ke departemen dengan nilai maks. Ada beberapa opsi untuk menangani hubungan dengan pendekatan ini juga.

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.
sbha
sumber
11

Berdasarkan saran di atas, data.tablesolusi berikut bekerja sangat cepat untuk saya:

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

Dan juga dilengkapi dengan keuntungan yang selalu dapat menentukan kolom apa yang .SDharus dipertimbangkan dengan menyebutkannya di .SDcols:

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

Jika kita membutuhkan nama kolom dengan nilai terkecil, seperti yang disarankan oleh @lwshang, kita hanya perlu menggunakan -.SD:

DT[, col_min := colnames(.SD)[max.col(-.SD, ties.method = "first")]]
Valentin
sumber
Saya memiliki persyaratan yang serupa tetapi ingin mendapatkan nama kolom yang memiliki nilai minimum untuk setiap baris ..... sepertinya kami tidak memiliki min.col di R ..... tahukah Anda apa yang akan menjadi solusi yang setara ?
pengguna1412
Hai @ user1412. Terima kasih atas pertanyaan menarik Anda. Saya tidak tahu apa-apa sekarang selain menggunakan which.mindalam sesuatu yang akan terlihat seperti: DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]atau DT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]pada data dummy di atas. Ini tidak mempertimbangkan ikatan dan hanya mengembalikan minimum pertama. Mungkin pertimbangkan untuk mengajukan pertanyaan terpisah. Saya juga ingin tahu jawaban lain apa yang akan Anda dapatkan.
Valentin
1
Sebuah trik untuk mendapatkan kolom minimum mengirimkan negatif data.frame ke max.col, seperti: colnames(.SD)[max.col(-.SD, ties.method="first")].
lwshang
6

Sebuah dplyrsolusi:

Ide:

  • tambahkan rowid sebagai kolom
  • membentuk kembali ke format panjang
  • filter untuk max di setiap grup

Kode:

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) 

Hasil:

# 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

Pendekatan ini dapat dengan mudah diperluas untuk mendapatkan nkolom teratas . Contoh untuk n=2:

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

Hasil:

# 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
Gregor Sturm
sumber
1
Bisakah Anda mengomentari perbedaan antara pendekatan ini dan jawaban sbha di atas? Mereka terlihat hampir sama bagiku.
Gregor Thomas
2

forLoop sederhana juga bisa berguna:

> 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
rar
sumber
2

Salah satu opsi dari dplyr 1.0.0bisa jadi:

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     

Contoh data:

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))
tmfmnk
sumber
0

Berikut adalah jawaban yang berfungsi dengan data.table dan lebih sederhana. Ini mengasumsikan data.table Anda diberi nama yourDF:

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

Ganti ("V1", "V2", "V3", "V4")dan (V1, V2, V3, V4)dengan nama kolom Anda

Mempelajari statistik dengan contoh
sumber
Bisakah seseorang membantu dengan bagaimana kita mengabaikan nilai NA jika ada di nilai kolom
Partha sarathi