各行について、最大値の列名を返します


97

私には従業員の名簿があり、どの部門に最も頻繁にいるかを知る必要があります。部門名に対して従業員IDを集計するのは簡単ですが、頻度表から名簿の数ではなく部門名を返すのはより複雑です。以下の簡単な例(列名=部門、行名=従業員ID)。

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

今私はどうやって得るのですか

> DF2
  RE
1 V3
2 V1
3 V2

実際のデータの大きさは?
2013

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

6
興味深い一般化は、行ごとの最大n値の列名です
Hack-R

回答:


99

データを使用する1つのオプション(将来の参照のためset.seed()に、sample再現性を使用して例を作成するために使用します):

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"

使用するよりも早く解決策はapplyあるかもしれませんmax.col

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

... またはのties.methodいずれかになります"random" "first""last"

もちろん、最大に等しい2つの列がある場合、これにより問題が発生します。一部の行では複数の結果が得られるため、そのインスタンスで何をしたいのかわかりません。例えば:

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 

2つの等しい列がある場合、通常は最初の列を選択します。これらは、私の統計分析を混乱させない国境のケースです。
dmvianna 2013

1
@dmvianna-使用which.maxすると問題ありません。
thelatemail 2013

順序が保持されることを想定しているため、このベクトルを使用して新しい列を作成し、従業員IDに正しく位置合わせすることができます。あれは正しいですか?
dmvianna 2013

applydata.framematrix内部的に変換します。ただし、これらのディメンションではパフォーマンスの違いが見られない場合があります。
2013

2
@PankajKaundal-明確な値を想定して、これはどうcolnames(DF)[max.col(replace(DF, cbind(seq_len(nrow(DF)), max.col(DF,ties.method="first")), -Inf), "first")]
ですか

15

data.table解決策に興味がある場合は、こちらをご覧ください。最初の最大値のIDを取得したいので、少し注意が必要です。最後の最大値が必要な場合は、はるかに簡単です。それにもかかわらず、それはそれほど複雑ではなく、高速です!

ここに私はあなたの次元のデータを生成しました(26746 * 18)。

データ

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

data.table 回答:

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

ベンチマーク:

# 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

これらのディメンションのデータでは、約11倍高速であり、data.tableスケーリングもかなり良好です。


編集:最大IDのいずれかに問題がない場合は、次のようにします。

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

それが最初の最大値であるか最後の最大値であるかは、実際には気にしません。最初に簡単にするつもりですが、data.tableソリューションが将来役立つと確信しています。
dmvianna 2013

11

1つの解決策は、すべての部署を1つの列に入れて別の列に数え、日付を広いものから長いものに変更し、雇用主ID(この場合は行番号)でグループ化してから、最大値。このアプローチとの関係を処理するためのオプションもいくつかあります。

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

上記の提案に基づいて、次のdata.table解決策は私にとって非常に速く機能しました:

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

また、列.SDで言及することにより、どの列を考慮すべきかを常に指定できるという利点もあります.SDcols

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

@lwshangで提案されているように、最小値の列名が必要な場合は、次のように使用するだけ-.SDです。

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

同様の要件がありましたが、各行の最小値を持つ列名を取得したいのですが、Rにmin.colがないようです。 ?
user1412 2017

@ user1412さん、こんにちは。興味深い質問をありがとう。私は今のwhich.minように見えるもので使用すること以外に何も考えていません:DT[, MIN := colnames(.SD)[apply(.SD,1,which.min)]]またはDT[, MIN2 := colnames(.SD)[which.min(.SD)], by = 1:nrow(DT)]上記のダミーデータで。これはタイを考慮せず、最初の最小値のみを返します。別の質問をすることを検討してください。他にどのような回答が得られるかについても知りたいです。
Valentin

1
最小列を取得するトリックは、次のようにdata.frameの負の値をmax.colに送信することですcolnames(.SD)[max.col(-.SD, ties.method="first")]
lwshang 2018年

6

dplyr解決策:

考え:

  • 列としてROWIDを追加する
  • 長い形式に再形成する
  • 各グループの最大のフィルター

コード:

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) 

結果:

# 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

このアプローチは、上位のn列を取得するために簡単に拡張できます。の例n=2

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

結果:

# 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
このアプローチと上記のsbhaの答えの違いについてコメントしていただけますか?彼らは私にはほぼ同じように見えます。
グレゴールトーマス

2

単純なforループも便利です。

> 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

1

からの1つのオプションはdplyr 1.0.0次のとおりです。

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     

サンプルデータ:

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

これはdata.tableで機能し、より簡単な答えです。これは、data.tableが次の名前であると想定していますyourDF

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

交換する("V1", "V2", "V3", "V4")(V1, V2, V3, V4)、あなたの列名を持ちます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.