R中循环的矢量化

R中循环的矢量化,r,R,我正在努力对以下函数的重复应用进行矢量化,我目前已将其实现为for循环。这个小示例表明了较大数据集的问题,对于较大数据集,矢量化将允许有益的运行时改进: action = function(x,y,i) { firsttask = cumsum(x[which(x<y[i])]) secondtask = mean(firsttask) thirdtask = min(firsttask[which(firsttask>secondtask)]) f

我正在努力对以下函数的重复应用进行矢量化,我目前已将其实现为for循环。这个小示例表明了较大数据集的问题,对于较大数据集,矢量化将允许有益的运行时改进:

action = function(x,y,i) {

    firsttask = cumsum(x[which(x<y[i])])
    secondtask = mean(firsttask)
    thirdtask = min(firsttask[which(firsttask>secondtask)])
    fourthtask = length(firsttask)

    output = list(firsttask, data.frame(average=secondtask,
                                        min_over_mean=thirdtask,
                                        size=fourthtask))
    return(output)
}

thingofinterest = c(1:10)
limits = c(5:10)

test = vector("list", length = length(limits))
for(i in 1:length(limits)) {
test[[i]] = action(thingofinterest, limits, i)
}

test
action=函数(x,y,i){
firsttask=cumsum(x[which(xsecondtask)])
第四个任务=长度(第一个任务)
输出=列表(第一个任务,data.frame(平均值=第二个任务,
最小值超过平均值=第三个任务,
大小=第四个任务)
返回(输出)
}
Thingofinanterest=c(1:10)
极限=c(5:10)
测试=矢量(“列表”,长度=长度(限值))
用于(i/1:长度(限值)){
测试[[i]]=动作(ThingFinTerest,极限,i)
}
测试

我想用矢量化命令代替for循环,而不是任何
apply
函数系列,因为它们并不总能提高性能(我并不是说for循环有什么问题,在这种情况下,我只需要优化速度。请参阅:)。我该怎么做呢?

这里有一个关于我上面评论的比较。我在注释中做了更改(初始化测试,在
操作
中更改顺序,并删除了
操作
列表输出中的
data.frame
调用,如果您可以接受的话):

这些修改使速度提高了约15倍

用于比较的功能和数据:

action0 = function(x,y,i) {
  firsttask = cumsum(x[which(x<y[i])])
  secondtask = min(firsttask[which(firsttask>mean(firsttask))])
  thirdtask = mean(firsttask)
  fourthtask = length(firsttask)
  output = list(firsttask, data.frame(min_over_mean=secondtask,
                                      average=thirdtask,
                                      size=fourthtask))
  return(output)
}

f0 <- function() {
  test = vector("list")
  for(i in 1:length(limits)) {
    test[[i]] = action0(thingofinterest, limits, i)
  }
}

thingofinterest = c(1:1000)
limits = c(50:100)

action1 = function(x,y,i) {
  firsttask = cumsum(x[which(x<y[i])])
  thirdtask = mean(firsttask)
  secondtask = min(firsttask[which(firsttask>thirdtask)])
  fourthtask = length(firsttask)
  list(firsttask, min_over_mean=secondtask,
                                      average=thirdtask,
                                      size=fourthtask)
}

f1 <- function() {
  test = vector("list", length = length(limits))
  for(i in 1:length(limits)) {
    test[[i]] = action1(thingofinterest, limits, i)
  }
}
action0=函数(x,y,i){
firsttask=cumsum(x[其中(xmean(firsttask))]))
第三个任务=平均值(第一个任务)
第四个任务=长度(第一个任务)
输出=列表(第一个任务,数据帧(最小值超过平均值=第二个任务,
平均值=第三个任务,
大小=第四个任务)
返回(输出)
}

f0在开始尝试更改代码以使其更快之前,您需要了解代码中的瓶颈在哪里。例如:

timer <- function(action, thingofinterest, limits) {
  st <- system.time({           # for the wall time
    Rprof(interval=0.01)        # Start R's profile timing
    for(j in 1:1000) {          # 1000 function calls
      test = vector("list")
      for(i in 1:length(limits)) {
        test[[i]] = action(thingofinterest, limits, i)
      }
    }
    Rprof(NULL)  # stop the profiler
  })
  # return profiling results
  list(st, head(summaryRprof()$by.total))
}
action = function(x,y,i) {
  firsttask = cumsum(x[which(x<y[i])])
  secondtask = min(firsttask[which(firsttask>mean(firsttask))])
  thirdtask = mean(firsttask)
  fourthtask = length(firsttask)
  output = list(firsttask, data.frame(average=secondtask,
                                      min_over_mean=thirdtask,
                                      size=fourthtask))
  return(output)
}
timer(action, 1:1000, 50:100)
# [[1]]
#    user  system elapsed 
#   9.720   0.012   9.737 
# 
# [[2]]
#                 total.time total.pct self.time self.pct
# "system.time"         9.72    100.00      0.07     0.72
# "timer"               9.72    100.00      0.00     0.00
# "action"              9.65     99.28      0.24     2.47
# "data.frame"          8.53     87.76      0.84     8.64
# "as.data.frame"       5.50     56.58      0.44     4.53
# "force"               4.40     45.27      0.11     1.13
现在,您还可以取消对
mean
的一个调用和对
which
的两个调用

action2 = function(x,y,i) {
  firsttask = cumsum(x[x < y[i]])
  secondtask = mean(firsttask)
  thirdtask = min(firsttask[firsttask > secondtask])
  fourthtask = length(firsttask)
  list(task=firsttask, average=secondtask,
    min_over_mean=thirdtask, size=fourthtask)
}
timer(action2, 1:1000, 50:100)
# [[1]]
#    user  system elapsed 
#   0.808   0.000   0.808 
# 
# [[2]]
#               total.time total.pct self.time self.pct
# "system.time"       0.80    100.00      0.12    15.00
# "timer"             0.80    100.00      0.00     0.00
# "action"            0.68     85.00      0.24    30.00
# "<"                 0.20     25.00      0.20    25.00
# "mean"              0.13     16.25      0.08    10.00
# ">"                 0.05      6.25      0.05     6.25
action2=函数(x,y,i){
firsttask=cumsum(x[x第二个任务])
第四个任务=长度(第一个任务)
列表(任务=第一个任务,平均值=第二个任务,
最小值超过平均值=第三个任务,大小=第四个任务)
}
计时器(动作2,1:1000,50:100)
# [[1]]
#用户系统运行时间
#   0.808   0.000   0.808 
# 
# [[2]]
#total.time total.pct self.time self.pct
#“系统时间”0.80 100.00 0.12 15.00
#“计时器”0.80 100.00 0.00 0.00
#“行动”0.68 85.00 0.24 30.00
# ""                 0.05      6.25      0.05     6.25
现在你可以看到,在你的
操作
功能之外,有大量的时间花在做事情上。我在引号中加上了重要的数字,因为它占运行时间的15%,但只有120毫秒。如果您的实际代码运行约12小时,则此新的
操作
功能将在约1小时内完成


如果我在
定时器
函数中的
for
循环之外预先分配
测试
列表,结果会稍微好一些,但是调用
data.frame
是最大的时间消耗。

只是为了与*apply family添加一个比较点,我使用了这个代码(结果与
相同(f1(),f2())
f3返回不同的布局)

测试后,
调用
会在较大的
感兴趣的
向量上提高速度

thingofinterest = c(1:100000)
limits = c(50:1000)

action1 = function(x,y,i) {
  firsttask = cumsum(x[which(x<y[i])])
  thirdtask = mean(firsttask)
  secondtask = min(firsttask[which(firsttask>thirdtask)])
  fourthtask = length(firsttask)
  list(firsttask, min_over_mean=secondtask,
                                      average=thirdtask,
                                      size=fourthtask)
}

f1 <- function() {
  test = vector("list", length = length(limits))
  for(i in 1:length(limits)) {
    test[[i]] = action1(thingofinterest, limits, i)
  }
  return(test)
}


action2 <- function(x,y) {
  firsttask = cumsum(x[which(x<y)])
  thirdtask = mean(firsttask)
  secondtask = min(firsttask[which(firsttask>thirdtask)])
  fourthtask = length(firsttask)
  list(firsttask, min_over_mean=secondtask,
                  average=thirdtask,
                  size=fourthtask)
}

f2 <- function() {
  test <- lapply(limits,action2,x=thingofinterest)
  return(test)
}

f3 <- function() {
  test <- sapply(limits,action2,x=thingofinterest)
  return(test)
}
因此,在这种情况下,一个干净的for循环并没有那么糟糕

我觉得可能有一种data.table的方法可以一次性完成“动作”工作,但目前这超出了我的知识范围

更多关于速度的话题,我看不到真正的矢量化方法。这些向量不是彼此的子集,它们的总和不能被“切割”以避免计算公共序列


正如您在评论中所说,限制通常在90到110个条目之间,并行处理可能是在不同的核心上计算每个迭代的正确方法,因为每个迭代独立于其他迭代。(Thinkink of
mclappy
,但可能还有其他更适合您的用例)

For循环通常可以使用
apply
do.call进行矢量化。我认为在这种情况下,后者将是最有用的。我试图避免
apply
,因为该系列中的许多函数只是为了循环而被美化
do.call
仅输出
action(thingofindertest,5)
。为什么要避免循环?隐式循环或显式循环没有本质上的错误。您可以轻松改进两件事:使用
test=vector(“list”,length=length(limits))
预先将
test
向量初始化为正确的大小,以避免对象在循环中增长(这会使它变慢!)在
action
函数中,您可以在第二步之前计算
mean(firsttask)
,并使用存储在variable@NigelStackhouse*apply系列是C实现的循环,在适用时调用基本R函数的相应C实现。所以“基本上为循环屏蔽”的说法是错误的。for循环和*apply调用自定义复杂函数都没有什么问题,只要你能避免在其中增加对象并一次又一次地重复相同的调用。@JoshuaUlrich,我知道,那是因为我删除了顶部提到的
data.frame
调用。。我假设这对于更快的计算来说是可以接受的。删除
数据。帧
调用几乎占了所有的加速。我猜如果使用更大的数据集进行测试,那么
测试
的初始化将变得更重要。一般来说,这可能仍然是对OP的一个有趣的见解,我猜
test
的先前初始化很有趣,尽管在我的实际问题中(不是提供的示例),我不知道
action2 = function(x,y,i) {
  firsttask = cumsum(x[x < y[i]])
  secondtask = mean(firsttask)
  thirdtask = min(firsttask[firsttask > secondtask])
  fourthtask = length(firsttask)
  list(task=firsttask, average=secondtask,
    min_over_mean=thirdtask, size=fourthtask)
}
timer(action2, 1:1000, 50:100)
# [[1]]
#    user  system elapsed 
#   0.808   0.000   0.808 
# 
# [[2]]
#               total.time total.pct self.time self.pct
# "system.time"       0.80    100.00      0.12    15.00
# "timer"             0.80    100.00      0.00     0.00
# "action"            0.68     85.00      0.24    30.00
# "<"                 0.20     25.00      0.20    25.00
# "mean"              0.13     16.25      0.08    10.00
# ">"                 0.05      6.25      0.05     6.25
thingofinterest = c(1:100000)
limits = c(50:1000)

action1 = function(x,y,i) {
  firsttask = cumsum(x[which(x<y[i])])
  thirdtask = mean(firsttask)
  secondtask = min(firsttask[which(firsttask>thirdtask)])
  fourthtask = length(firsttask)
  list(firsttask, min_over_mean=secondtask,
                                      average=thirdtask,
                                      size=fourthtask)
}

f1 <- function() {
  test = vector("list", length = length(limits))
  for(i in 1:length(limits)) {
    test[[i]] = action1(thingofinterest, limits, i)
  }
  return(test)
}


action2 <- function(x,y) {
  firsttask = cumsum(x[which(x<y)])
  thirdtask = mean(firsttask)
  secondtask = min(firsttask[which(firsttask>thirdtask)])
  fourthtask = length(firsttask)
  list(firsttask, min_over_mean=secondtask,
                  average=thirdtask,
                  size=fourthtask)
}

f2 <- function() {
  test <- lapply(limits,action2,x=thingofinterest)
  return(test)
}

f3 <- function() {
  test <- sapply(limits,action2,x=thingofinterest)
  return(test)
}
> microbenchmark(f1(),f2(),f3(),times=10)
Unit: seconds
 expr      min       lq     mean   median       uq      max neval
 f1() 4.303302 4.336767 4.373119 4.380383 4.403434 4.441945    10
 f2() 4.267922 4.327208 4.450175 4.399422 4.423191 5.041011    10
 f3() 4.240551 4.293855 4.412548 4.362949 4.468117 4.730717    10