r - 缓慢的 data.frame 行分配

标签 r performance dataframe

我正在使用 RMongoDB,我需要用查询的值填充一个空的 data.frame。结果很长,大约有 200 万个文档(行)。

在进行性能测试时,我发现将值写入行的时间会随着数据框的维度而增加。也许这是一个众所周知的问题,而我是最后一个注意到它的人。

一些代码示例:

set.seed(20140430)
nreg <- 2e3
dfres <- as.data.frame(matrix(rep(NA,nreg*7),nrow=nreg,ncol=7))
system.time(dfres[1e3,] <-  c(1:5,"a","b"))
summary(replicate(10,system.time(dfres[sample(1:nreg,1),] <- c(1:5,"a","b"))[3]))

nreg <- 2e6
dfres <- as.data.frame(matrix(rep(NA,nreg*7),nrow=nreg,ncol=7))
system.time(dfres[1e3,] <-  c(1:5,"a","b"))
summary(replicate(10,system.time(dfres[sample(1:nreg,1),] <- c(1:5,"a","b"))[3]))

在我的机器上,200 万行 data.frame 的分配大约需要 0.4 秒。如果我想填充整个数据集,这需要很多时间。为了解决这个问题,这里进行了第二次模拟。
nreg <- seq(2e1,2e7,length.out=10)
te <- NULL 
for(i in nreg){
    dfres <- as.data.frame(matrix(rep(NA,i*7),nrow=i,ncol=7))
    te <- c(te,mean(replicate(10,{r <- sample(1:i,1); system.time(dfres[r,] <- c(1:5,"a","b"))[3]}) ) )
}
plot(nreg,te,xlab="Number of rows",ylab="Avg. time for 10 random assignments [sec]",type="o")
#rm(nreg,dfres,te)

enter image description here

问题 : 为什么会这样?有没有更快的方法来填充内存中的 data.frame?

最佳答案

让我们先从“列”开始,看看发生了什么,然后返回到行。
R 版本 < 3.1.0(不必要)复制整个 data.frame当您对它们进行操作时。例如:

## R v3.0.3
df <- data.frame(x=1:5, y=6:10)
dplyr:::changes(df, transform(df, z=11:15)) ## requires dplyr to be available

# Changed variables:
#           old            new           
# x         0x7ff9343fb4d0 0x7ff9326dfba8
# y         0x7ff9343fb488 0x7ff9326dfbf0
# z         <added>        0x7ff9326dfc38

# Changed attributes:
#           old            new           
# names     0x7ff934170c28 0x7ff934308808
# row.names 0x7ff934551b18 0x7ff934308970
# class     0x7ff9346c5278 0x7ff935d1d1f8
您可以看到添加"new"列导致了“旧”列的副本(地址不同)。属性也被复制。最让人头疼的是,这些副本是深副本,而不是浅副本。

Shallow copies only copy the vector of column pointers, not the entire data, where as deep copies copy everything (which is unnecessary here).


然而,在 R v3.1.0 中,有一些不错的欢迎变化,因为“旧”列没有被深度复制。全部归功于 R 核心开发团队。
## R v3.1.0
df <- data.frame(x=1:5, y=6:10)
dplyr:::changes(df, transform(df, z=11:15)) ## requires dplyr to be available

# Changed variables:
#           old     new           
# z         <added> 0x7f85d328dda8

# Changed attributes:
#           old            new           
# names     0x7f85d1459548 0x7f85d297bec8
# row.names 0x7f85d2c66cd8 0x7f85d2bfa928
# class     0x7f85d345cab8 0x7f85d2d6afb8
您可以看到列 xy根本没有改变(因此不存在于 changes 函数调用的输出中)。这是一个巨大(且受欢迎)的改进!
到目前为止,我们研究了在 R <3.1.0 和 v3.1.0 中添加列的问题

现在,来到你的问题:那么,“行”呢?让我们先考虑旧版本的 R,然后再回到 R v3.1.0。
## R v3.0.3
df <- data.frame(x=1:5, y=6:10)
df.old <- df
df$y[1L] <- -6L
dplyr:::changes(df.old, df)

# Changed variables:
#           old            new           
# x         0x7f968b423e50 0x7f968ac6ba40
# y         0x7f968b423e98 0x7f968ac6bad0
# 
# Changed attributes:
#           old            new           
# names     0x7f968ab88a28 0x7f968abca8e0
# row.names 0x7f968abb6438 0x7f968ab22bb0
# class     0x7f968ad73e08 0x7f968b580828
我们再次看到更改列 y已导致复制列x在旧版本的 R 中也是如此。
## R v3.1.0
df <- data.frame(x=1:5, y=6:10)
df.old <- df
df$y[1L] <- -6L
dplyr:::changes(df.old, df)

# Changed variables:
#           old            new           
# y         0x7f85d3544090 0x7f85d2c9bbb8
# 
# Changed attributes:
#           old            new           
# row.names 0x7f85d35a69a8 0x7f85d35a6690
我们在 R v3.1.0 中看到了很好的改进,这导致了只有列 y 的副本。 .再一次,R v3.1.0 有了很大的改进! R 的 copy-on-modify 变得更加明智。

But still, using data.table's assignment by reference semantics, we can do one step better - not copy even the y column as is the case in R v3.1.0.

The idea being: as long as the type of the object you assign to a column at certain indices don't change (here, column y is integer - so as long as you assign an integer back to y), we really can do it without having to copy by modifying in-place (by reference).

Why? Because we don't have to allocate/re-allocate anything here. As an example, if you had assigned a double/numeric type, which requires 8 bytes of storage as opposed to 4-bytes of storage for integer column y, then we've to create a new column y and copy values back.


也就是说,我们可以使用 data.table 通过引用进行子分配.我们可以使用 :=set()去做这个。我将演示使用 set()这里。
现在,这是与基数 R 和 data.table 的比较分别针对 R v3.0.3 和 v3.1.0 的 2,000 到 20,000,000 行的 10 倍数的数据。 You can find the code here .
与 R v3.0.3 进行比较的图:
R3.0.3 vs data.table
与 R v3.1.0 进行比较的图:
3.1.0 vs data.table
R v3.0.3、R v3.1.0 和 data.table 在 10 次复制的 2000 万行上的最小值、中值和最大值是:
      type    min  median    max
base_3.0.3  10.05   10.70  18.51
base_3.1.0   1.67    1.97   5.20
data.table   0.04    0.04   0.05

Note: You can see the complete timings in this gist.


这清楚地表明了 R v3.1.0 中的改进,但也表明正在更改的列仍在被复制并且仍然消耗一些时间,这可以通过引用 data.table 中的子赋值来克服。 .
HTH

关于r - 缓慢的 data.frame 行分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23388893/

相关文章:

r - 为什么当 class(a$alpha) 返回 "-none-"时 summary(a$alpha) 返回 class "numeric"?

r - 按组将不同的功能应用于不同的列集

r - 跟踪 R 脚本文件中未编码字符的确切位置

javascript - 我怎样才能提高使用 Javascript 定位我的 html 元素的效率

python - 如何从我 BeautifulSoup 结果中删除标签(例如 : Address = [a, b,c,d,r......])

python - 使用python实现异构csv的数据结构

r - R 中用于验证 data.frame 上特定列是否存在的函数

performance - Kotlin:coroutineScope 比 GlobalScope 慢

Ruby:避免多个 "or"语句?

python - Pandas - 提取以特定字符开头的字符串