Her satır için en büyük değerin sütun adını döndür


100

Çalışanlardan oluşan bir listem var ve en çok hangi departmanda olduklarını bilmem gerekiyor. Çalışan kimliğini departman adına göre tablo haline getirmek önemsizdir, ancak sıklık tablosundan görev listesi sayımlarının sayısı yerine departman adını döndürmek daha zordur. Aşağıdaki basit bir örnek (sütun adları = departmanlar, satır adları = çalışan kimlikleri).

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

Şimdi nasıl edinirim

> DF2
  RE
1 V3
2 V1
3 V2

gerçek verileriniz ne kadar büyük?
Arun

1
@Arun> dim (test) [1] 26746 18
dmvianna

6
İlginç bir genelleme, satır başına en büyük n değer sütun adları olacaktır
Hack-R

Yanıtlar:


103

Verilerinizi kullanan bir seçenek (ileride set.seed()başvurmak için , sampletekrarlanabilir kullanarak örnekler oluşturmak için kullanın ):

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"

Kullanmaktan daha hızlı bir çözüm applyşunlar olabilir max.col:

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

... nerede ties.methodolabilir "random" "first"veya"last"

Bu elbette, maksimuma eşit iki sütununuz varsa sorunlara neden olur. Bazı satırlar için birden fazla sonuca sahip olacağınız için bu durumda ne yapmak istediğinizden emin değilim. Örneğin:

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 

İki eşit sütunum varsa, genellikle ilkini seçerim. Bunlar, istatistiksel analizimi bozmayan sınır davaları.
dmvianna

1
@dmvianna - which.maxo halde kullanmak iyi olacak.
posta

Siparişin korunduğunu varsayıyorum, böylece bu vektörle çalışanların kimlikleriyle doğru şekilde hizalanacak yeni bir sütun oluşturabilirim. Bu doğru mu?
dmvianna

applydata.framedeğerini matrixdahili olarak olarak dönüştürür . Yine de bu boyutlarda bir performans farkı göremeyebilirsiniz.
Arun

2
@PankajKaundal - farklı değerler varsayarsak, buna ne dersincolnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
thelatemail

15

Bir data.tableçözümle ilgileniyorsanız , işte bir tane. İlk maksimum için kimliği almayı tercih ettiğiniz için biraz aldatıcı. Son maksimumu tercih ederseniz çok daha kolay. Yine de, o kadar karmaşık değil ve hızlı!

Burada boyutlarınızın verilerini oluşturdum (26746 * 18).

Veri

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

data.table Cevap:

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

Kıyaslama:

# 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

Bu boyutların verilerinde yaklaşık 11 kat daha hızlıdır ve data.tableoldukça iyi ölçeklenir.


Düzenleme: Maksimum kimliklerden herhangi biri uygunsa, o zaman:

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

Aslında ilk veya son maksimum olması umrumda değil. Önce basitlik için gidiyorum, ancak eminim bir data.table çözümü gelecekte kullanışlı olacaktır, teşekkürler!
dmvianna

11

Bir çözüm, tarihi genişten uzuna yeniden şekillendirmek, tüm departmanları bir sütuna ve diğerine saymak, işveren kimliğine göre gruplamak (bu durumda, satır numarası) ve ardından departman (lar) a maksimum değer. Bu yaklaşımla bağların üstesinden gelmek için de birkaç seçenek var.

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

Yukarıdaki önerilere dayanarak, aşağıdaki data.tableçözüm benim için çok hızlı çalıştı:

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

Ayrıca, hangi sütunların .SDgöz önünde bulundurulması gerektiğini her zaman belirleyebilen avantajla birlikte gelir .SDcols:

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

@Lwshang tarafından önerildiği gibi, en küçük değerin sütun adına ihtiyacımız olması durumunda, birinin kullanılması gerekir -.SD:

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

Benzer bir gereksinimim vardı, ancak her satır için minimum değere sahip sütun adını almak istiyorum ..... R'de min.col yok gibi görünüyor ..... eşdeğer çözümün ne olacağını biliyor musunuz ?
user1412

Merhaba @ user1412. İlginç sorunuz için teşekkürler. Şu anda which.minin'i benzer bir şey DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]veya DT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]yukarıdaki sahte verilerde kullanmak dışında hiçbir fikrim yok . Bu, bağları dikkate almaz ve yalnızca ilk minimum olanı döndürür. Belki ayrı bir soru sormayı düşünebilirsiniz. Başka ne cevaplar alacağınızı da merak ediyorum.
Valentin

1
Bir hile asgari sütun gibi max.col içine data.frame negatifini gönderiyor almak için: colnames(.SD)[max.col(-.SD, ties.method="first")].
lwshang

6

Bir dplyrçözüm:

Fikir:

  • Rowids'i sütun olarak ekle
  • uzun biçime yeniden şekillendir
  • her grupta maksimum için filtre

Kod:

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) 

Sonuç:

# 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

Bu yaklaşım, üst nsütunları elde etmek için kolayca genişletilebilir . Örnek n=2:

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

Sonuç:

# 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
Bu yaklaşım ile sbha'nın yukarıdaki cevabı arasındaki fark hakkında yorum yapabilir misiniz? Bana hemen hemen aynı görünüyorlar.
Gregor Thomas

2

Basit bir fordöngü de kullanışlı olabilir:

> 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

Bir seçenek şunlar dplyr 1.0.0olabilir:

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     

Örnek veri:

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

İşte data.table ile çalışan ve daha basit bir cevap. Bu, data.table adınızın şu şekilde adlandırıldığını varsayar yourDF:

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

("V1", "V2", "V3", "V4")Ve (V1, V2, V3, V4)sütun adlarınızla değiştirin


Sütun değerlerinde mevcutsa NA değerlerini nasıl göz ardı edebileceğimiz konusunda biri yardımcı olabilir mi
Partha sarathi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.