Fastest/Simplest Algorithm/Function 确定三个值中的最大值

The Fastest/Simplest Algorithm/Function to Determine Largest of Three Values

这是一个 非常 的基本编程问题,但为了将来,我只想知道哪种方法是处理这种常见情况的最佳方法。

我有 0 到 10 之间不同级别的三列,我想确定其中哪一个具有最高值,并显示列的名称(在变异列中或以其他方式创建 'Largest'列。)如果有任何关系,我更喜欢 c 而不是 b 而不是列,因为此开关将用于从其他列中提取值,这些值可能与这些列不同。

下面的代码可以解决问题,但是有更短更简单的方法吗?

set.seed(7)
mat <- matrix(as.integer(runif(15, 0, 10)), nrow = 5, ncol = 3)
colnames(mat) <- letters[1:3]
(mat)

matBestOf <- 
    data.frame(mat) %>% 
    mutate(Largest = ifelse(c >= b & c >= a, "c",
                     ifelse(b >= c & b >= a, "b",
                     "a"))
           )
matBestOf
#   a b c Largest
# 1 9 7 1       a
# 2 3 3 2       b
# 3 1 9 7       b
# 4 0 1 0       b
# 5 2 4 4       c

我尝试使用 max() 函数,但我只得到 return 最高值,而不是具有最高值的列名。此外,我显然没有比较所有三列的值,因为结果只是来自 ac 的最佳结果,而且永远不会b。此外,我似乎不喜欢大写字母,这没关系,也许没有那个附加功能我也可以生活。

matBestOf <- 
    data.frame(mat) %>% 
    rowwise %>% 
    mutate(Largest = max(a:c))
matBestOf
# Source: local data frame [5 x 4]
# Groups: <by row>
#
#       a     b     c Largest
#   (int) (int) (int)   (int)
# 1     9     7     1       9
# 2     3     3     2       3
# 3     1     9     7       7
# 4     0     1     0       0
# 5     2     4     4       4

这里有一个选项 max.col:

mat %>% 
  data.frame() %>%
  mutate(Largest = names(.)[max.col(., ties.method = "last")])

#  c b a Largest
#1 1 7 9       a
#2 2 3 3       b
#3 7 9 1       b
#4 0 1 0       b
#5 4 4 2       c

我使用 select 按照您指定的顺序放置列,这样我们就可以简单地使用 ties.method = "first"everything() 确保其他列(如果存在)也将被选中,但出现在前三列之后。

使用 applyrev 使 c 优先于 b 优先于 a:

cbind.data.frame(mat,
      Largest = apply(mat, 1,
                      function(i)rev(colnames(mat))[rev(i) == max(i)][1]))
#   a b c Largest
# 1 9 7 1       a
# 2 3 3 2       b
# 3 1 9 7       b
# 4 0 1 0       b
# 5 2 4 4       c

编辑: 基准测试

将 rev 放在 apply 之外会使代码在更大的数据上快 3-4 倍,但仍然不如 dplyr 解决方案快。

library(dplyr)

# bigger dummy data
bigmat <- matrix(rep(mat, 10000), ncol = 20)
colnames(bigmat) <- letters[1:ncol(bigmat)]


microbenchmark::microbenchmark(
  dplyr = {bigmat %>% 
      data.frame() %>% 
      select(c,b,a, everything()) %>%
      mutate(Largest = names(.)[max.col(., ties.method = "first")])},
  base_apply_v1 = {
    cbind.data.frame(bigmat,
                     Largest = apply(bigmat, 1,
                                     function(i)rev(colnames(bigmat))[rev(i) == max(i)][1]))
  },
  base_apply_v2 = {
    myFlip <- bigmat[nrow(bigmat):1, ncol(bigmat):1]
    myNames <- colnames(myFlip)
    cbind.data.frame(bigmat,
                     Largest = apply(myFlip, 1,
                                     function(i)myNames[i == max(i)][1]))
  }
  )

# Unit: milliseconds
#           expr       min       lq      mean    median        uq        max neval cld
#          dplyr  3.271673  3.52583  4.665696  3.730951  5.915583   8.405259   100 a  
#  base_apply_v1 86.191320 91.94412 99.370839 93.709812 96.214598 196.007909   100   c
#  base_apply_v2 23.121803 26.70536 30.906054 28.042854 29.065466 134.257780   100  b 

这是一个使用data.table

的选项
library(data.table)
as.data.table(mat)[, Largest := rev(colnames(mat))[which.max(rev(unlist(.SD)))] , 1:nrow(mat)][]
#    a b c Largest
#1: 9 7 1       a
#2: 3 3 2       b
#3: 1 9 7       b
#4: 0 1 0       b
#5: 2 4 4       c