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

enf*_*ion 15 performance for-loop nested r 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
 }
)
Run Code Online (Sandbox Code Playgroud)

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

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

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

JD *_*ong 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 
Run Code Online (Sandbox Code Playgroud)

  • 当他在芝加哥时,我抓住了Josh Ulrich一次,并把他带到办公室审查我的一些代码.我想他肯定会向我展示所有这些花哨的功夫,让我的代码更快.他耸耸肩说,用他冷静的方式说,"尝试使用矩阵更多,数据框架更少",然后我们去喝咖啡.最好.代码审查.EVAR.:) (4认同)

Tom*_*mmy 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
Run Code Online (Sandbox Code Playgroud)

不幸的是,我在这里使用的矩阵索引似乎不适用于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
Run Code Online (Sandbox Code Playgroud)


jor*_*ran 7

使用row并且col对我来说似乎不那么复杂:

t.df[col(t.df) == (row(t.df) %% 10) + 1]  <- 99
Run Code Online (Sandbox Code Playgroud)

我认为Tommy仍然更快,但使用row并且col可能更容易理解.


Ram*_*ath 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
}
Run Code Online (Sandbox Code Playgroud)

为了比较不同方法的性能,我们可以使用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
Run Code Online (Sandbox Code Playgroud)