比使用for循环更快的编码

时间:2017-02-24 03:07:16

标签: r dplyr

假设我有以下数据框

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,该程序将效率低下。

什么是更有效的代码解决方案?

2 个答案:

答案 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中的任务会更快。与有序向量相关的关键函数具有令人困惑的名称(findIntervalsortordercut),但幸运的是它们都在向量上工作。

连续与离散。上面的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与哪个xy相关联。之后,你只能保留你想要的那些。

您可以使用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,这应该更快。