并行化是加速这一过程的一种自然方式。它可以通过以下方式在 C 级别完成data.table
:
library("data.table")
data.table 1.14.2 using 4 threads (see ?getDTthreads). Latest news: r-datatable.com
set.seed(1L)
x <- as.data.frame(replicate(2L, sample.int(100L, size = 1e+06L, replace = TRUE), simplify = FALSE))
y <- as.data.table(x)
microbenchmark::microbenchmark(duplicated(x), duplicated(y), times = 1000L)
Unit: milliseconds
expr min lq mean median uq max neval
duplicated(x) 449.27693 596.242890 622.160423 625.610267 644.682319 734.39741 1000
duplicated(y) 5.75722 6.347518 7.413925 6.874593 7.407695 58.12131 1000
这里的基准测试表明,当应用于 a而不是等效的数据帧duplicated
时,速度要快得多。data.table
当然,快多少取决于您可以使用的 CPU 数量data.table
(请参阅 参考资料?setDTthreads
)。
如果你走这data.table
条路,那么你会像这样处理你的 17 个数据帧:
nduped <- function(ffd) {
x <- as.data.frame(ffd[c("chr", "pos")])
setDT(x)
n <- sum(duplicated(x))
rm(x)
gc(FALSE)
n
}
vapply(list_of_ffd, nduped, 0L)
在这里,我们使用setDT
而不是as.data.table
执行从数据帧到 的就地强制转换data.table
,并且我们使用rm
and在将另一个数据帧读入内存之前gc
释放所占用的内存。x
如果出于某种原因,data.table
这不是一个选项,那么您可以坚持使用duplicated
数据帧的方法,即duplicated.data.frame
. 它没有在 C 级别并行化,因此您需要在 R 级别进行并行化,例如,mclapply
将 17 个数据帧分配给批次并并行处理这些批次:
nduped <- function(ffd) {
x <- as.data.frame(ffd[c("chr", "pos")])
n <- sum(duplicated(x))
rm(x)
gc(FALSE)
n
}
unlist(parallel::mclapply(list_of_ffd, nduped, ...))
此选项速度较慢,并且消耗的内存比您预期的要多。幸运的是,还有优化的空间。该答案的其余部分重点介绍了一些主要问题以及解决这些问题的方法。如果您已经确定了,请随时停止阅读data.table
。
由于您有 18 个 CPU,因此您可以尝试同时处理所有 17 个数据帧,但由于一次将所有 17 个数据帧读入内存,您可能会遇到内存不足的问题。增加批量大小(即,将 17 个作业分布在少于 17 个 CPU 上)应该会有所帮助。
由于您的 17 个数据帧的长度(行数)差异很大,因此将它们随机分配到大小大致相同的批次可能不是一个好策略。您可以通过将较短的数据帧组合在一起而不是将较长的数据帧组合在一起来减少整体运行时间。mclapply
有一个affinity.list
参数给你这个控制。理想情况下,每批应该需要相同的处理时间。
每个作业使用的内存量实际上至少是存储数据帧所需量的两倍x
,因为duplicated.data.frame
复制了它的参数:
x <- data.frame(chr = rep(1:2, times = 5L), pos = rep(1:2, each = 5L))
tracemem(x)
[1] "<0x14babad48>"
invisible(duplicated(x))
tracemem[0x14babad48 -> 0x14babc088]: as.list.data.frame as.list vapply duplicated.data.frame duplicated
复制发生在vapply
方法主体的调用内部:
duplicated.data.frame
function (x, incomparables = FALSE, fromLast = FALSE, ...)
{
if (!isFALSE(incomparables))
.NotYetUsed("incomparables != FALSE")
if (length(x) != 1L) {
if (any(i <- vapply(x, is.factor, NA)))
x[i] <- lapply(x[i], as.numeric)
duplicated(do.call(Map, `names<-`(c(list, x), NULL)),
fromLast = fromLast)
}
else duplicated(x[[1L]], fromLast = fromLast, ...)
}
<bytecode: 0x15b44f0f0>
<environment: namespace:base>
这个vapply
电话是完全可以避免的:你应该已经知道是否chr
和pos
是因素。我建议定义一个替代品duplicated.data.frame
,只做给定您的用例所必需的。例如,如果您知道chr
并且pos
不是因素,那么您可以分配
duped <- function(x) {
duplicated.default(do.call(Map, `names<-`(c(list, x), NULL)))
}
并计算sum(duped(x))
而不是sum(duplicated(x))
. 事实上,您可以通过替换list
为c
:
fastduped <- function(x) {
duplicated.default(do.call(Map, `names<-`(c(c, x), NULL)))
}
在此处使用c
会导致将数据帧的行x
作为原子向量而不是列表进行存储和比较。换句话说,fastduped(x)
正在做
duplicated.default(<length-'m' list of length-'n' atomic vectors>)
而duped(x)
正在做
duplicated.default(<length-'m' list of length-'n' lists of length-1 atomic vectors>)
哪里m = nrow(x)
和n = length(x)
。后者速度较慢,消耗更多内存,?duplicated
说多了有一个警告:
将其用于列表可能会很慢,尤其是当元素不是原子向量(参见“向量”)或仅在属性上有所不同时。在最坏的情况下,它是 O(n^2)。
计算sum(fastduped(x))
而不是sum(duplicated(x))
应该增加您可以同时处理而不会耗尽内存的数据帧的数量。FWIW,这是一个比较 , 运行时间的基准测试duplicated
(duped
只字不提fastduped
内存使用情况):
set.seed(1L)
x <- as.data.frame(replicate(2L, sample.int(100L, size = 1e+06L, replace = TRUE), simplify = FALSE))
microbenchmark::microbenchmark(duplicated(x), duped(x), fastduped(x), times = 1000L)
Unit: milliseconds
expr min lq mean median uq max neval
duplicated(x) 521.7263 598.9353 688.7286 628.8813 769.6100 1324.458 1000
duped(x) 521.3863 598.7390 682.1298 627.1445 764.7331 1373.712 1000
fastduped(x) 431.0359 528.6613 594.1534 553.7739 609.6241 1123.542 1000