web-dev-qa-db-ja.com

RからExcelに書き込む際のJava.lang.OutOfMemoryErrorの処理

xlsxパッケージを使用して、RからExcelスプレッドシートを読み書きできます。残念ながら、適度に大きいスプレッドシートであっても、Java.lang.OutOfMemoryErrorが発生する可能性があります。特に、

## Error in .jcall("RJavaTools", "Ljava/lang/Object;", "invokeMethod", cl,  : 
##   Java.lang.OutOfMemoryError: Java heap space  

## Error in .jcall("RJavaTools", "Ljava/lang/Object;", "newInstance", .jfindClass(class),  : 
##   Java.lang.OutOfMemoryError: GC overhead limit exceeded 

(他の関連する例外も可能ですが、まれです。)

スプレッドシートを読むときに、このエラーに関して同様の質問がされました。

大きなxlsxファイルをRにインポートしますか?

ExcelスプレッドシートをCSVよりもデータストレージメディアとして使用する主な利点は、同じファイルに複数のシートを保存できることです。したがって、ここでは、ワークシートごとに1データフレームを書き込むデータフレームのリストを検討します。このサンプルデータセットには40個のデータフレームが含まれ、各フレームには最大20万行の2つの列があります。問題が生じるほど大きくなるように設計されていますが、n_sheetsn_rowsを変更することでサイズを変更できます。

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))

これをファイルに書き込む自然な方法は、 createWorkbook を使用してワークブックを作成し、 createSheet および addDataFrame 。最後に、ワークブックは saveWorkbook を使用してファイルに書き込むことができます。ループにメッセージを追加して、どこに落ちているかを簡単に確認できるようにしました。

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")  

これを8GB RAMのマシンで64ビットで実行すると、addDataFrameを初めて実行するときにGC overhead limit exceededエラーがスローされます。

xlsxを使用して大きなデータセットをExcelスプレッドシートに書き込むにはどうすればよいですか?

78
Richie Cotton

これは既知の問題です。 http://code.google.com/p/rexcel/issues/detail?id=

未解決の問題ページ ソリューションへのリンクGabor Grothendieck により、rJavaパッケージがロードされる前にJava.parametersオプションを設定することでヒープサイズを増やすことを提案しています。 (rJavaxlsxの依存関係です。)

options(Java.parameters = "-Xmx1000m")

1000は、RAMヒープを許可するJavaのメガバイト数です。任意の値に置き換えることができます。これを使った私の実験は、値が大きいほど良いことを示しており、RAMのフルエンタイトルメントを喜んで使用できます。たとえば、次を使用して最高の結果を得ました。

options(Java.parameters = "-Xmx8000m")

8GB RAMのマシン上。

ループの各反復でガベージコレクションを要求することで、さらに改善することができます。 @gjabelで述べたように、Rガベージコレクションは gc() を使用して実行できます。 Java System.gc() メソッドを呼び出すJavaガベージコレクション関数を定義できます。

jgc <- function()
{
  .jcall("Java/lang/System", method = "gc")
}    

その後、ループを次のように更新できます。

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)
}

これらの両方のコードの修正により、コードはエラーをスローするまでi = 29まで実行されました。

私が試みた失敗したテクニックの1つは、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
  )
}
77
Richie Cotton

@ richie-cottonの回答に基づいて、gc()jgc関数に追加すると、CPU使用率が低く抑えられることがわかりました。

jgc <- function()
{
  gc()
  .jcall("Java/lang/System", method = "gc")
}    

私の以前のforループは元のjgc関数にまだ苦労していましたが、余分なコマンドを使用すると、GC overhead limit exceededエラーメッセージに出会うことはなくなりました。

7
gjabel

上記のエラーの解決策:下記のrを使用してください-コード:

detach(package:xlsx)detach(package:XLConnect)library(openxlsx)

そして、もう一度ファイルをインポートしてみてください。それは私にとってうまくいくので、エラーは発生しません。

0
Santosh