我在r中有一个很大的性能问题。我写了一个迭代data.frame对象的函数。它只是简单地向data.frame添加一个新列并累积一些东西。(操作简单)。data.frame大约有850K行。我的电脑还在工作(大约10小时了),我不知道运行时间。

dayloop2 <- function(temp){
    for (i in 1:nrow(temp)){    
        temp[i,10] <- i
        if (i > 1) {             
            if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) { 
                temp[i,10] <- temp[i,9] + temp[i-1,10]                    
            } else {
                temp[i,10] <- temp[i,9]                                    
            }
        } else {
            temp[i,10] <- temp[i,9]
        }
    }
    names(temp)[names(temp) == "V10"] <- "Kumm."
    return(temp)
}

有什么办法可以加快这次行动吗?


当前回答

处理数据。表是一个可行的选择:

n <- 1000000
df <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
colnames(df) <- paste("col", 1:9, sep = "")

library(data.table)

dayloop2.dt <- function(df) {
  dt <- data.table(df)
  dt[, Kumm. := {
    res <- .I;
    ifelse (res > 1,             
      ifelse ((col6 == shift(col6, fill = 0)) & (col3 == shift(col3, fill = 0)) , 
        res <- col9 + shift(res)                   
      , # else
        res <- col9                                 
      )
     , # else
      res <- col9
    )
  }
  ,]
  res <- data.frame(dt)
  return (res)
}

res <- dayloop2.dt(df)

m <- microbenchmark(dayloop2.dt(df), times = 100)
#Unit: milliseconds
#       expr      min        lq     mean   median       uq      max neval
#dayloop2.dt(df) 436.4467 441.02076 578.7126 503.9874 575.9534 966.1042    10

如果忽略条件过滤可能带来的收益,它会非常快。显然,如果您可以在数据子集上进行计算,则会有所帮助。

其他回答

我不喜欢重写代码……当然,ifelse和lapply是更好的选择,但有时很难匹配。

我经常使用data.frames,就像使用df$var[I]这样的列表一样

这里有一个虚构的例子:

nrow=function(x){ ##required as I use nrow at times.
  if(class(x)=='list') {
    length(x[[names(x)[1]]])
  }else{
    base::nrow(x)
  }
}

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
})

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  d=as.list(d) #become a list
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
  d=as.data.frame(d) #revert back to data.frame
})

data.frame版本:

   user  system elapsed 
   0.53    0.00    0.53

表版本:

   user  system elapsed 
   0.04    0.00    0.03 

使用向量列表比data.frame快17倍。

对于为什么内部data.frames在这方面这么慢,有什么意见吗?有人会认为它们像列表一样运作……

为了更快地编写代码,使用class(d)='list'而不是d=as.list(d)和class(d)='data.frame'

system.time({
  d=data.frame(seq=1:10000,r=rnorm(10000))
  d$foo=d$r
  d$seq=1:5
  class(d)='list'
  mark=NA
  for(i in 1:nrow(d)){
    if(d$seq[i]==1) mark=d$r[i]
    d$foo[i]=mark
  }
  class(d)='data.frame'
})
head(d)

加速R代码的一般策略

首先,弄清楚慢的部分到底在哪里。没有必要优化运行速度不慢的代码。对于少量的代码,简单地思考一下就可以了。如果失败了,RProf和类似的分析工具可能会有所帮助。

一旦你找到了瓶颈,考虑更有效的算法来做你想做的事情。如果可能,计算应该只运行一次,因此:

存储结果并访问它们,而不是重复地重新计算 从循环中取出非依赖于循环的计算 避免不必要的计算(例如,不要使用固定搜索的正则表达式)

使用更有效的函数可以产生中等或较大的速度增益。例如,paste0会产生很小的效率提高,而. colsum()及其相关项会产生更明显的效率提高。Mean特别慢。

这样你就可以避免一些特别常见的问题:

Cbind会很快让你慢下来。 初始化数据结构,然后填充它们,而不是逐个展开它们 时间。 即使使用预分配,您也可以切换到按引用传递方法而不是按值传递方法,但这样做可能不值得这么麻烦。 看看R地狱更多的陷阱要避免。

尝试更好的向量化,这通常有帮助,但并不总是有帮助。在这方面,固有的向量化命令,如ifelse、diff等,将比apply命令系列提供更多的改进(对于编写良好的循环,apply命令系列几乎没有提高速度)。

您还可以尝试为R函数提供更多信息。例如,使用vapply而不是sapply,并在读入基于文本的数据时指定colClasses。速度的增加取决于你消除了多少猜测。

接下来,考虑优化的包:数据。在数据操作和读取大量数据(fread)时,表包可以在可能的情况下产生巨大的速度增益。

接下来,尝试通过调用R的更有效的方法来提高速度:

Compile your R script. Or use the Ra and jit packages in concert for just-in-time compilation (Dirk has an example in this presentation). Make sure you're using an optimized BLAS. These provide across-the-board speed gains. Honestly, it's a shame that R doesn't automatically use the most efficient library on install. Hopefully Revolution R will contribute the work that they've done here back to the overall community. Radford Neal has done a bunch of optimizations, some of which were adopted into R Core, and many others which were forked off into pqR.

最后,如果以上所有方法仍然不能让您达到所需的速度,您可能需要使用更快的语言来处理速度较慢的代码片段。Rcpp和内联的组合使得用c++代码替换算法中最慢的部分特别容易。例如,这里是我第一次尝试这样做,它甚至击败了高度优化的R解。

如果在这之后你仍然有麻烦,你只是需要更多的计算能力。看看并行化(http://cran.r-project.org/web/views/HighPerformanceComputing.html)或者甚至是基于gpu的解决方案(gpu-tools)。

其他指引的连结

http://www.noamross.net/blog/2013/4/25/faster-talk.html

看一下{purrr}中的accumulate()函数:

dayloop_accumulate <- function(temp) {
  temp %>%
    as_tibble() %>%
     mutate(cond = c(FALSE, (V6 == lag(V6) & V3 == lag(V3))[-1])) %>%
    mutate(V10 = V9 %>% 
             purrr::accumulate2(.y = cond[-1], .f = function(.i_1, .i, .y) {
               if(.y) {
                 .i_1 + .i
               } else {
                 .i
               }
             }) %>% unlist()) %>%
    select(-cond)
}

在R中,您通常可以通过使用apply族函数来加速循环处理(在您的示例中,可能是复制)。看一下提供进度条的plyr包。

另一种选择是完全避免循环,用向量化算法代替它们。我不确定你到底在做什么,但你可能可以将你的函数一次性应用到所有行:

temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]

这将会快得多,然后你可以用你的条件过滤行:

cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]

向量化算术需要更多的时间和思考问题,但有时可以节省几个数量级的执行时间。

如果你在使用for循环,你很可能会像编写C或Java或其他语言一样编写R。正确向量化的R代码非常快。

以这两段简单的代码为例,按顺序生成一个10,000个整数的列表:

第一个代码示例是如何使用传统的编码范式编写循环代码。它需要28秒才能完成

system.time({
    a <- NULL
    for(i in 1:1e5)a[i] <- i
})
   user  system elapsed 
  28.36    0.07   28.61 

你可以通过简单的预分配内存来获得几乎100倍的性能提升:

system.time({
    a <- rep(1, 1e5)
    for(i in 1:1e5)a[i] <- i
})

   user  system elapsed 
   0.30    0.00    0.29 

但是使用冒号操作符的基本R向量操作:这个操作几乎是瞬时的:

system.time(a <- 1:1e5)

   user  system elapsed 
      0       0       0