はじめに
現状、数十GB・数百GBといった、大きなファイルを扱う際には、R/メモリ上で全データを読み込むことはややリスキーである。
ファイル全体を読み込まず、ファイル内の1行ずつで処理を実施する工夫が必要となる。*1
Rで、1行ずつの処理を実行するには、readLines関数
を用いる。
readLines関数は、connection オブジェクトから一部またはすべてのテキスト行を読み込む関数である。
また、connection オブジェクトとは、つまり「一般化されたファイル」(圧縮ファイル、URL、パイプなど)を作成、オープン、クローズするための関数オブジェクトを指す。このR Documentationの説明を読んでてても、いまいち分からないのだが、要するに、R上に全ファイルのロードせずに、DB的にファイルと接続して、データのやり取りをするというイメージだろう。
テストファイルを用意したので、readLinesの実行例を解説する。
練習ファイルのダウンロード
#gistからファイル・ダウンロード utils::download.file(url="https://gist.githubusercontent.com/kumeS/398fbeb50c71cf828190aced63b0d1b0/raw/0fb095ae07a4e331ef23e6fc9350a5d7d276b732/file.txt", destfile="file.txt") #ファイル表示 system("cat file.txt") #AAA #BBB #CCC #DDD #EEE #FFF #GGG #HHH #III #JJJ #KKK #LLL #MMM #NNN #OOO #PPP #QQQ #RRR
readLinesのダメな実行例
これは、間違った使い方であるが、 ファイル名を指定して、readLinesを実行してもダメ・・・である。
以下の実行だと、同じ出力が繰り返されるだけである。
#ファイルパス txt_file <- "./file.txt" readLines(con=txt_file, n = 1) #[1] "AAA" readLines(con=txt_file, n = 1) #[1] "AAA" ##引数 #con: connection オブジェクト #n: 読み込む (最大の) 行数
readLinesの実行コード例
まず、ファイルパスから、connection オブジェクトを作成する。
con_file <- file(description = "./file.txt", open = "r") str(con_file) # 'file' int 3 # - attr(*, "conn_id")=<externalptr> con_file #A connection with #description "./file.txt" #class "file" #mode "r" #text "text" #opened "opened" #can read "yes" #can write "no"
connection オブジェクトをreadLinesで読み込んでいくと、実行回数ごとに出力が変わる。
#readLinesを実行するごとに、違う行が表示される readLines(con_file, n = 1) #[1] "AAA" readLines(con_file, n = 1) #[1] "BBB" readLines(con_file, n = 1) #[1] "CCC" readLines(con_file, n = 1) #[1] "DDD" readLines(con_file, n = 1) #[1] "EEE"
次に、forループで実行する例を示す。
#for実行の場合 con_file <- file(description = "./file.txt", open = "r") for(n in 1:18){ a <- readLines(con_file, n = 1) print(a) } #[1] "AAA" #[1] "BBB" #[1] "CCC" #[1] "DDD" #[1] "EEE" #[1] "FFF" #[1] "GGG" #[1] "HHH" #[1] "III" #[1] "JJJ" #[1] "KKK" #[1] "LLL" #[1] "MMM" #[1] "NNN" #[1] "OOO" #[1] "PPP" #[1] "QQQ" #[1] "RRR"
さらに、stackoverflow内で、dvdさんが示していた関数の事例を下記に示す。
while
を使って、if ( length(line) == 0 )
の条件を入れることで、
ファイルの全行数が分からなくても、実行の開始・終了ができる。
processFile = function(filepath) { con = file(filepath, "r") while ( TRUE ) { line = readLines(con, n = 1) if ( length(line) == 0 ) { break } print(line) } close(con) } processFile(filepath="./file.txt") #[1] "AAA" #[1] "BBB" #[1] "CCC" #[1] "DDD" #[1] "EEE" #[1] "FFF" #[1] "GGG" #[1] "HHH" #[1] "III" #[1] "JJJ" #[1] "KKK" #[1] "LLL" #[1] "MMM" #[1] "NNN" #[1] "OOO" #[1] "PPP" #[1] "QQQ" #[1] "RRR" #あるいは、repeat関数 でもOK!! processFile_r <- function(filepath) { con = file(filepath, "r") repeat { line = readLines(con, n = 1) if ( length(line) == 0 ) { break } print(line) } close(con) }
まとめ
connection オブジェクトの扱い方とか、ノーマークだったけど、Rにも便利なオブジェクトがあるもんだ。
R・ビッグデータ解析の処方箋 関連記事
R言語 お勧め書籍
補足
readLines関数で、10万行の同時読み込み
実際の事例では、readLines
関数でGBオーダーのテキストファイルを読み込んでいく場合に、n = 1
では、インタプリンタ言語の性格上、非常に読み込み効率が悪く、n = 10000
あるいはn = 100000
くらいに設定して読み込むのが良い。
私の経験上、readLines
関数での10万行の読み込みは、1-2秒で完了する。
#readLinesの実行例 readLines(con_file, n = 100000)
参考資料
*1:sedコマンドで、1行ずつ行を抽出できるが、sedコマンドでも一度ファイル全体を読み込んでから処理を実行しているらしく、大きなファイルにsed実行は不向きである。