Mam listę pracowników i muszę wiedzieć, w jakim dziale są najczęściej. Tabelaryczne zestawienie identyfikatora pracownika z nazwą działu jest trywialne, ale zwrócenie z tabeli częstotliwości nazwy działu zamiast liczby jego spisów jest trudniejsze. Prosty przykład poniżej (nazwy kolumn = działy, nazwy wierszy = identyfikatory pracowników).
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
Teraz, jak to się stanie
> DF2
RE
1 V3
2 V1
3 V2
Odpowiedzi:
Jedna opcja wykorzystująca Twoje dane (do wykorzystania w przyszłości, użyj
set.seed()
przykładów z wykorzystaniemsample
odtwarzalnych):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"
Szybszym rozwiązaniem niż używanie
apply
może byćmax.col
:colnames(DF)[max.col(DF,ties.method="first")] #[1] "V3" "V1" "V2"
... gdzie
ties.method
może być dowolny z"random"
"first"
lub"last"
To oczywiście powoduje problemy, jeśli masz dwie kolumny, które są równe maksimum. Nie jestem pewien, co chcesz zrobić w tym przypadku, ponieważ w niektórych wierszach będziesz mieć więcej niż jeden wynik. Na przykład:
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
źródło
which.max
będzie wtedy w porządku.apply
przekształcadata.frame
sięmatrix
wewnętrznie. Możesz jednak nie zauważyć różnicy w wydajności tych wymiarów.colnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
Jeśli interesuje Cię
data.table
rozwiązanie, oto jedno. Jest to trochę trudne, ponieważ wolisz uzyskać identyfikator dla pierwszego maksimum. O wiele łatwiej jest, jeśli wolisz ostatnie maksimum. Niemniej jednak nie jest to takie skomplikowane i jest szybkie!Tutaj wygenerowałem dane o Twoich wymiarach (26746 * 18).
Dane
set.seed(45) DF <- data.frame(matrix(sample(10, 26746*18, TRUE), ncol=18))
data.table
odpowiedź: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
Dane o tych wymiarach są około 11 razy szybsze i
data.table
całkiem nieźle się skalują.Edycja: jeśli którykolwiek z maksymalnych identyfikatorów jest w porządku, to:
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"]
źródło
Jednym z rozwiązań może być zmiana kształtu daty z szerokiej na długą, umieszczenie wszystkich działów w jednej kolumnie i zliczanie w innej, pogrupowanie według identyfikatora pracodawcy (w tym przypadku numeru wiersza), a następnie przefiltrowanie do działu (ów) za pomocą funkcji maksymalna wartość. Istnieje również kilka opcji radzenia sobie z powiązaniami z tym podejściem.
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.
źródło
Bazując na powyższych sugestiach
data.table
bardzo szybko działało dla mnie następujące rozwiązanie: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
Ma też tę zaletę, że zawsze można określić, które kolumny
.SD
należy wziąć pod uwagę, wymieniając je w.SDcols
:DT[, MAX2 := colnames(.SD)[max.col(.SD, ties.method="first")], .SDcols = c("V9", "V10")]
W przypadku, gdy potrzebujemy nazwy kolumny o najmniejszej wartości, jak sugeruje @lwshang, wystarczy użyć
-.SD
:DT[, col_min := colnames(.SD)[max.col(-.SD, ties.method = "first")]]
źródło
which.min
w czymś, co by wyglądało:DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]
lubDT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]
na powyższych fikcyjnych danych. Nie uwzględnia to remisów i zwraca tylko pierwsze minimum. Może rozważ zadanie osobnego pytania. Byłbym również ciekawy, jakie inne odpowiedzi byś uzyskał.colnames(.SD)[max.col(-.SD, ties.method="first")]
.dplyr
Rozwiązanie:Pomysł:
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)
Wynik:
# 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
To podejście można łatwo rozszerzyć, aby uzyskać górne
n
kolumny. Przykład dlan=2
:DF %>% rownames_to_column() %>% gather(column, value, -rowname) %>% group_by(rowname) %>% mutate(rk = rank(-value)) %>% filter(rk <= 2) %>% arrange(rowname, rk)
Wynik:
# 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
źródło
for
Przydatna może być również prosta pętla:> 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
źródło
Jedną z opcji
dplyr 1.0.0
może być: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
Przykładowe dane:
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))
źródło
Oto odpowiedź, która działa z data.table i jest prostsza. Zakładamy, że twoja data.table ma nazwę
yourDF
:j1 <- max.col(yourDF[, .(V1, V2, V3, V4)], "first") yourDF$newCol <- c("V1", "V2", "V3", "V4")[j1]
Zastąp
("V1", "V2", "V3", "V4")
i(V1, V2, V3, V4)
nazwami kolumnźródło