我正在使用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]))
在我的机器上,2 milion rows 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)
问题:为什么会这样?是否有更快的方法来填充内存中的data.frame?
答案 0 :(得分:11)
让我们首先从“列”开始,看看发生了什么,然后返回行。
R版本&lt; 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
您可以看到添加“新”列已导致“旧”列的副本(地址不同)。还会复制属性。最容易咬的是这些副本是深层副本,而不是浅层副本。
浅拷贝只复制列指针的向量,而不是整个数据,深拷贝复制一切(这里不需要)。
然而,在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
您可以看到列x
和y
根本没有更改(因此不会出现在changes
函数调用的输出中)。这是一个巨大的(和欢迎)改进!
到目前为止,我们在R&lt; 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
导致在旧版本的R中复制列x
。
## 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的复制修改变得更加明智。
但是,通过引用语义使用
data.table
的赋值,我们可以做得更好一步 - 不像R v3中的情况那样复制y
列.1.0。这个想法是:只要在某些索引处分配给某个列的对象的类型不会改变(这里,列
y
是整数 - 所以只要你将一个整数赋给{ {1}}),我们真的可以做到这一点,而无需通过修改就地(通过引用)进行复制。为什么呢?因为我们不必在这里分配/重新分配任何东西。例如,如果您分配了一个双/数字类型,这需要8个字节的存储空间而不是整数列
y
的4个字节的存储空间,那么我们将创建一个新的列{{1}然后复制值。
也就是说,我们可以使用y
通过引用进行子分配。我们可以使用y
或data.table
来执行此操作。我将在此处演示使用:=
。
现在,这里是对数据的基数R和set()
的比较,其中2,000到20,000,000行的倍数为10,而R v3.0.3和v3.1.0则分开。 You can find the code here
用于与R v3.0.3进行比较的图:
用于与R v3.1.0进行比较的图:
R v3.0.3,R v3.1.0的min,median和max以及2000万行的data.table,重复10次:
set()
注意:您可以在this gist中看到完整的时间安排。
这清楚地显示了R v3.1.0的改进,但也表明正在更改的列仍在被复制,并且仍然会消耗一段时间,这可以通过通过引用进行子分配来克服在data.table
。
HTH