我有一个数据表dt1:

id1 id2 V1 V2
1 a c(1, 2, 3, 4) c(1, 3, 6)
2 b c(2, 6, 9, 8) c(8, 5)

我想添加新的列,这些列是对V1V2变量进行setdiff()intersect()union()操作的结果.

预期输出:

id1 id2 V1 V2 diff_V1_V2 intersect_V1_V2 union_V1_V2
1 a c(1, 2, 3, 4) c(1, 3, 6) c(2, 4) c(1, 3) c(1, 2, 3, 4, 6)
2 b c(2, 6, 9, 8) c(8, 5) c(2, 6, 9) c(8) c(2, 5, 6, 8, 9)

我试过:

dt_new <- dt1[, c("diff_V1_V2", "intersect_V1_V2", "union_V1_V2") := list(
              Map(setdiff, V1, V2),
              Map(intersect, V1, V2),
              Map(union, V1, V2))]

但我的实向量很长,所以这些运算需要很长时间.

So, how can these operations be sped up or how to get similar results using another functions/approaches? I'm looking for the most efficient way.
Or is it possible to parallelize calculations?

推荐答案

天真的方法:

Naive <- function (a, b) {
  list(intersect = intersect(a, b),
       union = union(a, b),
       adiffb = setdiff(a, b))
}

您可以利用基本数学在向量ab的一次扫描中完成所有三个操作,而不是通过三个函数调用完成三次扫描.此外,如果确定两个向量中都没有重复的值,则可以跳过昂贵的unique.

SetOp <- function (a, b, no.dup.guaranteed = FALSE) {
  if (no.dup.guaranteed) {
    au <- a
    bu <- b
  } else {
    au <- unique(a)
    bu <- unique(b)
  }
  ind <- match(bu, au, nomatch = 0)
  INTERSECT <- au[ind]
  DIFF <- au[-c(ind, length(au) + 1)]  ## https://stackoverflow.com/a/52772380
  UNION <- c(bu, DIFF)
  list(intersect = INTERSECT, union = UNION, adiffb = DIFF)
}

SetOp(a = c(1, 2, 3, 4), b = c(1, 3, 6))
SetOp(a = c(2, 6, 9, 8), b = c(8, 5))

一些基准:

## no duplicated values in either a or b; can set no.dup.guaranteed = TRUE
a <- sample.int(11000, size = 10000, replace = FALSE)
b <- sample.int(11000, size = 10000, replace = FALSE)

microbenchmark::microbenchmark(naive = Naive(a, b),
                               better = SetOp(a, b),
                               fly = SetOp(a, b, no.dup.guaranteed = TRUE))
#Unit: milliseconds
#   expr      min       lq     mean   median       uq      max neval
#  naive 6.457302 6.489710 6.751996 6.511399 6.567941 8.571623   100
# better 3.251701 3.268873 3.377910 3.277086 3.306723 3.880755   100
#    fly 1.734898 1.749300 1.805163 1.755927 1.767114 3.326179   100

## lots of duplicated values in both a and b; must have no.dup.guaranteed = FALSE
a <- sample.int(100, size = 10000, replace = TRUE)
b <- sample.int(100, size = 10000, replace = TRUE)

microbenchmark::microbenchmark(naive = Naive(a, b),
                               better = SetOp(a, b))
#Unit: microseconds
#   expr      min       lq      mean   median       uq      max neval
#  naive 1421.702 1431.023 1653.1339 1443.147 1483.255 3809.031   100
# better  396.193  398.695  446.7062  400.293  412.046 1995.294   100

如果你想进一步加速,你需要考虑如何加速unique().这并不容易,因为您可能无法击败R使用的内部算法.


我看到了速度的进一步提高,用R fast Rfast::sort_unique()取代了unique().

谢谢你,@M.Viking.很高兴看到your answer.我没有时间在我的操作系统上安装GNU Scientific Library(GSL),所以我无法自己安装并try Rfast.

下面是对您的基准测试结果的一些 comments .

  • "better2"比"fly"快的原因是,match在排序向量上更快.所以是的,即使ab中没有重复的值,应用sort_unique仍然是一个好主意.

  • 您可能还想try Rfast中的Match函数.我在软件包的文档中发现了这个函数,但不知道它与R的基本版本相比有多快.此外,文档没有明确说明Match如何处理不匹配.相比之下,基本版本match有一个有用的参数nomatch,我将其设置为0以避免NA索引.

好主意.不幸的是,Rfast::Match()并不能取代base::match().然而,幸运的是,fastmatch::fmatch()match()的快速替代品.

我们这里有一个非常鼓舞人心的迭代!很高兴知道!拥有如此多有用工具的惊人R社区!


我的V1V2变量不包含重复项,因此如果我理解正确,就没有必要使用unique函数?

直觉上,是的,因为我们不想做额外的工作.但有趣的是,M.Viking's answer中的基准测试结果表明,对向量进行排序以加速match是值得的.所以你可以用M.Viking给出的SetOp2().

我认为在你的申请中,用base::match代替SetOp3()是不值得的.根据其文档,只有当我们执行重复匹配(如match(a, key)match(b, key)等)时,fmatch才比match快,其中key被重用.维京(M.Viking)的基准支持这一点,因为microbenchmark()次重复SetOp3(a, b)次达SetOp3()次.在第一次运行中,fmatchmatch一样快;然后,在接下来的99次运行中,它比match次快得多.然而,在应用程序中,每个向量只使用一次.由于不存在重用,所以我们最好使用match.

那么,如何将您的解决方案应用于我的每一行数据(V1V2是列表)?

我们必须使用循环或类似循环的函数,就像你在问题中使用的Map.唯一的问题是,我们需要一些后处理来提取结果.见下文.

V1 <- list(a1 = c(1, 2, 3, 4), a2 = c(2, 6, 9, 8))
V2 <- list(b1 = c(1, 3, 6), b2 = c(8, 5))

## or: ans <- Map(SetOp2, V1, V2)
ans <- Map(SetOp, V1, V2, no.dup.guaranteed = TRUE)
## post-processing
INTERSECT <- lapply(ans, "[[", 1)
UNION <- lapply(ans, "[[", 2)
SETDIFF <- lapply(ans, "[[", 3)

Additional thoughts on parallel processing

jblood94's answer通过并行计算又迈出了一步.干得好这是练习parallel的一个很好的练习.然而,原则上,我们不想并行这个任务,因为它是内存限制的,而不是CPU限制的.我们只是从内存中扫描数据,而没有执行复杂的CPU算法.众所周知,并行处理在这类工作中不太有希望,我也不希望有太大的加速.与串行处理相比,jblood94似乎能够获得82.89/34.21=2.42的加速.但是,他/她没有提到使用了多少CPU核.例如,如果开发了8个内核,那么2.42的加速比就非常差.

R相关问答推荐

找出疾病消失的受试者

如何将移除事件分配给动态创建的按钮?

筛选出以特定顺序患病的个体

如何在geom_col中反转条

条形图和在Ploly中悬停的问题

根据元素和前一个值之间的差值过滤矩阵的元素

在数组索引上复制矩阵时出错

如何读取CSV的特定列时,给定标题作为向量

R:从geom_ol()中删除轮廓并导出为pdf

从服务器在Shiny中一起渲染图标和文本

有没有一种方法可以同时对rhandsontable进行排序和从rhandsontable中删除?

如何使这些react 表对象相互独立?

如何将一个方阵分解成没有循环的立方体

优化从每个面的栅格中提取值

数值型数据与字符混合时如何进行绑定

在不重复主题的情况下重新排列组

将某个阈值以下的列中的值分类到不同的列中,否则保持该列的原样

将每晚的平均值与每晚的值进行比较,统计是否有效?

即使使用相同的种子,mtry值也取决于TuneGrid范围

移除y轴断开的geom_bar图的外框