r:嵌套索引的for循环操作运行速度超慢

时间:2011-11-30 18:40:50

标签: performance r for-loop nested vectorization

我有一个操作,我想为数据框的每一行运行,更改一列。我是一个apply / ddply / sqldf人,但是当它们有意义时我会使用循环,我认为这是其中之一。这种情况很棘手,因为要更改的列取决于按行更改的信息;根据一个单元格中的信息,我应该只更改该行中的十个其他单元格中的一个。对于75列和20000行,操作需要10分钟,当我的脚本中的每个其他操作需要0-5秒,最多10秒。我已经将问题解决了下面非常简单的测试用例。

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time(
 for (i in 1:nrow(t.df)) {
 t.df[i,(t.df[i,1]%%10 + 1)] <- 99
 }
)

十列需要70秒,而ncol = 50需要360秒。太疯狂了。循环是错误的方法吗?是否有更好,更有效的方法来做到这一点?

我已经尝试将嵌套术语(t.df [i,1] %% 10 + 1)初始化为for循环外的列表。它节省了大约30秒(10分钟内),但使上面的示例代码更加复杂。所以它有所帮助,但它不是解决方案。

在准备此测试用例时,我当前最好的想法。对我来说,只有10列是相关的(75-11列是无关紧要的)。由于运行时间在很大程度上取决于列数,因此我可以在排除不相关列的数据框上运行上述操作。那会让我失望一分钟。但是“使用嵌套索引进行循环”甚至是考虑我问题的最佳方式吗?

5 个答案:

答案 0 :(得分:11)

似乎真正的瓶颈是以data.frame的形式提供数据。我假设在你真正的问题中你有一个令人信服的理由使用data.frame。有没有什么方法可以将数据转换为可以保留在矩阵中的方式?

顺便说一句,很棒的问题和一个非常好的例子。

以下是对矩阵的循环速度比data.frames快多少的说明:

> n <- 20000
> t.df <- (matrix(1:5000, ncol=10, nrow=n) )
> system.time(
+   for (i in 1:nrow(t.df)) {
+     t.df[i,(t.df[i,1]%%10 + 1)] <- 99
+   }
+ )
   user  system elapsed 
  0.084   0.001   0.084 
> 
> n <- 20000
> t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
> system.time(
+   for (i in 1:nrow(t.df)) {
+     t.df[i,(t.df[i,1]%%10 + 1)] <- 99
+   }
+   )
   user  system elapsed 
 31.543  57.664  89.224 

答案 1 :(得分:7)

@JD Long是正确的,如果t.df可以表示为矩阵,那么事情就会快得多。

...然后你可以实际上对整个事物进行矢量化,以便快速闪电:

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time({
  m <- as.matrix(t.df)
  m[cbind(seq_len(nrow(m)), m[,1]%%10L + 1L)] <- 99
  t2.df <- as.data.frame(m)
}) # 0.00 secs

不幸的是,我在这里使用的矩阵索引似乎不适用于data.frame

修改 我在data.frame创建索引的逻辑矩阵的变体,几乎一样快:

n <- 20000
t.df <- data.frame(matrix(1:5000, ncol=10, nrow=n) )
system.time({
  t2.df <- t.df

  # Create a logical matrix with TRUE wherever the replacement should happen
  m <- array(FALSE, dim=dim(t2.df))
  m[cbind(seq_len(nrow(t2.df)), t2.df[,1]%%10L + 1L)] <- TRUE

  t2.df[m] <- 99
}) # 0.01 secs

答案 2 :(得分:7)

使用rowcol对我来说似乎不那么复杂了:

t.df[col(t.df) == (row(t.df) %% 10) + 1]  <- 99

我认为Tommy仍然更快,但使用rowcol可能更容易理解。

答案 3 :(得分:7)

更新:在基准测试练习中添加了Tommy解决方案的矩阵版本。

你可以对它进行矢量化。这是我的解决方案,并与循环

进行比较
n <- 20000
t.df <- (matrix(1:5000, ncol=10, nrow=n))

f_ramnath <- function(x){
  idx <- x[,1] %% 10 + 1
  x[cbind(1:NROW(x), idx)] <- 99  
  return(x)
}

f_long <- function(t.df){
  for (i in 1:nrow(t.df)) {
    t.df[i,(t.df[i,1]%%10 + 1)] <- 99
  }
  return(t.df)
}

f_joran <- function(t.df){
  t.df[col(t.df) == (row(t.df) %% 10) + 1]  <- 99
  return(t.df)
}

f_tommy <- function(t.df){
  t2.df <- t.df
  # Create a logical matrix with TRUE wherever the replacement should happen
  m <- array(FALSE, dim=dim(t2.df))
  m[cbind(seq_len(nrow(t2.df)), t2.df[,1]%%10L + 1L)] <- TRUE
  t2.df[m] <- 99
  return(t2.df)
}

f_tommy_mat <- function(m){
  m[cbind(seq_len(nrow(m)), m[,1]%%10L + 1L)] <- 99
}

为了比较不同方法的表现,我们可以使用rbenchmark

library(rbenchmark)
benchmark(f_long(t.df), f_ramnath(t.df), f_joran(t.df), f_tommy(t.df), 
  f_tommy_mat(t.df), replications = 20,  order = 'relative',
  columns = c('test', 'elapsed', 'relative')

               test elapsed  relative
5 f_tommy_mat(t.df)   0.135  1.000000
2   f_ramnath(t.df)   0.172  1.274074
4     f_tommy(t.df)   0.311  2.303704
3     f_joran(t.df)   0.705  5.222222
1      f_long(t.df)   2.411 17.859259

答案 4 :(得分:1)

当您需要混合列类型(因此无法使用matrix)的另一个选项是data.table中的:=?":="的示例:

require(data.table)
m = matrix(1,nrow=100000,ncol=100)
DF = as.data.frame(m)
DT = as.data.table(m)    
system.time(for (i in 1:1000) DF[i,1] <- i)
    # 591 seconds 
system.time(for (i in 1:1000) DT[i,V1:=i])
    # 1.16 seconds  ( 509 times faster )