数据框行赋值缓慢

7

我正在使用RMongoDB,并需要用查询结果填充一个空数据框。结果非常长,大约有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万行数据框的赋值需要约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

问题: 为什么会出现这种情况?有没有更快的方式在内存中填充数据框?


一种解决方法:它包括将临时数据框中的行分配为1e4行,直到其已满。然后我将其与最终数据框进行rbind,并重新填充小的数据框。 - Emer
1个回答

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

您可以看到添加“new”列导致“old”列的副本(地址不同)。此外,属性也被复制了。最让人困扰的是这些副本是深度副本,而不是浅层副本

浅层副本只复制列指针的向量,而不是整个数据,而深层副本复制一切(在这里是不必要的)。

然而,在R v3.1.0中,有一个很好的变化,即“old”列没有被深度复制。所有荣誉归功于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

我们再次看到,在旧版本的R中更改列y导致复制列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中看到很好的改进,这导致了仅复制justy。再次感受到R v3.1.0的巨大改进! R的修改复制已经更加智能。

但是,仍然可以通过使用data.table的按引用赋值语义进一步改进-即不要复制y列,这在R v3.1.0中是一种情况。

想法是:只要分配给某些索引的列的对象的类型不改变(这里,列y是整数-因此只要将整数赋回y),我们就可以实现原地修改(按引用)而不必进行复制。

为什么?因为我们不需要在此处分配/重新分配任何东西。例如,如果您分配了一个需要8字节存储空间而不是整数列y的4字节存储空间的double / numeric类型,则必须创建一个新的列y并将值复制回去。

也就是说,我们可以使用data.table进行按引用子分配。我们可以使用:=set()来实现这一点。我将在此处演示使用set()

现在,以下是您的数据分别针对具有2,000到20,000,000行的R v3.0.3和v3.1.0以及data.table的比较。 您可以在此处找到代码.

与R v3.0.3进行比较的图表:

R3.0.3 vs data.table

与R v3.1.0进行比较的图表: 3.1.0 vs data.table

20,000,000行数据在10次复制中R v3.0.3、R v3.1.0和data.table的最小值、中位数和最大值如下:

      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

注意:您可以在此 gist中看到完整的时间。

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

希望对您有所帮助。


2
非常详细的答案。 - BrodieG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接