缓慢的data.frame行分配

Eme*_*mer 7 performance r dataframe

我正在使用RMongoDB,我需要使用查询的值填充空data.frame.结果很长,约有2百万个文件(行).

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

一些代码示例:

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]))
Run Code Online (Sandbox Code Playgroud)

在我的机器上,2百万行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)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

Aru*_*run 11

让我们首先从"列"开始,看看发生了什么,然后返回行.

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
Run Code Online (Sandbox Code Playgroud)

您可以看到添加"新"列导致"旧"列的副本(地址不同).还会复制属性.最容易咬的是这些副本是深拷贝,而不是浅拷贝.

浅拷贝只复制列指针的向量,而不是整个数据,深拷贝复制所有内容(这里不需要).

但是,在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
Run Code Online (Sandbox Code Playgroud)

您可以看到列x并且y根本没有更改(因此不会出现在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
Run Code Online (Sandbox Code Playgroud)

我们再次看到更改列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
Run Code Online (Sandbox Code Playgroud)

我们看到R v3.1.0中的很好的改进,它导致了just列的副本y.再次,R v3.1.0的重大改进!R的复制修改变得更加明智.

但是,使用引用语义data.table赋值,我们可以做得更好一步 - 不像yR v3.1.0中的情况那样复制列.

这个想法是:只要在某些索引处分配给某个列的对象的类型不会改变(这里,列y是整数 - 只要你将一个整数分配回来y),我们真的可以做到这一点通过就地修改(通过引用)进行复制.

为什么?因为我们不必在这里分配/重新分配任何东西.例如,如果您指定了一个双/数字类型,这需要8个字节的存储空间而不是整数列的4个字节的存储空间y,那么我们将创建一个新列y并将值复制回来.

也就是说,我们可以通过引用进行子分配data.table.我们可以使用:=set()执行此操作.我将set()在这里演示使用.

现在,这里是与基数R和data.table数据的比较,其中2,000到20,000,000行是10的倍数,分别是R v3.0.3和v3.1.0.你可以在这里找到代码.

用于与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的min,median和max以及2000万行的data.table,重复10次:

      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
Run Code Online (Sandbox Code Playgroud)

注意:您可以在此要点中查看完整的时间安排.

这清楚地表明作为R V3.1.0的改善,但还示出了正在被改变的列仍然被复制,并且仍然消耗的某个时候,这是通过克服由参考子分配data.table.

HTH

  • 非常详细的答案. (2认同)