R for loop比sapply更快

时间:2013-11-02 00:47:15

标签: r for-loop lapply sapply

每当我用apply语句替换for循环时,我的R脚本运行得更快,但这是一个例外。我仍然缺乏正确使用apply系列的经验,那么我可以对apply语句做什么比for循环更好(即变得更快)?

示例数据:

vc<-as.character(c("120,129,129,114","103,67,67,67,67,10,10,10,12","2,1,1,1,2,4,3,1,1,1,3,2,1,1","1,3,1,1,1,1,1,4",NA,"5","1,1,99","2,2,2,16,11,11,11,11,11,29,29,26,26,26,26,26,26,26,26,26,26,31,24,29,29,29,29,40,24,23,3,3,3,6,6,4,5,4,4,3,3,4,4,6,8,8,6,6,6,5,3,3,4,4,5,5,4,4,4,4,6,11,10,11,10,14,2,2,22,22,22,22,24,24,24,23,24,24,24,23,24,23,23,23,24,25,27,27,24,24,26,24,25,25,24,25,26,29,31,32,32,32,32,33,32,35,35,35,52,44,37,26","20,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,19,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,19,19,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,1,1,1,12,10","67,63,73,70,75,135,94,94,96,94,95,96,96,97,94,94,94,94,24,24,24,24,24,24,24,24,24,24,24,1,1,1"))

目标是填充数字矩阵m.res,其中每行包含vc中每个元素的top3值。这是for循环:

fx.test1 
function(vc) 
     {
     m.res<-matrix(ncol=3, nrow=length(vc))
     for (j in 1:length(vc)) 
      {vn<-as.numeric(unlist(strsplit(vc[j], split=","))) 
      vn[is.na(vn)]<-0; vn2<-rev(sort(vn)) 
      m.res[j,]<-vn2[1:3]
      }
     }

以下是我的“应用解决方案”。它为什么慢?我怎样才能让它更快?谢谢!

fx.test2
function(vc) 
    {
    m.res<-matrix(ncol=3, nrow=length(vc))
    vc[is.na(vc)]<-"0"
    ls.vc<-sapply(vc, function(x) tail(sort(as.numeric(unlist(strsplit(x, split=",")))),3), simplify=TRUE)
    #names(ls.vc)<-seq(1:length(vc))
    ls.vc2<-lapply(ls.vc, function(x) c(as.numeric(x), rep(0, times = 3 - length(x))))
    m.res<-as.matrix(t(as.data.frame(ls.vc)))
    return(m.res)
}

system.time(m.res<-fx.test1(vc))
#   user  system elapsed 
#  0.001   0.000   0.001 

system.time(m.res<-fx.test2(vc))
#   user  system elapsed 
#  0.003   0.000   0.003

更新:我遵循了@John的建议并生成了两个修剪过的&amp;真正等效的功能。实际上,我能够稍微提高一个lapply函数,但它仍然比for循环更低。如果您对如何针对速度优化这些功能有任何想法,请告诉我。谢谢大家。

fx.test3<-function(vc) 
{
    L<-strsplit(vc,split=",")
    m.res<-matrix(ncol=3, nrow=length(vc))
    for (j in 1:length(vc)) 
        {
        m.res[j,]<-sort(c(as.numeric(L[[j]]),rep(0,3)), decreasing=TRUE)[1:3]
    }
    return(m.res)
}



fx.test4<-function(vc) 
    {
        L<-strsplit(vc, split=",")
        D<-t(as.data.frame(lapply(L, function(X) {sort(c(as.numeric(X),rep(0,3)),decreasing=TRUE)[1:3]})))
        row.names(D)<-NULL
        m.res<-as.matrix(D)
        return(m.res)
    }

system.time(fx.test3(vc))
#   user  system elapsed 
#  0.001   0.000   0.001

system.time(fx.test4(vc))
#   user  system elapsed 
#  0.002   0.000   0.002 

3 个答案:

答案 0 :(得分:2)

UPDATE2&amp;可能的答案:

我现在将fx.test4简化为如下,现在它的速度与for循环相当。因此,正如@John指出的那样,额外的转换步骤使得lapply解决方案变得更慢。另外,正如@Ari B. Friedman和@ SimonO101所讨论的那样,*应用HAD更快的假设可能是错误的!谢谢大家!

fx.test5<-function(vc) 
    {
        L<-strsplit(vc, split=",")
        m.res<-t(sapply(seq_along(L), function(X){sort(c(as.numeric(L[[X]]),rep(0,3)),decreasing=TRUE)[1:3]}))
        return(m.res)
    }

fx.test5(vc)
      [,1] [,2] [,3]
 [1,]  129  129  120
 [2,]  103   67   67
 [3,]    4    3    3
 [4,]    4    3    1
 [5,]    0    0    0
 [6,]    5    0    0
 [7,]   99    1    1
 [8,]   52   44   40
 [9,]   20   19   19
[10,]  135   97   96

system.time(fx.test5(vc))
   user  system elapsed 
  0.001   0.000   0.001 

UPDATE3:事实上,在一个较长的例子中,* apply函数更快(通过头发)。

system.time(fx.test3(vc2))
#   user  system elapsed 
#  3.596   0.006   3.601 
system.time(fx.test5(vc2))
#   user  system elapsed 
#  3.355   0.006   3.359

答案 1 :(得分:1)

您可以使用splitstackshape包中的concat.split函数解决您的问题:

library(splitstackshape)
kk<-data.frame(vc)
nn<-concat.split(kk,split.col="vc",sep=",")
head(nn[1:10,1:4])
                           vc vc_1 vc_2 vc_3
1             120,129,129,114  120  129  129
2 103,67,67,67,67,10,10,10,12  103   67   67
3 2,1,1,1,2,4,3,1,1,1,3,2,1,1    2    1    1
4             1,3,1,1,1,1,1,4    1    3    1
5                        <NA>   NA   NA   NA
6                           5    5   NA   NA

您可以操作nn数据帧以获取具有最大值的列。

答案 2 :(得分:1)

你在循环中做了很多事情,applyfor,这不应该。 apply的主要特征并不是它比for更快,而是它鼓励表达式允许你尽可能地保持向量化(即尽可能少地循环)。 R特别慢的是解释一个函数调用,每次通过循环它都需要解释它遇到的每个函数调用。有时循环是不可避免的,但它们应尽可能小。

您的strsplit可以在第一个食物之外使用。那样你就叫它一次。那么您在unlist之前也不需要as.numeric。您还可以使用sort decreasing = FALSE而不是另外调用tail(尽管可能与[1:3]选择器一样快)。所有这些都可以在循环中反复调用循环中的函数解释。

您不必预先分配矩阵,因为您将同时生成所有值并将其整形为矩阵。

看看是否遵循该建议可以加快速度。