如何在不增加内存消耗的情况下绑定data.table?

4
我有几个大的数据表 dt_1, dt_2, ..., dt_N,它们具有相同的列。我想将它们绑定成一个单独的datatable。如果我使用
dt <- rbind(dt_1, dt_2, ..., dt_N)

或者
dt <- rbindlist(list(dt_1, dt_2, ..., dt_N))

如果将 dt_1,dt_2,...,dt_N 绑定在一起,则内存使用量大约是其所需量的两倍。有没有一种方法可以将它们绑定在一起,而不会显著增加内存消耗?请注意,一旦它们被组合在一起,我就不再需要 dt_1, dt_2, ..., dt_N


我可能错了,但是你是否考虑过在合并 dt 后从你的环境中删除 dt_1, dt_2 等变量? - Heroka
是的,我之后确实将它们删除了。但在绑定期间,内存仍然会翻倍。 - imsc
请看我的答案,可能会稍微慢一些,但使用删除绑定可能更有效地利用内存。 - Heroka
@imsc 你是在问关于按引用rbind的问题吗?听起来很酷,但不确定是否可行。 - jangorecki
@jangorecki。这就是我想要的……既避免了复制,又避免了内存消耗。 - imsc
4个回答

4

另一种方法是使用临时文件来“绑定”:

nobs=10000
d1 <- d2 <- d3 <-  data.table(a=rnorm(nobs),b=rnorm(nobs))
ll<-c('d1','d2','d3')
tmp<-tempfile()

# Write all, writing header only for the first one
for(i in seq_along(ll)) {
  write.table(get(ll[i]),tmp,append=(i!=1),row.names=FALSE,col.names=(i==1))
}

# 'Cleanup' the original objects from memory (should be done by the gc if needed when loading the file
rm(list=ll)

# Read the file in the new object
dt<-fread(tmp)

# Remove the file
unlink(tmp)

显然比rbind方法慢,但如果您的内存有争用,这不会比要求系统交换内存页变慢。

当然,如果您的原始对象首先从文件加载,请优先使用另一个专门用于文件处理的工具(如cat、awk等)连接文件,然后再在R中加载。


3

当您绑定数据表后,可以将其删除,双倍的内存使用是由新数据框架中包含的副本引起的。

举例说明:

#create some data
nobs=10000
d1 <- d2 <- d3 <-  data.table(a=rnorm(nobs),b=rnorm(nobs))
dt <- rbindlist(list(d1,d2,d3))

然后我们可以查看每个对象的内存使用情况 来源

sort( sapply(ls(),function(x){object.size(get(x))}))
  nobs     d1     d2     d3     dt 
    48 161232 161232 161232 481232 

如果内存使用量非常大,单独的数据表和组合数据表无法共存,我们可以(虽然很震惊,但我认为这种情况值得这样做,因为数据表数量很少,易于阅读和理解)使用 for 循环和 get 创建组合数据表,并同时删除各个单独的数据表。
mydts <- c("d1","d2","d3") #vector of datatable names

dt<- data.table() #empty datatable to bind objects to

for(d in mydts){
  dt <- rbind(dt, get(d))
  rm(list=d)
  gc() #garbage collection
}

或许值得澄清的是:d1 <- d2 <- d3 只占用内存中第一个的空间。<- DT 创建了一个指向 DT 的新指针,而 <- copy(DT) 则会创建一个新副本(使空间消耗翻倍)。尝试 address(d1)address(d2),它们应该具有相同的值。tables() 命令也很方便用于检查内存。此外,不确定为什么要在这里使用 get... L <- list(d1,d2,d3) 可以迭代,并且也只是指针,我认为:sapply(L, address) - Frank

2
我想<<-get可以帮助你解决这个问题。 更新:<<-不是必要的。
df1 <- data.frame(x1=1:4, x2=letters[1:4], stringsAsFactors=FALSE)
df2 <- df1
df3 <- df1

dt.lst <- c("df2", "df3")

for (i in dt.lst) {
  df1 <- rbind(df1, get(i))
  rm(list=i)
}

df1

0

感谢其他出色的答案,如果您的数据框包含在一个大的数据框列表中。您可以使用NULL赋值(在this answer中解释)或者within(在this answer中解释)来在每次迭代中从列表中删除数据框。

# Large list if data frames
l_df <- list(head(iris), iris[c(92:95),], tail(iris))
df_stack <- data.table::data.table()
# As long as the list is not empty,
# Bind the first list item and remove it
while(!identical(l_df, list())){
    df_stack <- rbind(df_stack, l_df[[1]])
    l_df[1] <- NULL
}

这种方式比将数据框绑定在一起所需的内存更少:

l_df <- list(head(iris), iris[c(92:95),], tail(iris))
dfdt = data.table::rbindlist(l_df)

并且应该返回一个类似的数据框

identical(df_stack, dfdt)
# [1] TRUE

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