web-dev-qa-db-ja.com

非常に大きなテーブルをデータフレームとしてすばやく読み取る

Rのデータフレームとしてロードしたい非常に大きなテーブル(3000万行)があります。read.table()には便利な機能がたくさんありますが、実装には遅くなるロジックがたくさんあるようです。私の場合は、列の種類が事前にわかっていること、表に列ヘッダーや行名が含まれていないこと、および心配しなければならない病理学的な文字が含まれていないことを前提としています。

私は、scan()を使ってテーブルをリストとして読み込むのはとても速いことを知っています。例えば:

datalist <- scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0)))

しかし、これをデータフレームに変換しようとする試みの中には、上記のパフォーマンスを6分の1に低下させるように見えるものもあります。

df <- as.data.frame(scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0))))

これを行うより良い方法はありますか?それとも、まったく別の問題へのアプローチですか?

471
eytan

数年後の更新

この答えは古く、Rは進んでいます。 read.table を微調整して少し速く実行しても、ほとんど利点はありません。オプションは次のとおりです。

  1. data.tablefread を使用して、csv/tab-delimitedファイルからデータを直接Rにインポートします。 mnel's answer

  2. readrread_table を使用(2015年4月からCRANで)。これは上記のfreadとほぼ同じように機能します。リンクのreadmeは、2つの関数の違いを説明しています(readrは、現在data.table::freadより「1.5-2x遅い」と主張しています)。

  3. read.csv.raw from iotools は、CSVファイルをすばやく読み取るための3番目のオプションを提供します。

  4. フラットファイルではなく、データベースにできるだけ多くのデータを保存しようとしています。 (より良い永続的な記憶媒体であるだけでなく、データはRとの間でバイナリ形式で受け渡しされます。これは高速です。) read.csv.sql in sqldf パッケージ( JD Longのanswer で説明)は、データを一時SQLiteデータベースにインポートし、Rに読み込みます。参照: RODBC パッケージ、およびその逆は DBI package ページのセクションに依存します。 MonetDB.R は、データフレームのふりをするデータ型を提供しますが、実際にはその下にあるMonetDBであるため、パフォーマンスが向上します。 monetdb.read.csv 関数を使用してデータをインポートします。 dplyr を使用すると、いくつかのタイプのデータベースに保存されているデータを直接操作できます。

  5. データをバイナリ形式で保存することも、パフォーマンスの向上に役立ちます。 saveRDS/readRDS(下記参照)、HDF5形式の h5 または rhdf5 パッケージ、または fstwrite_fst/read_fstを使用します パッケージ。


元の回答

Read.tableを使用する場合でもscanを使用する場合でも、試してみるのは簡単なことです。

  1. nrows =データ内のレコード数を設定しますnmaxscan)。

  2. comment.char=""がコメントの解釈をオフにすることを確認してください。

  3. read.tablecolClassesを使用して、各列のクラスを明示的に定義します。

  4. multi.line=FALSEを設定すると、スキャンのパフォーマンスも向上する場合があります。

これらのいずれも機能しない場合は、 プロファイリングパッケージ のいずれかを使用して、どの行が遅くなっているのかを判断します。おそらく、結果に基づいてread.tableの削減バージョンを作成できます。

もう1つの方法は、Rに読み込む前にデータをフィルタリングすることです。

または、定期的に読み取る必要があるという問題がある場合は、これらのメソッドを使用してデータを一度に読み取り、データフレームをバイナリblobとして保存します save saveRDS 、それから次回、より速くそれを取得できる load readRDS

396
Richie Cotton

これはdata.table 1.8.7からのfreadを利用する例です。

例は私のウィンドウXP Core 2 duo E8400上のタイミングで、ヘルプページからfreadへ来ています。

library(data.table)
# Demo speedup
n=1e6
DT = data.table( a=sample(1:1000,n,replace=TRUE),
                 b=sample(1:1000,n,replace=TRUE),
                 c=rnorm(n),
                 d=sample(c("foo","bar","baz","qux","quux"),n,replace=TRUE),
                 e=rnorm(n),
                 f=sample(1:1000,n,replace=TRUE) )
DT[2,b:=NA_integer_]
DT[4,c:=NA_real_]
DT[3,d:=NA_character_]
DT[5,d:=""]
DT[2,e:=+Inf]
DT[3,e:=-Inf]

標準のread.table

write.table(DT,"test.csv",sep=",",row.names=FALSE,quote=FALSE)
cat("File size (MB):",round(file.info("test.csv")$size/1024^2),"\n")    
## File size (MB): 51 

system.time(DF1 <- read.csv("test.csv",stringsAsFactors=FALSE))        
##    user  system elapsed 
##   24.71    0.15   25.42
# second run will be faster
system.time(DF1 <- read.csv("test.csv",stringsAsFactors=FALSE))        
##    user  system elapsed 
##   17.85    0.07   17.98

最適化されたread.table

system.time(DF2 <- read.table("test.csv",header=TRUE,sep=",",quote="",  
                          stringsAsFactors=FALSE,comment.char="",nrows=n,                   
                          colClasses=c("integer","integer","numeric",                        
                                       "character","numeric","integer")))


##    user  system elapsed 
##   10.20    0.03   10.32

フリード

require(data.table)
system.time(DT <- fread("test.csv"))                                  
 ##    user  system elapsed 
##    3.12    0.01    3.22

sqldf

require(sqldf)

system.time(SQLDF <- read.csv.sql("test.csv",dbname=NULL))             

##    user  system elapsed 
##   12.49    0.09   12.69

# sqldf as on SO

f <- file("test.csv")
system.time(SQLf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

##    user  system elapsed 
##   10.21    0.47   10.73

ff/ffdf

 require(ff)

 system.time(FFDF <- read.csv.ffdf(file="test.csv",nrows=n))   
 ##    user  system elapsed 
 ##   10.85    0.10   10.99

要約すれば:

##    user  system elapsed  Method
##   24.71    0.15   25.42  read.csv (first time)
##   17.85    0.07   17.98  read.csv (second time)
##   10.20    0.03   10.32  Optimized read.table
##    3.12    0.01    3.22  fread
##   12.49    0.09   12.69  sqldf
##   10.21    0.47   10.73  sqldf on SO
##   10.85    0.10   10.99  ffdf
266
mnel

私は最初この質問を見なかったし、数日後に同様の質問をした。私は前の質問を取り下げるつもりですが、私はこれを行うために私がsqldf()を使った方法を説明するためにここに答えを加えたいと思いました。

2GB以上のテキストデータをRデータフレームにインポートする最善の方法については 少し議論がありました 。昨日私は ブログ投稿 を使ってステージング領域としてsqldf()を使ってデータをSQLiteにインポートし、それをSQLiteからRに吸い込むことについて書いた。私にとっても5分以内に2GB(3列、40mm行)のデータを引き込むことができました。これとは対照的に、read.csvコマンドは一晩中実行され、完了することはありませんでした。

これが私のテストコードです。

テストデータを設定します。

bigdf <- data.frame(dim=sample(letters, replace=T, 4e7), fact1=rnorm(4e7), fact2=rnorm(4e7, 20, 50))
write.csv(bigdf, 'bigdf.csv', quote = F)

次のインポートルーチンを実行する前にRを再起動しました。

library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))

私は次の行を一晩中走らせたが、それは完了しなかった。

system.time(big.df <- read.csv('bigdf.csv'))
246
JD Long

data.framesは単に正しい属性を持つリストなので、リストにas.data.frameなどを使用したくない場合は、奇妙なことに、何年も質問の最後の部分に答えてくれませんでした。 。リストをその場でデータフレームに単純に「変換」する方がはるかに高速です。

attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"

これによってデータのコピーが作成されないため、即座にデータがコピーされます(他の方法とは異なり)。それに応じて、すでにリストにnames()が設定されていると仮定しています。

[大きなデータをRにロードすることに関して - 個人的には、私はそれらをバイナリでバイナリファイルにダンプしてreadBin()を使う - これは(mmapping以外の)最も速い方法であり、ディスク速度によってのみ制限される。 ASCIIファイルの解析は、バイナリデータに比べて(C言語でも)本質的に低速です。]

71
Simon Urbanek

これは以前に 質問されたものです Rヘルプ なので、検討する価値があります。

1つの提案は、readChar()を使用してから、strsplit()substr()を使用して結果に対して文字列操作を行うことでした。 readCharに含まれるロジックはread.tableよりはるかに少ないことがわかります。

ここでメモリが問題になっているかどうかはわかりませんが、 を見てみるとよいでしょう。 HadoopStreaming パッケージ これは Hadoop を使います。これは大規模なデータセットを扱うために設計されたMapReduceフレームワークです。これには、hsTableReader関数を使用します。これは一例です(ただしHadoopを学ぶための習熟曲線があります)。

str <- "key1\t3.9\nkey1\t8.9\nkey1\t1.2\nkey1\t3.9\nkey1\t8.9\nkey1\t1.2\nkey2\t9.9\nkey2\"
cat(str)
cols = list(key='',val=0)
con <- textConnection(str, open = "r")
hsTableReader(con,cols,chunkSize=6,FUN=print,ignoreKey=TRUE)
close(con)

ここでの基本的な考え方は、データのインポートをチャンクに分割することです。並列フレームワークの1つ(snowなど)を使用してファイルをセグメント化してデータのインポートを並行して実行することもできますが、大部分のデータセットではメモリの制約に遭遇するので役に立ちません。これが、map-reduceがより良いアプローチである理由です。

30
Shane

言及する価値があるマイナーな追加ポイント。非常に大きなファイルがある場合は、(ヘッダーがない場合は)次のように(ヘッダーがない場合は)行数をオンザフライで計算できます(bedGraphは作業ディレクトリ内のファイルの名前です)。

>numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/'"), intern=T))

その後、それをread.csvread.tableのいずれかで使用できます。

>system.time((BG=read.table(bedGraph, nrows=numRow, col.names=c('chr', 'start', 'end', 'score'),colClasses=c('character', rep('integer',3)))))
   user  system elapsed 
 25.877   0.887  26.752 
>object.size(BG)
203949432 bytes
5

別の方法はvroomパッケージを使うことです。今CRANに。 vroomはファイル全体をロードするのではなく、各レコードがある場所にインデックスを付け、後で使用するときに読み取られます。

あなたが使うものだけを払う。

vroomの概要vroom および vroomベンチマーク を参照してください。

基本的な概要は、巨大なファイルの最初の読み込みはずっと速くなり、その後のデータの修正は少し遅くなるかもしれないということです。だからあなたの用途が何であるかに応じて、それは最良の選択肢かもしれません。

下記の vroom benchmarks からの単純化された例を見てください。見るべき重要な部分は超高速読み取り時間ですが、集約などのようなわずかに遅い操作です。

package                 read    print   sample   filter  aggregate   total
read.delim              1m      21.5s   1ms      315ms   764ms       1m 22.6s
readr                   33.1s   90ms    2ms      202ms   825ms       34.2s
data.table              15.7s   13ms    1ms      129ms   394ms       16.3s
vroom (altrep) dplyr    1.7s    89ms    1.7s     1.3s    1.9s        6.7s
5

多くの場合、私はデータベースの中にもっと大きなデータベースを置くことがちょうど良い習慣だと思います(例えばPostgres)。私は(nrow * ncol)ncell = 10Mよりも大きすぎるものは使用しません。これはかなり小さいです。しかし私は、Rが複数のデータベースから問い合わせをしている間だけ、メモリ集約型のグラフを作成して保持したいと思うことがよくあります。 32 GBラップトップの将来、これらのタイプのメモリ問題のいくつかは消えるでしょう。しかし、データベースを使用してデータを保持してから、結果のクエリ結果とグラフにRのメモリを使用するという魅力は、依然として有用かもしれません。いくつかの利点があります:

(1)データはデータベースにロードされたままです。ノートパソコンの電源を入れたときにpgadminで必要なデータベースに再接続するだけです。

(2)RがSQLよりももっと気の利いた統計演算やグラフ化演算を実行できるのは事実です。しかし、SQLはRよりも大量のデータを照会するように設計されていると私は思います。

# Looking at Voter/Registrant Age by Decade

library(RPostgreSQL);library(lattice)

con <- dbConnect(PostgreSQL(), user= "postgres", password="password",
                 port="2345", Host="localhost", dbname="WC2014_08_01_2014")

Decade_BD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from Birthdate) from voterdb where extract(DECADE from Birthdate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;")

Decade_RD_1980_42 <- dbGetQuery(con,"Select PrecinctID,Count(PrecinctID),extract(DECADE from RegistrationDate) from voterdb where extract(DECADE from RegistrationDate)::numeric > 198 and PrecinctID in (Select * from LD42) Group By PrecinctID,date_part Order by Count DESC;")

with(Decade_BD_1980_42,(barchart(~count | as.factor(precinctid))));
mtext("42LD Birthdays later than 1980 by Precinct",side=1,line=0)

with(Decade_RD_1980_42,(barchart(~count | as.factor(precinctid))));
mtext("42LD Registration Dates later than 1980 by Precinct",side=1,line=0)
4
rferrisx

従来のread.tableの代わりに、freadが速い関数だと感じます。必要な列のみを選択するなどの追加の属性を指定し、要素としてcolclassesとstringを指定すると、ファイルのインポートにかかる時間が短縮されます。

data_frame <- fread("filename.csv",sep=",",header=FALSE,stringsAsFactors=FALSE,select=c(1,4,5,6,7),colClasses=c("as.numeric","as.character","as.numeric","as.Date","as.Factor"))
0
Aayush Agrawal