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)
@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)
使用row
并且col
对我来说似乎不那么复杂:
t.df[col(t.df) == (row(t.df) %% 10) + 1] <- 99
Run Code Online (Sandbox Code Playgroud)
我认为Tommy仍然更快,但使用row
并且col
可能更容易理解.
更新:在基准测试练习中添加了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)