About connecting the dots.

data science related trivial things

Rで高速に大量データを読み込んでデータフレームに格納する方法 (1)

注)この記事よりもっと効率的なやり方が,Rで高速に大量データを読み込んでデータフレームに格納する方法 (2) - About connecting the dots.に載っています.

===

サンプリングした小規模データをRでいろいろ処理して,必要な分析がすべてできたら,最後に全データを読み込んで同じ処理を再度行う,といったことをよくやります.Rは基本的にデータがメモリに載らないと処理できないので,必然的にそれほど大きなデータを扱うことはできません.しかし手元にあるメモリ8GBのMBAでも,だいたい1GBのデータを読み込んで諸々の処理をすることくらいは問題なくできます.それでも,1GBのデータを読み込んでメモリに載せるだけで2-3GB近く食ってしまう仕様はどうにかならないのかなと思いますが...

さて,濱田さんの勉強会資料金明哲先生の解説ページなんかでも触れられているように,小中規模のデータはread.table()で,大規模データはscan()で読み込むのがよい,というふうに書かれています.いつも何も考えずにread.table()で書いてそれを1GBサイズのデータ読み込みの際にも流用していたので,scan()で早くならないかなという期待を込めて実験してみました.

実験概要

マシンスペック

  • マシン: MacBook Air (11-inch, Mid 2012)
  • CPU: 1.7GHz dual-core Intel Core i5 (Turbo Boost up to 2.6GHz) with 3MB shared L3 cache
  • メモリ: 8GB of 1600MHz DDR3L onboard memory
  • ストレージ: SSD

R環境

  • Rバージョン: 2.15.2
  • R実行環境: 0.97.248

読み込みデータ

下記のphpコードで適当に生成した,2000万行のサンプルデータ

<?php
$num = $argv[1];
$label = $argv[2];

$fp = fopen("sample_" . $num . ".dat", "w");

if ($label === "true") {
	fwrite($fp, "user_id\tsex\tage\n");
}

for ($i = 1; $i <= $num; $i++) {
	if (mt_rand(0, 1)) $sex = "male";
	else $sex = "female";
	$age = mt_rand(1, 100);
	fwrite($fp, md5($i). "\t" . $sex . "\t" . $age . "\n");
}

fclose($fp);
$ php generate.php 20000000 false
$ head sample_20000000.dat
c4ca4238a0b923820dcc509a6f75849b	female	82
c81e728d9d4c2f636f067f89cc14862c	female	9
eccbc87e4b5ce2fe28308fd9f2a7baf3	female	76
a87ff679a2f3e71d9181a67b7542122c	male	23
e4da3b7fbbce2345d7772b0674a318d5	male	53
1679091c5a880faf6fb5e6087eb1b2dc	female	55
8f14e45fceea167a5a36dedd4bea2543	male	4
c9f0f895fb98ab9159f51fd0297e236d	male	9
45c48cce2e2d7fbdea1afc51c7c6ad26	female	41
d3d9446802a44259755d38e6d163e820	female	54

Rコード

read.table()
data<-read.table("~/Documents/tmp/sample_20000000.dat", sep="\t")
scan()
data <-scan("/Users/smrmkt/Documents/tmp/sample_20000000.dat",
            list(user_id="", sex="", age=0))
data <- data.frame(data)

実験結果

条件 データ読み込み データフレーム変換 合計
read.table() - - 544秒
scan() 198秒 423秒 621秒

上記結果の通り,単にデータを読み込むだけなら,確かにscan()のほうが3倍くらい早いのですが,そのあとデータフレームに変換する際に相当に時間を食ってしまう結果となりました.今回の結果だと,scan()でデータフレーム変換を行うあたりでメモリがつきかけてきて,若干swap気味だったのが影響したのかもしれません.もう少し小規模なデータで同じ実験すれば,もう少し正確なデータが得られるはずです.

ただポイントなのは,scanでデータフレームを作成する場合には,データをいったんリストに格納して,それをデータフレームに変換するという処理が発生するため,時間もかかり,かつメモリを2重に食うことになってしまいます.そこでscanを使うつもりなのであれば,データフレームではなく最初からリストで処理を書くということになるんでしょうかね...? うーんそれも微妙そうな感じしかしないんですが... ということで銀の弾丸はなかったというのが今日の結論です.

しかし一定以上の規模のデータ使う前提なら,はじめからRなんか使わないのが一番ということになるのがあれですね... 分析環境の使い分けって案外難しい問題です.上記の分析にHadoop使うってのもそれはそれで無駄感しかしないですしね...