如何在R中高效地计算大向量中成对差异的直方图?

10

我在R中处理一个包含大量整数的向量(约1000万个整数),我需要找到每对差值小于等于500的不同整数,并绘制它们之间差异的直方图(即对于每一对,第二个数减去第一个数的差值)。

以下是非矢量化代码,可以极其缓慢地实现我的目标:

# Generate some random example data
V <- round(rnorm(100) * 1000)

# Prepare the histogram
my.hist <- rep(0, 500)
names(my.hist) <- as.character(seq(1,500))
for (x1 in V) {
    for (x2 in V) {
        difference = x2 - x1
        if (difference > 0 && difference <= 500) {
            my.hist[difference] = my.hist[difference] + 1
        }
    }
}

假设每个整数都是唯一的,因此difference > 0位是可以的。这是允许的,因为我实际上不关心差值为零的情况。

以下是向量化内部循环的代码:

my.hist2 <- rep(0, 500)
names(my.hist2) <- as.character(seq(1,500))
for (x1 in V) {
    differences <- V[V > x1 & V <= x1+500] - x1
    difftable <- table(differences)
    my.hist2[names(difftable)] = my.hist2[names(difftable)] + difftable
}

这肯定比第一个版本快。然而,即使是这个变体当V只包含500000个元素(半百万)时也已经太慢了。

我可以按照以下方式无需任何显式循环来完成:

X <- combn(V, 2)
# X is a matrix with two rows where each column represents a pair
diffs <- abs(X[2,] - X[1,])
my.hist3 <- table(diffs[diffs <= 500])

然而,矩阵 X 将包含 10e6 * (10e6 - 1) / 2,即约为 50,000,000,000,000 列,这可能是个问题。

那么有没有一种方法可以在不使用显式循环(太慢)或展开所有对的矩阵(太大)的情况下完成此操作?

如果你想知道我为什么需要这样做,我正在实现这个: http://biowhat.ucsd.edu/homer/chipseq/qc.html#Sequencing_Fragment_Length_Estimation


我理解的对吗,你有 50,000,000,000,000 列,每列有 10,000,000 个元素?我很想看到这个问题的解决 :) 这个规模使它成为一个令人敬畏的问题。 - ma cılay
@user306848: 我认为Ryan的意思是combn将返回一个2 x 50E12的对矩阵(所有可能的组合)。 - jbaums
是的,@jbaums 是正确的。 - Ryan C. Thompson
您的向量范围/分布是什么样子?有多少个唯一整数? - frankc
假设没有两个整数相等,它们的范围从0到100,000,000。在这个范围内的分布预计会高度不均匀,有许多类似值的聚集体。(这些聚集体代表人类基因组中特定蛋白质附着的坐标。) - Ryan C. Thompson
1个回答

16

一种可能的改进是对数据进行排序: 距离低于500的(i,j)对将接近对角线, 这样就不必探索所有值。

代码如下所示(仍然非常缓慢)。

n <- 1e5
k <- 500
V <- round(rnorm(n) * n * 10)
V <- as.integer(V)
V <- sort(V)
h <- rep(0,k)

for(i in 1:(n-1)) {
  for(j in (i+1):n) {
    d <- V[j] - V[i]
    if( d > k ) break
    if( d > 0 ) h[d] <- h[d]+1
  }
}

编辑:如果你想要更快的速度,你可以用C语言编写循环。处理1000万个元素只需要1秒。

n <- 10e6
k <- 500
V <- round(rnorm(n) * n * 10)
V <- as.integer(V)
V <- sort(V)
h <- rep(0,k)

library(inline)
sig <- signature(n="integer", v="integer", k="integer", h="integer")
code <- "
  for( int i = 0; i < (*n) - 1; i++ ) {
    for( int j = i + 1; j < *n; j++ ) {
      int d = v[j] - v[i];
      if( d > *k ) break;
      if( d > 0 ) h[d-1]++;
    }
  }
"
f <- cfunction( sig, code, convention=".C" )
h <- f(n,V,k,h)$h

希望我能够点赞更多。我也有同样的策略,但你的实现方式要好得多。 - IRTFM
C语言中的内联功能是否不支持函数参数和返回语句? - Ryan C. Thompson
实际上,我有点困惑于R如何传递给C。在C代码中,变量n、v、k和h是指向C整数和整数数组的指针,还是指向R向量的指针?或者说,R整数向量的内存表示与C整数数组兼容,因此实际上两者都可以? - Ryan C. Thompson
1
在R和C之间交换数据有两种约定: .Call使用R对象(在C中为SEXPR类型), 而.C使用指针(指向C数组)。 如果您只有数字数组,则.C约定更容易使用, 但您还必须传递数组的大小 (这通常在包装函数中完成,因此最终用户永远不会看到它)。 在大多数情况下, 更容易在R中创建包含结果的对象, 并在C中填充它们。 有关更多信息,请参见 编写R扩展手册。 - Vincent Zoonekynd
好的,在阅读了那份手册之后,我想我明白了。当使用.C约定将R向量传递给cfunction时,R向量会被转换为C数组。那么返回值呢?从我创建的一个玩具示例来看,它似乎返回一个由签名中的变量组成的列表,这些变量被转换回R向量。这正确吗? - Ryan C. Thompson
太好了,修正了一个小错误(在第二个循环中将i改为j),内联C代码运行得非常好。谢谢! - Ryan C. Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接