【问题标题】:R for loop faster than sapplyR for 循环比 sapply 快
【发布时间】:2013-11-13 07:12:50
【问题描述】:

每当我用 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 中每个元素的前 3 个值。这是 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 的建议并生成了两个经过修剪且真正等效的函数。事实上,我能够稍微加快 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 

【问题讨论】:

  • apply 系列命令很少比循环快。你预先分配好。你能用真正的矢量化函数(sum、rowSum 等)重写吗?你看过这个帖子吗? stackoverflow.com/questions/2908822/…
  • @AriB.Friedman 我不太同意这一点。 *apply 如果在正确的上下文中使用,通常可以快得多(但请记住,它们本身仍然是循环)。相反,如果在正确的上下文中使用for 循环实际上可以更有效且有意义(即当您期望副作用而不是返回值时 - 我讨厌看到人们使用lapply 来编写一堆data.frame的文件例如)。
  • @SimonO101 我同意有时它们可​​以更快,但在大多数情况下并非如此。参考文献stackoverflow.com/questions/2275896/… 。一个常见的误解是,因为*apply 更像“R”并且其他类似 R 的编程方式(例如,使用sum 的真正矢量化等)更快,所以*apply 总是快得多。而且这似乎通常不是真的。
  • @SimonO101 澄清一下:我不认为你持有这种误解——你是从一个看到异常的地方来的。但大多数人不知道规则,更不用说例外......
  • 谢谢你们 - 这正是我发布问题的原因:我曾期望 apply 会更快,但如果不是,我想知道原因。 @AriB.Friedman,这两个帖子非常有帮助 - 我正在经历和试验。

标签: r for-loop lapply sapply


【解决方案1】:

UPDATE2 和可能的答案:

我现在将 fx.test4 简化如下,现在它的速度与 for 循环相当。因此,正如@John 指出的那样,正是额外的转换步骤使 lapply 解决方案变慢了。此外,@Ari B. Friedman 和@SimonO101 讨论的 *apply 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 

更新 3:确实,在更长的示例中,*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

【讨论】:

    【解决方案2】:

    您的问题可以使用 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 数据框以获取具有最大值的列。

    【讨论】:

    • 谢谢@Metrics。我试图更好地理解 apply 系列和 R 本身,但很高兴知道有一个方便的包可以用于此目的。
    【解决方案3】:

    您在循环中做了很多事情,apply 或 for,这是不应该的。 apply 的主要特点与其说它比for 快​​,不如说它鼓励表达,使您可以尽可能多地保持向量化(即循环中尽可能少)。 R 特别慢的事情是解释函数调用,并且每次通过循环它都需要解释它遇到的每个函数调用。有时循环是不可避免的,但应该尽可能小。

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

    您不必预先分配矩阵,因为您将一次生成所有值并将它们塑造成一个矩阵。

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

    【讨论】:

    • 谢谢你,@John,确实我还没有“得到”申请家庭。是的,我不必在 fx2 中预先分配,我只是为了保持一致。我怎样才能在外面做strsplit?也感谢您的其他指点!
    • 正如 Yoda R 所说,“不要问,做”(也许我会在这里改名并用这种方式写下我的答案)。 strsplit(vc) 的结果是什么样的?
    • 天哪。这要容易得多 - 我认为它不会那么简单,所以我认为你的意思是更复杂的。现在我只需要将它从列表中删除,但应该更容易!谢谢!
    • 顺便说一句,您的特定 applu 和 for 循环版本之间的速度差异并不是 apply 和 for 本身。您还进行了许多其他更改,例如使用 rev 和 [1:3] 而不是 tail。
    • 是的,你完全正确 - 我不得不稍微改变流程以使 apply 语句对我有用,但后来我也没有回去更改 for 循环。我很矛盾,我应该这样做;我遇到过其他例子,其中向量化函数的选择很重要。好点子!
    猜你喜欢
    • 1970-01-01
    • 2017-12-26
    • 1970-01-01
    • 1970-01-01
    • 2017-11-29
    • 2013-01-12
    • 2021-10-01
    • 2019-04-15
    • 2012-07-06
    相关资源
    最近更新 更多