对于两个长度> 1 E8的逻辑向量x
和y
,计算2x2交叉表的最快方法是什么?
我怀疑答案是用C/C++编写它,但我想知道R中是否有一些东西已经非常聪明地解决了这个问题,因为它并不罕见。
示例代码,用于300 M条目(如果3E 8太大,可以让N = 1 E8;我选择的总大小略低于2.5GB(2.4GB)。我的目标密度为0.02,只是为了让它更有趣(如果有用的话,可以使用稀疏向量,但类型转换可能需要时间)。
set.seed(0)
N = 3E8
p = 0.02
x = sample(c(TRUE, FALSE), N, prob = c(p, 1-p), replace = TRUE)
y = sample(c(TRUE, FALSE), N, prob = c(p, 1-p), replace = TRUE)
字符串
一些明显的方法:
table
bigtabulate
1.简单的逻辑运算(例如sum(x & y)
)
1.向量乘法(BOO)data.table
1.以上部分,使用multicore
包中的parallel
(或新的parallel
包)
我已经尝试了前三个选项(见我的答案),但我觉得必须有更好更快的东西。
我发现table
的工作速度非常慢。bigtabulate
对于一对逻辑向量来说似乎有点大材小用。最后,执行普通逻辑运算似乎是一种组装,它会多次查看每个向量(3X?7 X?),更不用说它在处理过程中会占用大量额外的内存,这是一个巨大的时间浪费。
向量乘法通常是一个坏主意,但当向量稀疏时,可以将其存储为这样,然后使用向量乘法。
既然一个次级样本可能足以制作一个交叉表格,为什么要使用这么多数据?这些数据来自两个变量的TRUE
观测值非常罕见的情况。一个是数据异常的结果,另一个是由于代码中可能存在错误(可能的bug,因为我们只看到计算结果-将变量x
视为“垃圾输入”,而y
为“垃圾输出”。因此,问题是,由代码引起的输出中的问题是否仅仅是数据异常的情况,还是存在其他一些好数据变坏的情况?(这就是为什么我问了一个关于stopping when aNaN
,NA
, orInf
is encountered的问题。)
这也解释了为什么我的例子中TRUE
值的概率很低;这些值实际上发生的概率远远低于0.1%。
这是否意味着一种不同的解决途径?是的:它表明我们可以使用两种指数(即TRUE
在每个集合中的位置)和计数集合交集。我避免了集合交集,因为我被Matlab烧了一段时间,它会先对集合中的元素进行排序,然后再进行交集。(我隐约记得复杂性更令人尴尬:比如O(n^2)
而不是O(n log n)
。
那么,在R中如何实现呢?
5条答案
按热度按时间hjqgdpho1#
如果你要对巨大的逻辑向量进行大量的操作,看看bit包,它通过将布尔值存储为真正的1位布尔值来节省大量的内存。
这对
table
没有帮助;实际上它使情况变得更糟,因为由于它的构造方式,位向量中有更多的唯一值。但它确实有助于逻辑比较。字符串
px9o7tmv2#
这个答案给出了三个简单方法的时间,这是相信
table
很慢的基础。然而,要认识到的关键是“逻辑”方法效率非常低。看看它在做什么:sum
)不仅如此,它甚至没有被编译或并行化。然而,它仍然击败了
table
。注意,bigtabulate
,加上 * 一个额外的类型转换 *(1 * cbind...
)仍然击败了table
。下面是逻辑方法
table
和bigtabulate
的结果,N = 3E 8:字符串
在这种情况下,
table
是一场灾难。为了比较,这里是N = 3E 6:
在这一点上,似乎编写自己的逻辑函数是最好的,即使这会滥用
sum
,并多次检查每个逻辑向量。我还没有尝试编译函数,但这应该会产生更好的结果。更新1如果我们给予
bigtabulate
已经是整数的值,即如果我们在bigtabulate之外进行类型转换1 * cbind(v1,v2)
,那么N= 3E 6的倍数是1.80,而不是2.4。相对于“逻辑”方法的N= 3E 8的倍数只有1.21,而不是1.53。更新2
正如约书亚乌尔里希所指出的,转换为位向量是一个重大的改进-我们分配和移动的数据少了很多:R的逻辑向量每个条目占用4个字节(“为什么?“,你可能会问... Well, I don't know, but an answer may turn up here.),而一个位向量消耗,嗯,一个位,每个条目-即1/32的数据。所以,
x
消耗1.2e9字节,而xb
(下面代码中的位版本)仅消耗3.75e7字节。我已经从更新的基准测试中删除了
table
和bigtabulate
变体(N= 3e 8)。请注意,logicalB1
假设数据已经是位向量,而logicalB2
是相同的操作,但类型转换会受到惩罚。由于我的逻辑向量是对其他数据进行操作的结果,我没有从位向量开始的好处。尽管如此,付出的代价相对较小。[“logical 3”系列只执行3个逻辑运算,然后做减法。因为它是交叉制表,我们知道总数,正如DWin所说。]我们现在已经加快到只需要1.8-2.8秒,即使有许多严重的效率低下。毫无疑问它应该是可行的,在1秒内完成,更改包括一个或多个:C代码、编译和多核处理。在所有3个(或4个)不同的逻辑操作都可以独立完成之后,即使这仍然是浪费计算周期。
最好的挑战者中最相似的
logical3B2
,比table
快80倍左右,比朴素逻辑运算快10倍左右,而且还有很大的改进空间。下面是生成上述内容的代码。注意我建议注解掉一些操作或向量,除非你有很多RAM -创建
x
、x1
和xb
,沿着相应的y
对象,将占用相当多的内存。另外,请注意:我应该使用
1L
作为bigtabulate
的整数乘数,而不仅仅是1
。在某些时候,我会重新运行这个更改,并建议任何使用bigtabulate
方法的人进行更改。mi7gmzs63#
这是一个使用RCPP糖的答案。
字符串
其结果是:
所以,我们可以得到一个小的速度超过位包,虽然我很惊讶,如何竞争激烈的时代。
更新:为了荣誉Iterator,这里有一个Rcpp迭代器解决方案:
aurhwmvo4#
一种不同的策略是只考虑集合的交集,使用
TRUE
值的索引,利用样本非常有偏见(即主要是FALSE
)。为此,我介绍了
func_find01
和一个使用bit
包的翻译(func_find01B
);所有没有出现在上面答案中的代码都粘贴在下面。我重新运行了完整的N= 3e 8评估,除了忘记使用
func_find01B
;我在第二遍中重新运行了更快的方法。字符串
只有“快速”的方法:
所以,
find01B
是不使用预转换位向量的方法中最快的,以微小的差距(2.099秒对2.327秒)。find02
是从哪里来的?我后来写了一个使用预计算位向量的版本。这是现在最快的。一般来说,“指数法”的运行时间可能会受到边际概率和联合概率的影响。我怀疑,当概率更低时,它将特别具有竞争力,但必须事先知道,或通过子样本。
更新1.我还对Josh奥布莱恩的建议进行了计时,使用
tabulate()
而不是table()
。结果,在12秒过去后,大约是2Xfind01
和bigtabulate2
的一半。现在最好的方法接近1秒,这也相对较慢:代码:
9w11ddsr5#
这里有一个关于
Rcpp
的答案,只列出那些不都是0
的条目。这也是我第一次尝试使用Rcpp
,所以在移动数据时可能会有一些明显的低效之处。这应该让其他人展示如何改善这一点。字符串
N = 3E8
的计时结果:这需要比我第二个答案中的
func_find01B
长6倍多的时间。