Geben Sie für jede Zeile den Spaltennamen des größten Werts zurück

94

Ich habe eine Liste von Mitarbeitern, und ich muss wissen, in welcher Abteilung sie sich am häufigsten befinden. Es ist trivial, die Mitarbeiter-ID anhand des Abteilungsnamens zu tabellieren, aber es ist schwieriger, den Abteilungsnamen und nicht die Anzahl der Dienstplanzählungen aus der Häufigkeitstabelle zurückzugeben. Ein einfaches Beispiel unten (Spaltennamen = Abteilungen, Zeilennamen = Mitarbeiter-IDs).

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

Wie komme ich jetzt?

> DF2
  RE
1 V3
2 V1
3 V2
dmvianna
quelle
Wie groß sind Ihre tatsächlichen Daten?
Arun
1
@Arun> dim (Test) [1] 26746 18
dmvianna
6
Eine interessante Verallgemeinerung wären die Spaltennamen der größten n Werte pro Zeile
Hack-R

Antworten:

98

Eine Option, bei der Ihre Daten verwendet werden (zum späteren Nachschlagen verwenden Sie diese set.seed(), um Beispiele mithilfe samplereproduzierbarer Daten zu erstellen ):

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"

Eine schnellere Lösung als die Verwendung applykönnte sein max.col:

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

... wo ties.methodkann einer von "random" "first"oder sein"last"

Dies verursacht natürlich Probleme, wenn Sie zufällig zwei Spalten haben, die dem Maximum entsprechen. Ich bin nicht sicher, was Sie in diesem Fall tun möchten, da Sie für einige Zeilen mehr als ein Ergebnis haben. Z.B:

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 
die E-Mail
quelle
Wenn ich zwei gleiche Spalten habe, wähle ich normalerweise nur die erste aus. Dies sind Grenzfälle, die meine statistische Analyse nicht stören.
dmvianna
1
@dmvianna - Verwendung which.maxwird dann in Ordnung sein.
E-Mail
Ich gehe davon aus, dass die Reihenfolge beibehalten wird, damit ich mit diesem Vektor eine neue Spalte erstellen kann, die korrekt an den Mitarbeiter-IDs ausgerichtet ist. Ist das korrekt?
dmvianna
applykonvertiert das data.framein matrixintern. Möglicherweise sehen Sie bei diesen Dimensionen jedoch keinen Leistungsunterschied.
Arun
2
@PankajKaundal - unter der Annahme unterschiedlicher Werte, wie wäre es damitcolnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
E-Mail
15

Wenn Sie an einer data.tableLösung interessiert sind, finden Sie hier eine. Es ist etwas schwierig, da Sie es vorziehen, die ID für das erste Maximum zu erhalten. Es ist viel einfacher, wenn Sie lieber das letzte Maximum wollen. Trotzdem ist es nicht so kompliziert und schnell!

Hier habe ich Daten Ihrer Dimensionen generiert (26746 * 18).

Daten

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

data.table Antworten:

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

Bei Daten dieser Dimensionen ist es ungefähr elfmal schneller und lässt sich auch data.tableziemlich gut skalieren.


Bearbeiten: Wenn eine der maximalen IDs in Ordnung ist, dann:

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
quelle
Es ist mir eigentlich egal, ob es das erste oder letzte Maximum ist. Ich werde mich zunächst der Einfachheit widmen, aber ich bin sicher, dass eine data.table-Lösung in Zukunft nützlich sein wird, danke!
dmvianna
11

Eine Lösung könnte darin bestehen, das Datum von breit auf lang umzustellen, alle Abteilungen in eine Spalte zu setzen und in einer anderen zu zählen, nach der Arbeitgeber-ID (in diesem Fall der Zeilennummer) zu gruppieren und dann mit dem zu den Abteilungen zu filtern Maximalwert. Es gibt auch einige Optionen für den Umgang mit Verbindungen mit diesem Ansatz.

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
quelle
10

Basierend auf den obigen Vorschlägen hat die folgende data.tableLösung für mich sehr schnell funktioniert:

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

Und hat auch den Vorteil, dass Sie immer angeben können, welche Spalten .SDberücksichtigt werden sollen, indem Sie sie erwähnen in .SDcols:

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

Wenn wir den Spaltennamen des kleinsten Werts benötigen, wie von @lwshang vorgeschlagen, muss man nur Folgendes verwenden -.SD:

DT[, col_min := colnames(.SD)[max.col(-.SD, ties.method = "first")]]
Valentin
quelle
Ich hatte eine ähnliche Anforderung, möchte aber den Spaltennamen mit dem Mindestwert für jede Zeile erhalten ..... wir scheinen nicht min.col in R zu haben ..... würden Sie wissen, was die äquivalente Lösung wäre ?
user1412
Hi @ user1412. Vielen Dank für Ihre interessante Frage. Ich habe momentan keine andere Idee, als das which.minin etwas zu verwenden, das aussehen würde: DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]oder DT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]auf den Dummy-Daten oben. Dies berücksichtigt keine Bindungen und gibt nur das erste Minimum zurück. Vielleicht sollten Sie eine separate Frage stellen. Ich wäre auch neugierig, welche anderen Antworten Sie bekommen würden.
Valentin
1
Ein Trick, um die minimale Spalte zu erhalten, besteht darin, das Negativ des data.frame in max.col zu senden, wie : colnames(.SD)[max.col(-.SD, ties.method="first")].
Lwshang
6

Eine dplyrLösung:

Idee:

  • Fügen Sie Rowids als Spalte hinzu
  • in Langformat umformen
  • Filter für max in jeder Gruppe

Code:

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) 

Ergebnis:

# 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

Dieser Ansatz kann leicht erweitert werden, um die oberen nSpalten zu erhalten. Beispiel für n=2:

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

Ergebnis:

# 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
quelle
1
Könnten Sie den Unterschied zwischen diesem Ansatz und der obigen Antwort von sbha kommentieren? Sie sehen für mich ungefähr gleich aus.
Gregor Thomas
2

Eine einfache forSchleife kann auch nützlich sein:

> 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
quelle
0

Hier ist eine Antwort, die mit data.table funktioniert und einfacher ist. Dies setzt voraus, dass Ihre data.table den Namen hat yourDF:

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

Ersetzen Sie ("V1", "V2", "V3", "V4")und (V1, V2, V3, V4)durch Ihre Spaltennamen

Statistiken anhand eines Beispiels lernen
quelle
0

Eine Option von dplyr 1.0.0könnte sein:

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     

Beispieldaten:

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
quelle