从R写入Excel时处理java.lang.OutOfMemoryError

Ric*_*ton 78 r xlsx

xlsx软件包可用于从R读取和写入Excel电子表格.不幸的是,即使对于中等大小的电子表格,java.lang.OutOfMemoryError也可能发生.特别是,

library(xlsx)
set.seed(19790801)
n_sheets <- 40
the_data <- replicate(
  n_sheets,
  {
    n_rows <- sample(2e5, 1)
    data.frame(
      x = runif(n_rows),
      y = sample(letters, n_rows, replace = TRUE)
    )
  },
  simplify = FALSE
)
names(the_data) <- paste("Sheet", seq_len(n_sheets))
Run Code Online (Sandbox Code Playgroud)

(其他相关的例外也是可能的,但更罕见.)

在阅读电子表格时,有人提出了类似的问题.

将大型xlsx文件导入R?

使用Excel电子表格作为CSV上的数据存储介质的主要优点是,您可以在同一文件中存储多个工作表,因此我们在此处考虑每个工作表要写入一个数据框的数据框列表.此示例数据集包含40个数据框,每个数据框具有两列,最多200k行.它设计得足够大,有问题,但你可以通过改变n_sheets和改变大小n_rows.

wb <- createWorkbook()  
for(i in seq_along(the_data))
{
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}
saveWorkbook(wb, "test.xlsx")  
Run Code Online (Sandbox Code Playgroud)

将此文件写入文件的自然方法是使用创建工作簿createWorkbook,然后循环遍历每个数据框调用createSheetaddDataFrame.最后,可以使用工作簿将文件写入文件saveWorkbook.我已经在循环中添加了消息,以便更容易看到它落在哪里.

library(xlsx)
set.seed(19790801)
n_sheets <- 40
the_data <- replicate(
  n_sheets,
  {
    n_rows <- sample(2e5, 1)
    data.frame(
      x = runif(n_rows),
      y = sample(letters, n_rows, replace = TRUE)
    )
  },
  simplify = FALSE
)
names(the_data) <- paste("Sheet", seq_len(n_sheets))
Run Code Online (Sandbox Code Playgroud)

在具有8GB RAM的计算机上以64位运行它,它会在第一次GC overhead limit exceeded运行时抛出错误addDataFrame.

如何使用大型数据集写入Excel电子表格xlsx

Ric*_*ton 77

这是一个众所周知的问题:http: //code.google.com/p/rexcel/issues/detail?id = 33

虽然尚未解决,但问题页面链接到Gabor Grothendieck 的解决方案,建议通过java.parametersrJava加载包之前设置选项来增加堆大小.(rJava是.的依赖xlsx.)

options(java.parameters = "-Xmx1000m")
Run Code Online (Sandbox Code Playgroud)

该值1000是允许Java堆的RAM的兆字节数; 它可以替换为您喜欢的任何值.我的实验表明,更大的值更好,您可以愉快地使用您的完整RAM权利.例如,我使用以下方法获得了最佳结果:

options(java.parameters = "-Xmx8000m")
Run Code Online (Sandbox Code Playgroud)

在8GB RAM的机器上.

通过在循环的每次迭代中请求垃圾收集,可以获得进一步的改进.如@gjabel所述,可以使用R执行R垃圾收集gc().我们可以定义一个调用Java System.gc()方法的Java垃圾收集函数:

jgc <- function()
{
  .jcall("java/lang/System", method = "gc")
}    
Run Code Online (Sandbox Code Playgroud)

然后循环可以更新为:

for(i in seq_along(the_data))
{
  gc()
  jgc()
  message("Creating sheet", i)
  sheet <- createSheet(wb, sheetName = names(the_data)[i])
  message("Adding data frame", i)
  addDataFrame(the_data[[i]], sheet)
}
Run Code Online (Sandbox Code Playgroud)

通过这两个代码修复,代码运行到i = 29抛出错误之前.

我尝试失败的一种技术是write.xlsx2在每次迭代时使用将内容写入文件.这比其他代码慢,并且它在第10次迭代时失败了(但至少部分内容被写入文件).

for(i in seq_along(the_data))
{
  message("Writing sheet", i)
  write.xlsx2(
    the_data[[i]], 
    "test.xlsx", 
    sheetName = names(the_data)[i], 
    append    = i > 1
  )
}
Run Code Online (Sandbox Code Playgroud)

  • 现在可以通过交换`openxlsx`包的`xlsx`包来回避整个问题,该包依赖于`Rcpp`而不是Java. (33认同)
  • `readxl`是另一种看起来很有希望的新C/C++替代品. (3认同)
  • @RichieCotton,不错的选择。但是,openxlsx无法读取.xls或.xlm文件!(2007 Excel文件格式)。 (2认同)

gja*_*bel 7

基于@ richie-cotton的答案,我发现添加gc()jgc功能可以保持较低的CPU使用率.

jgc <- function()
{
  gc()
  .jcall("java/lang/System", method = "gc")
}    
Run Code Online (Sandbox Code Playgroud)

我之前的for循环仍然在使用原始jgc函数,但是使用额外的命令,我不再遇到GC overhead limit exceeded错误消息.