2

每当我用 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 
4

3 回答 3

2

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
于 2013-11-04T00:47:29.607 回答
1

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

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 数据框以获取具有最大值的列。

于 2013-11-02T01:05:14.873 回答
1

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

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

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

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

于 2013-11-02T03:02:54.297 回答