假设我有以下数据框
set.seed(36)
n <- 300
dat <- data.frame(x = round(runif(n,0,200)), y = round(runif(n, 0, 500)))
d <- dat[order(dat$y),]
对于d$y<=300
的每个值,我必须创建一个变量res
,其中分子是指标(d$x <= d$y[i])
的总和,而分母是指标{{的总和1}}。我已经在(d$y >= d$y[i])
中编写了代码:
for loop
但我担心的是,当res <- NULL
for( i in seq_len(sum(d$y<=300)) ){
numerator <- sum(d$x <= d$y[i])
denominator <- sum(d$y >= d$y[i])
res[i] <- numerator / denominator
}
和x
的观察数量很大时,即数据框的行数增加时,y
将会缓慢运行。此外,如果我模拟数据1000次并且每次运行for loop
,该程序将效率低下。
什么是更有效的代码解决方案?
答案 0 :(得分:9)
这取决于d
已按原样排序:
# example data
set.seed(36)
n <- 1e5
dat <- data.frame(x = round(runif(n,0,200)), y = round(runif(n, 0, 500)))
d <- dat[order(dat$y),]
我的建议(感谢@alexis_laz的分母):
system.time(res3 <- {
xs <- sort(d$x) # sorted x
yt <- d$y[d$y <= 300] # truncated y
num = findInterval(yt, xs)
den = length(d$y) - match(yt, d$y) + 1L
num/den
})
# user system elapsed
# 0 0 0
OP的方法:
system.time(res <- {
res <- NULL
for( i in seq_len(sum(d$y<=300)) ){
numerator <- sum(d$x <= d$y[i])
denominator <- sum(d$y >= d$y[i])
res[i] <- numerator / denominator
}
res
})
# user system elapsed
# 50.77 1.13 52.10
# verify it matched
all.equal(res,res3) # TRUE
@ d.b的方法:
system.time(res2 <- {
numerator = rowSums(outer(d$y, d$x, ">="))
denominator = rowSums(outer(d$y, d$y, "<="))
res2 = numerator/denominator
res2 = res2[d$y <= 300]
res2
})
# Error: cannot allocate vector of size 74.5 Gb
# ^ This error is common when using outer() on large-ish problems
矢量化。一般来说,如果可以向量化任务,则R中的任务会更快。与有序向量相关的关键函数具有令人困惑的名称(findInterval
,sort
,order
和cut
),但幸运的是它们都在向量上工作。
连续与离散。上面的match
应该是计算分母的快速方法,无论数据是连续的还是具有质量点/重复值。如果数据是连续的(因此没有重复),分母可以只是seq(length(xs), length = length(yt), by=-1)
。如果它是完全离散的并且有很多重复(比如这里的例子),那么可能还有一些方法可以让它更快,也许就像其中一个:
den2 <- inverse.rle(with(rle(yt), list(
values = length(xs) - length(yt) + rev(cumsum(rev(lengths))),
lengths = lengths)))
tab <- unname(table(yt))
den3 <- rep(rev(cumsum(rev(tab))) + length(xs) - length(yt), tab)
# verify
all.equal(den,den2) # TRUE
all.equal(den,den3) # TRUE
findInterval
仍可用于连续数据的分子。这对于我猜测的重复值情况并不理想(因为我们冗余地找到了许多重复yt
值的间隔)。加快这种想法的类似想法可能适用。
其他选项。正如@chinsoon建议的那样,如果findInterval
太慢,data.table包可能是合适的,因为它有很多专注于排序数据的功能,但对我来说,如何在这里应用它并不明显。
答案 1 :(得分:3)
不是运行循环,而是一次生成所有分子和分母。这样,您还可以跟踪哪个res
与哪个x
和y
相关联。之后,你只能保留你想要的那些。
您可以使用outer
进行向量之间的元素比较。
numerator = rowSums(outer(d$y, d$x, ">=")) #Compare all y against all x
denominator = rowSums(outer(d$y, d$y, "<=")) #Compare all y against itself
res2 = numerator/denominator #Obtain 'res' for all rows
#I would first 'cbind' res2 to d and only then remove the ones for 'y <=300'
res2 = res2[d$y <= 300] #Keep only those 'res' that you want
由于这是使用rowSums
,这应该更快。