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
Jawaban:
Satu opsi menggunakan data Anda (untuk referensi di masa mendatang, gunakan
set.seed()
untuk membuat contoh menggunakan dapatsample
direproduksi):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
apply
mungkinmax.col
:colnames(DF)[max.col(DF,ties.method="first")] #[1] "V3" "V1" "V2"
... di mana
ties.method
bisa 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
sumber
which.max
akan baik-baik saja.apply
mengonversidata.frame
menjadi secaramatrix
internal. Anda mungkin tidak melihat perbedaan kinerja pada dimensi ini.colnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
Jika Anda tertarik dengan
data.table
solusinya, 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.table
berskala 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"]
sumber
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.
sumber
Berdasarkan saran di atas,
data.table
solusi 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
.SD
harus 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")]]
sumber
which.min
dalam sesuatu yang akan terlihat seperti:DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]
atauDT[, 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.colnames(.SD)[max.col(-.SD, ties.method="first")]
.Sebuah
dplyr
solusi:Ide:
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
n
kolom teratas . Contoh untukn=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
sumber
for
Loop 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
sumber
Salah satu opsi dari
dplyr 1.0.0
bisa 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))
sumber
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 Andasumber