优化列表函数以避免在R中循环

Duc*_*uck 2 performance r

我正在使用大量的值列表R.我需要将一些函数应用于列表的每个元素.我使用的列表i1是由下一个代码生成的:

i1=list(0)
i1[1:120000]=runif(120000,min = 10000,max = 100000)
Run Code Online (Sandbox Code Playgroud)

i1我必须应用一些函数,以便使用列表中的每个值作为输入来获取新的数据帧.函数是下一个:使用某些条件使用f_1每个值作为输入计算新值i1.在这个函数中,我使用了一些条件来获得值.功能是下一个:

f_1=function(x)
{
  y=ifelse((x/18)>20,x-(x/18),ifelse(x>20,x-20,ifelse(x==0,0,x)))
  return(y)
}
Run Code Online (Sandbox Code Playgroud)

第二个功能是f_2.此函数用作输入f_1,它由一个for有160次迭代的结构组成.在此函数中,将创建一个空向量.然后,通过应用f_1函数来增长矢量.最终结果f_2是一个数据框,其中包含for结构中生成的所有元素.功能是下一个:

f_2=function(v)
{
  x=c()
  y=v
  x[1]=y
  for(i in 2:160)
  {
    x[i]=f_1(x[i-1])
  }
  x=x[!duplicated(x)]
  x=c(x,0)
  z=as.data.frame(t(abs(diff(x))))
  return(z)
}
Run Code Online (Sandbox Code Playgroud)

最后,要同时适用f_1f_2i1我用的是包plyr,以功能应用到列表中.我为该活动构建了这个函数:

compute=function(x)
{
  y=f_2(x)
  return(y)
}
Run Code Online (Sandbox Code Playgroud)

通过使用compute我可以为列表中的所有元素应用函数.我用这个代码:

L2=llply(i1,compute)
Run Code Online (Sandbox Code Playgroud)

一切正常,但需要很长时间才能产生最终结果:

system.time(llply(i1,compute))
   user  system elapsed 
 436.71    0.92  447.70 
Run Code Online (Sandbox Code Playgroud)

我认为该过程太慢的原因在函数中有一个基础,f_2因为它在其中使用了一个循环.我已经寻找一些想法来避免这种结构,但我不清楚如何改变f_2以提高效率.拜托,您能帮忙解决一下解决这个问题的方向吗?我对函数有所了解,但在这种情况下,我使用了for函数内部来创建我希望的结果.

谢谢你的帮助!

Rol*_*and 9

您的代码有几个问题.例如,你犯了在循环中增长对象的经典错误.

但是,如果您对代码的性能不满意,则应该开始对其进行分析:

Rprof()
L2=llply(i1,compute)
Rprof(NULL)
summaryRprof()$by.self
#                       self.time self.pct total.time total.pct
#"ifelse"                    3.38    35.58       4.06     42.74
#"f_2"                       2.28    24.00       9.48     99.79
#"f_1"                       1.46    15.37       5.52     58.11
#"as.vector"                 0.86     9.05       0.86      9.05
#"as.data.frame.matrix"      0.32     3.37       1.44     15.16
#"paste0"                    0.20     2.11       0.22      2.32
#"is.na"                     0.20     2.11       0.20      2.11
#</snip>
Run Code Online (Sandbox Code Playgroud)

你看,大部分时间花在了ifelse,as.vectoras.data.frame.matrix.as.vector被称为[1]的地方并不十分明显,但其他两个是显而易见的.

你可以使用ifelse不是更好地获得性能ifelse,但它没有多大帮助.我会转而使用Rcpp f1for循环f2转换为编译代码(使用RStudio非常简单).显然你需要工具链,即在Windows上安装Rtools.

#include <Rcpp.h>
using namespace Rcpp;

double f1 (const double x) {
  if((x/18)>20) return x-(x/18); 
  if(x>20) return x-20; 
  if(x==0) return 0; 
  return x; 
}

// [[Rcpp::export]]
NumericVector f2_1 (const double init, const int n){ 
  NumericVector res(n);
  res(0) = init;
  for (int i=1; i<n; i++) res(i) = f1(res(i-1));
  return res;
}
Run Code Online (Sandbox Code Playgroud)

这比写出矢量化纯R解决方案(提供一个甚至存在)写得更快.

我们可以将您的其余部分定义f2为:

f_2a=function(v)
{
  x = f2_1(v, 160)
  x=x[!duplicated(x)]
  x=c(x,0)
  z=abs(diff(x))
  return(z)
}
Run Code Online (Sandbox Code Playgroud)

请注意我如何遗漏t,as.data.frame因为如果性能很重要,应该避免使用data.frames.它们更专为方便而非性能而设计.向量可以存储单行全数字data.frame的等效信息,我无法想象返回单行data.frames列表的好理由.

现在我们称之为功能:

L2a = lapply(i1, f_2a)
Run Code Online (Sandbox Code Playgroud)

让我们测试结果是否相等:

all.equal(L2[[1]], as.data.frame(t(L2a[[1]])))
#[1] TRUE
Run Code Online (Sandbox Code Playgroud)

现在比较时间:

system.time(llply(i1,compute))
# user  system elapsed 
#13.91    0.00   13.93 

system.time(lapply(i1, f_2a))
#user  system elapsed 
#0.26    0.00    0.27
Run Code Online (Sandbox Code Playgroud)

[1]在as.data.frame.matrix将矩阵拆分为列向量列表的循环中调用它.