我已更新我的答案以提供三种解决方案; fun2() 回想起来是最好的(最快、最强大、易于理解)的答案。
有各种 StackOverflow 帖子可用于查找第 n 个最高值,例如 https://stackoverflow.com/a/2453619/547331 。这是实现该解决方案的函数
nth <- function(x, nth_largest) {
n <- length(x) - (nth_largest - 1L)
sort(x, partial=n)[n]
}
将此应用于 data.frame 的每一(数字)行
data$nth <- apply(data[,-1], 1, nth, nth_largest = 4)
我做了一个大数据集
for (i in 1:20) data = rbind(data, data)
然后做了一些基本的计时
> system.time(apply(head(data[,-1], 1000), 1, nth, 4))
user system elapsed
0.012 0.000 0.012
> system.time(apply(head(data[,-1], 10000), 1, nth, 4))
user system elapsed
0.150 0.005 0.155
> system.time(apply(head(data[,-1], 100000), 1, nth, 4))
user system elapsed
1.274 0.005 1.279
> system.time(apply(head(data[,-1], 1000000), 1, nth, 4))
user system elapsed
14.847 0.095 14.943
所以它随着行数线性扩展(不足为奇...),每百万行大约 15 秒。
为了比较,我把这个解决方案写成
fun0 <-
function(df, nth_largest)
{
n <- ncol(df) - (nth_largest - 1L)
nth <- function(x)
sort(x, partial=n)[n]
apply(df, 1, nth)
}
用作fun0(data[,-1], 4)。
另一种策略是根据数值数据创建矩阵
m <- as.matrix(data[,-1])
然后对整个矩阵进行排序,将值的行索引排序
o <- order(m)
i <- row(m)[o]
然后对于最大的,次大的,...值,将每个行索引的最后一个值设置为NA;第 n 个最大值是最后一次出现的行索引
for (iter in seq_len(nth_largest - 1L))
i[!duplicated(i, fromLast = TRUE)] <- NA_integer_
idx <- !is.na(i) & !duplicated(i, fromLast = TRUE)
对应的值为m[o[idx]],按行顺序排列
m[o[idx]][order(i[idx])]
因此,另一种解决方案是
fun1 <-
function(df, nth_largest)
{
m <- as.matrix(df)
o <- order(m)
i <- row(m)[o]
for (idx in seq_len(nth_largest - 1L))
i[!duplicated(i, fromLast = TRUE)] <- NA_integer_
idx <- !is.na(i) & !duplicated(i, fromLast = TRUE)
m[o[idx]][order(i[idx])]
}
我们有
> system.time(res0 <- fun0(head(data[,-1], 1000000), 4))
user system elapsed
17.604 0.075 17.680
> system.time(res1 <- fun1(head(data[,-1], 1000000), 4))
user system elapsed
3.036 0.393 3.429
> identical(unname(res0), res1)
[1] TRUE
一般来说,当nth_largest 不太大时,fun1() 似乎会更快。
对于fun2(),将原始数据按行后值排序,只保留相关索引
fun2 <-
function(df, nth_largest)
{
m <- as.matrix(df)
o <- order(row(m), m)
idx <- seq(ncol(m) - (nth_largest - 1), by = ncol(m), length.out = nrow(m))
m[o[idx]]
}
有
> system.time(res1 <- fun1(head(data[, -1], 1000000), 4))
user system elapsed
2.948 0.406 3.355
> system.time(res2 <- fun2(head(data[, -1], 1000000), 4))
user system elapsed
0.316 0.062 0.379
> identical(res1, res2)
[1] TRUE
在完整数据集上分析fun2()
> dim(data)
[1] 6291456 13
> Rprof(); res2 <- fun2(data[, -1], 4); Rprof(NULL); summaryRprof()
$by.self
self.time self.pct total.time total.pct
"order" 1.50 63.56 1.84 77.97
"unlist" 0.36 15.25 0.36 15.25
"row" 0.34 14.41 0.34 14.41
"fun2" 0.10 4.24 2.36 100.00
"seq.default" 0.06 2.54 0.06 2.54
...
显示大部分时间都花在order();我不完全确定如何在多个因素上实现order(),但它可能具有与基数排序相关的复杂性。无论如何,它都非常快!