我有一个操作,我想为数据框的每一行运行,更改一列。我是一个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列是无关紧要的)。由于运行时间在很大程度上取决于列数,因此我可以在排除不相关列的数据框上运行上述操作。那会让我失望一分钟。但是“使用嵌套索引进行循环”甚至是考虑我问题的最佳方式吗?
答案 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)
使用row
和col
对我来说似乎不那么复杂了:
t.df[col(t.df) == (row(t.df) %% 10) + 1] <- 99
我认为Tommy仍然更快,但使用row
和col
可能更容易理解。
答案 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 )