从大列表 (R) 中查找对之间交集的时间和内存有效方法
Time and memory efficient way to find intersection between pairs from a large list (R)
我有一个长度为 N 的列表,每个列表元素是从某个更大的组中采样的 10 个字符串。我想找到每对非常相似的元素,比如共同 >=8 个字符串。 我可以使用 crossprod
对 N<10,000 执行此操作,但我 运行 内存不足,无法使用更大的 N。
这是一个示例,其中 N=1000,产生 6 个非常相似的对:
# make list x
set.seed(1)
N = 1000
x = lapply(1:N, function(x) sample(letters, 10))
names(x) = as.character(1:length(x))
# find pairwise intersect, store in square matrix N_intersect
N_intersect = x %>% stack() %>% table() %>% crossprod()
diag(N_intersect) = 0
N_intersect[lower.tri(N_intersect)] = 0
# find when N_intersect is over some threshold
result = which(N_intersect > 8, arr.ind = T)
result
ind ind
111 111 375
705 705 708
48 48 771
317 317 797
566 566 883
705 705 958
但是由于crossprod
的输出是一个NxN矩阵,对于大N它很快就会耗尽内存。我知道crossprod有稀疏方法,但这似乎只减少了大约内存~ 30%.
事实是,高度相似的元素的数量通常非常少,比如 1/1000 对。所以 我不需要存储来自 crossprod 的大方阵,但我想不出可以快速完成此操作的内存高效方法。 我可以检查每一对for 循环中的元素,但这需要几个小时。
回答我自己的问题以防对任何人有帮助。
我发现了一种检测列表 x
中相似对的方法,它的运行时间与 crossprod
大致相同,但最重要的是它不会耗尽内存。我通过将输入输入到 crossprod x %>% stack() %>% table()
来做到这一点,这是一个 Nxm 矩阵,其中 m 是可能的字符数。上例中的 m=26,因为我使用的是字母。每行代表 x
中的一个列表元素,其中 1 表示元素中的字母,0 表示元素中不包含的字母。
然后我的问题是“哪些行对至少有 9 个共享列为 1?”我通过 1) 子集列和 2) rowSums 为每一行回答了这个问题。这个方法是 f.new
我把它和老方法比较 f.crossprod
:
f.crossprod = function(x, threshold) {
# find intersection of each pair elements with crossprod
# store in upper triangular square matrix
N_intersect = x %>% stack() %>% table() %>% crossprod()
diag(N_intersect) = 0
N_intersect[lower.tri(N_intersect)] = 0
# find when N_intersect is over some threshold
result = which(N_intersect >= threshold, arr.ind = T) %>%
as.data.frame() %>%
`colnames<-`(c("row", "col"))
rownames(result) = NULL
return(result)
}
f.new = function(x, threshold) {
x2 = t(table(stack(x)))
result = list()
for (ii in 3:nrow(x2)) {
ia = which(x2[ii,]>0)
cands = which(rowSums(x2[1:(ii-1),ia]) >= threshold)
result[[ii]] = data.frame(row=cands, col=rep(ii, length(cands)))
}
result = do.call(rbind, result) %>% filter(!row==col)
rownames(result) = NULL
return(result)
}
我可以看到它们 return 相同的结果:
set.seed(1)
N = 1000
x = lapply(1:N, function(x) sample(letters, 10))
names(x) = as.character(1:length(x))
r1 = f.new(x, 9)
r2 = f.crossprod(x, 9)
identical(r1, r2)
> TRUE
时间是相似的,尽管 f.new 对于 N>5000 来说稍微快一些。最重要的是 f.new 没有问题 N>20000:
我有一个长度为 N 的列表,每个列表元素是从某个更大的组中采样的 10 个字符串。我想找到每对非常相似的元素,比如共同 >=8 个字符串。 我可以使用 crossprod
对 N<10,000 执行此操作,但我 运行 内存不足,无法使用更大的 N。
这是一个示例,其中 N=1000,产生 6 个非常相似的对:
# make list x
set.seed(1)
N = 1000
x = lapply(1:N, function(x) sample(letters, 10))
names(x) = as.character(1:length(x))
# find pairwise intersect, store in square matrix N_intersect
N_intersect = x %>% stack() %>% table() %>% crossprod()
diag(N_intersect) = 0
N_intersect[lower.tri(N_intersect)] = 0
# find when N_intersect is over some threshold
result = which(N_intersect > 8, arr.ind = T)
result
ind ind
111 111 375
705 705 708
48 48 771
317 317 797
566 566 883
705 705 958
但是由于crossprod
的输出是一个NxN矩阵,对于大N它很快就会耗尽内存。我知道crossprod有稀疏方法,但这似乎只减少了大约内存~ 30%.
事实是,高度相似的元素的数量通常非常少,比如 1/1000 对。所以 我不需要存储来自 crossprod 的大方阵,但我想不出可以快速完成此操作的内存高效方法。 我可以检查每一对for 循环中的元素,但这需要几个小时。
回答我自己的问题以防对任何人有帮助。
我发现了一种检测列表 x
中相似对的方法,它的运行时间与 crossprod
大致相同,但最重要的是它不会耗尽内存。我通过将输入输入到 crossprod x %>% stack() %>% table()
来做到这一点,这是一个 Nxm 矩阵,其中 m 是可能的字符数。上例中的 m=26,因为我使用的是字母。每行代表 x
中的一个列表元素,其中 1 表示元素中的字母,0 表示元素中不包含的字母。
然后我的问题是“哪些行对至少有 9 个共享列为 1?”我通过 1) 子集列和 2) rowSums 为每一行回答了这个问题。这个方法是 f.new
我把它和老方法比较 f.crossprod
:
f.crossprod = function(x, threshold) {
# find intersection of each pair elements with crossprod
# store in upper triangular square matrix
N_intersect = x %>% stack() %>% table() %>% crossprod()
diag(N_intersect) = 0
N_intersect[lower.tri(N_intersect)] = 0
# find when N_intersect is over some threshold
result = which(N_intersect >= threshold, arr.ind = T) %>%
as.data.frame() %>%
`colnames<-`(c("row", "col"))
rownames(result) = NULL
return(result)
}
f.new = function(x, threshold) {
x2 = t(table(stack(x)))
result = list()
for (ii in 3:nrow(x2)) {
ia = which(x2[ii,]>0)
cands = which(rowSums(x2[1:(ii-1),ia]) >= threshold)
result[[ii]] = data.frame(row=cands, col=rep(ii, length(cands)))
}
result = do.call(rbind, result) %>% filter(!row==col)
rownames(result) = NULL
return(result)
}
我可以看到它们 return 相同的结果:
set.seed(1)
N = 1000
x = lapply(1:N, function(x) sample(letters, 10))
names(x) = as.character(1:length(x))
r1 = f.new(x, 9)
r2 = f.crossprod(x, 9)
identical(r1, r2)
> TRUE
时间是相似的,尽管 f.new 对于 N>5000 来说稍微快一些。最重要的是 f.new 没有问题 N>20000: