另一个关于效率的答案(尽管此 QA 与速度无关)。
首先,最好避免将“列表”-y 结构转换为“矩阵”;有时值得转换为“矩阵”并使用有效处理“具有“暗淡”属性的向量(即“矩阵”/“数组”)的函数 - 其他时候则不然。 max.col 和 apply 都转换为“矩阵”。
其次,在这样的情况下,我们不需要在获得解决方案时检查所有数据,我们可以从具有控制下一次迭代的循环的解决方案中受益。在这里,我们知道当我们找到第一个“1”时我们可以停止。 max.col(和which.max)都必须循环一次才能找到最大值;我们知道“max == 1”没有被利用这一事实。
第三,match 在我们只在另一个值向量中寻找一个值时可能会变慢,因为match 的设置相当复杂且成本高:
x = 5; set.seed(199); tab = sample(1e6)
identical(match(x, tab), which.max(x == tab))
#[1] TRUE
microbenchmark::microbenchmark(match(x, tab), which.max(x == tab), times = 25)
#Unit: milliseconds
# expr min lq median uq max neval
# match(x, tab) 142.22327 142.50103 142.79737 143.19547 145.37669 25
# which.max(x == tab) 18.91427 18.93728 18.96225 19.58932 38.34253 25
总而言之,一种处理“data.frame”的“列表”结构并在找到“1”时停止计算的方法可能是如下所示的循环:
ff = function(x)
{
x = as.list(x)
ans = as.integer(x[[1]])
for(i in 2:length(x)) {
inds = ans == 0L
if(!any(inds)) return(ans)
ans[inds] = i * (x[[i]][inds] == 1)
}
return(ans)
}
以及其他答案中的解决方案(忽略输出的额外步骤):
david = function(x) max.col(x, "first")
plafort = function(x) apply(x, 1, match, x = 1)
ff(df[-1])
#[1] 1 3 4 1
david(df[-1])
#[1] 1 3 4 1
plafort(df[-1])
#[1] 1 3 4 1
还有一些基准测试:
set.seed(007)
DF = data.frame(id = seq_len(1e6),
"colnames<-"(matrix(sample(0:1, 1e7, T, c(0.25, 0.75)), 1e6),
paste("in", 11:20, sep = "")))
identical(ff(DF[-1]), david(DF[-1]))
#[1] TRUE
identical(ff(DF[-1]), plafort(DF[-1]))
#[1] TRUE
microbenchmark::microbenchmark(ff(DF[-1]), david(DF[-1]), as.matrix(DF[-1]), times = 30)
#Unit: milliseconds
# expr min lq median uq max neval
# ff(DF[-1]) 64.83577 65.45432 67.87486 70.32073 86.72838 30
# david(DF[-1]) 112.74108 115.12361 120.16118 132.04803 145.45819 30
# as.matrix(DF[-1]) 20.87947 22.01819 27.52460 32.60509 45.84561 30
system.time(plafort(DF[-1]))
# user system elapsed
# 4.117 0.000 4.125
并不是真正的末日,但值得一看的是,简单、直接的算法方法可以 - 确实 - 证明同样好,甚至更好,具体取决于问题。显然,(大多数)其他时候在 R 中循环可能很费力。