ben*_*min 6 grouping r data.table
在R中,我有一个大的data.table.对于每一行,我想计算具有类似值x1(+/-一些容差,tol)的行.我可以使用adply来使用它,但它太慢了.似乎data.table有点好处 - 事实上,我已经在使用data.table进行部分计算了.
有没有办法完全使用data.table?这是一个例子:
library(data.table)
library(plyr)
my.df = data.table(x1 = 1:1000,
x2 = 4:1003)
tol = 3
adply(my.df, 1, function(df) my.df[x1 > (df$x1 - tol) & x1 < (df$x1 + tol), .N])
Run Code Online (Sandbox Code Playgroud)
结果:
x1 x2 V1
1: 1 4 3
2: 2 5 4
3: 3 6 5
4: 4 7 5
5: 5 8 5
---
996: 996 999 5
997: 997 1000 5
998: 998 1001 5
999: 999 1002 4
1000: 1000 1003 3
Run Code Online (Sandbox Code Playgroud)
这是一个与我的真实数据更接近的示例数据集:
set.seed(10)
x = seq(1,100000000,100000)
x = x + sample(1:50000, length(x), replace=T)
x2 = x + sample(1:50000, length(x), replace=T)
my.df = data.table(x1 = x,
x2 = x2)
setkey(my.df,x1)
tol = 100000
og = function(my.df) {
adply(my.df, 1, function(df) my.df[x1 > (df$x1 - tol) & x1 < (df$x1 + tol), .N])
}
microbenchmark(r_ed <- ed(copy(my.df)),
r_ar <- ar(copy(my.df)),
r_og <- og(copy(my.df)),
times = 1)
Unit: milliseconds
expr min lq median uq max neval
r_ed <- ed(copy(my.df)) 8.553137 8.553137 8.553137 8.553137 8.553137 1
r_ar <- ar(copy(my.df)) 10.229438 10.229438 10.229438 10.229438 10.229438 1
r_og <- og(copy(my.df)) 1424.472844 1424.472844 1424.472844 1424.472844 1424.472844 1
Run Code Online (Sandbox Code Playgroud)
显然,来自@eddi和@Arun的解决方案比我的快得多.现在我只需要尝试理解卷.
x1不是整数的情况.您正在寻找的算法是Interval Tree.并且有一个名为IRanges的生物传导器包可以完成这项任务.这很难打败.
require(IRanges)
require(data.table)
my.df[, res := countOverlaps(IRanges(my.df$x1, width=1),
IRanges(my.df$x1-tol+1, my.df$x1+tol-1))]
Run Code Online (Sandbox Code Playgroud)
如果分解代码,可以用三行代码编写:
ir1 <- IRanges(my.df$x1, width=1)
ir2 <- IRanges(my.df$x1-tol+1, my.df$x1+tol-1)
cnt <- countOverlaps(ir1, ir2)
Run Code Online (Sandbox Code Playgroud)
我们基本上做的是创建两个"范围"(只是键入ir1并ir2查看它们是如何).然后我们询问,每个条目中ir1它们重叠的数量ir2(这是"间隔树"部分).这非常有效.隐式自变量type到countOverlaps,默认情况下是"类型=任何".如果需要,您可以探索其他类型.这非常有用.findOverlaps功能也是相关的.
注意:对于这种特殊情况,可以有更快的解决方案(事实上,参见@ eddi's),其中ir1的宽度= 1.但是对于宽度可变和/或> 1的问题,这应该是最快的.
ag <- function(my.df) my.df[, res := sum(abs(my.df$x1-x1) < tol), by=x1]
ro <- function(my.df) {
my.df[,res:= { y = my.df$x1
sum(y > (x1 - tol) & y < (x1 + tol))
}, by=x1]
}
ar <- function(my.df) {
my.df[, res := countOverlaps(IRanges(my.df$x1, width=1),
IRanges(my.df$x1-tol+1, my.df$x1+tol-1))]
}
require(microbenchmark)
microbenchmark(r1 <- ag(copy(my.df)), r2 <- ro(copy(my.df)),
r3 <- ar(copy(my.df)), times=100)
Unit: milliseconds
expr min lq median uq max neval
r1 <- ag(copy(my.df)) 33.15940 39.63531 41.61555 44.56616 208.99067 100
r2 <- ro(copy(my.df)) 69.35311 76.66642 80.23917 84.67419 344.82031 100
r3 <- ar(copy(my.df)) 11.22027 12.14113 13.21196 14.72830 48.61417 100 <~~~
identical(r1, r2) # TRUE
identical(r1, r3) # TRUE
Run Code Online (Sandbox Code Playgroud)
这是一个更快的data.table解决方案。我们的想法是使用 的滚动合并功能data.table,但在此之前,我们需要稍微修改数据并将列设为x1数字而不是整数。这是因为 OP 使用严格的不等式并使用滚动连接,我们必须将容差减少一点点,使其成为浮点数。
my.df[, x1 := as.numeric(x1)]
# set the key to x1 for the merges and to sort
# (note, if data already sorted can make this step instantaneous using setattr)
setkey(my.df, x1)
# and now we're going to do two rolling merges, one with the upper bound
# and one with lower, then get the index of the match and subtract the ends
# (+1, to get the count)
my.df[, res := my.df[J(x1 + tol - 1e-6), list(ind = .I), roll = Inf]$ind -
my.df[J(x1 - tol + 1e-6), list(ind = .I), roll = -Inf]$ind + 1]
# and here's the bench vs @Arun's solution
ed = function(my.df) {
my.df[, x1 := as.numeric(x1)]
setkey(my.df, x1)
my.df[, res := my.df[J(x1 + tol - 1e-6), list(ind = .I), roll = Inf]$ind -
my.df[J(x1 - tol + 1e-6), list(ind = .I), roll = -Inf]$ind + 1]
}
microbenchmark(ed(copy(my.df)), ar(copy(my.df)))
#Unit: milliseconds
# expr min lq median uq max neval
# ed(copy(my.df)) 7.297928 10.09947 10.87561 11.80083 23.05907 100
# ar(copy(my.df)) 10.825521 15.38151 16.36115 18.15350 21.98761 100
Run Code Online (Sandbox Code Playgroud)
注意:正如 Arun 和 Matthew 都指出的那样,如果x1是整数,则不必转换为数字并从中减去少量,tol可以tol - 1L代替tol - 1e-6上面使用。
| 归档时间: |
|
| 查看次数: |
1904 次 |
| 最近记录: |