148 votes

Pour chaque ligne, renvoyer le nom de la colonne de la valeur la plus grande.

Je dispose d'une liste d'employés et j'ai besoin de savoir dans quel département ils se trouvent le plus souvent. Il est trivial de tabuler l'ID de l'employé en fonction du nom du département, mais il est plus délicat de renvoyer le nom du département, plutôt que le nombre de comptages du fichier, à partir de la table de fréquence. Voici un exemple simple (noms des colonnes = départements, noms des lignes = ID des employés).

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

Maintenant, comment puis-je obtenir

> DF2
  RE
1 V3
2 V1
3 V2

143voto

thelatemail Points 21202

Une option utilisant vos données (pour référence future, utiliser set.seed() pour faire des exemples en utilisant sample reproductible) :

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"

Une solution plus rapide que l'utilisation de apply pourrait être max.col :

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

...où ties.method peut être l'un des éléments suivants "random" "first" o "last"

Cela pose bien sûr des problèmes si vous avez deux colonnes qui sont égales au maximum. Je ne suis pas sûr de ce que vous voulez faire dans ce cas, car vous aurez plus d'un résultat pour certaines lignes. Par exemple :

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

20voto

sbha Points 1948

Une solution pourrait être de remodeler la date de large à long en mettant tous les départements dans une colonne et les comptes dans une autre, de regrouper par l'id de l'employeur (dans ce cas, le numéro de ligne), puis de filtrer sur le(s) département(s) avec la valeur maximale. Il existe également plusieurs options pour gérer les liens avec cette approche.

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.

18voto

Valentin Points 1062

Sur la base des suggestions ci-dessus, les points suivants data.table La solution a fonctionné très rapidement pour moi :

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

Et a également l'avantage de pouvoir toujours spécifier quelles colonnes .SD devraient envisager en les mentionnant dans .SDcols :

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

Dans le cas où nous avons besoin du nom de la colonne de la plus petite valeur, comme suggéré par @lwshang, il suffit d'utiliser -.SD :

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

17voto

Arun Points 41689

Si vous êtes intéressé par un data.table solution, en voici une. C'est un peu délicat puisque vous préférez obtenir l'id pour le premier maximum. C'est beaucoup plus facile si vous préférez obtenir le dernier maximum. Néanmoins, ce n'est pas si compliqué et c'est rapide !

Ici j'ai généré des données de vos dimensions (26746 * 18).

Données

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

data.table réponse :

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

Benchmarking :

# 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

Il est environ 11 fois plus rapide sur des données de ces dimensions, et data.table L'échelle est également très bonne.


Edit : si n'importe lequel des ids max est ok, alors :

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

17voto

tmfmnk Points 8978

Une option parmi dplyr 1.0.0 pourrait être :

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     

Dans certains contextes, il pourrait être plus sûr d'utiliser pmap() (nécessite purrr ) :

DF %>%
    mutate(row_max = pmap(across(everything()), ~ names(c(...)[which.max(c(...))])))

Les données de l'échantillon :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X