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))))
これを行うより良い方法はありますか?それとも、まったく別の問題へのアプローチですか?
数年後の更新
この答えは古く、Rは進んでいます。 read.table
を微調整して少し速く実行しても、ほとんど利点はありません。オプションは次のとおりです。
data.table
で fread
を使用して、csv/tab-delimitedファイルからデータを直接Rにインポートします。 mnel's answer 。
readr
で read_table
を使用(2015年4月からCRANで)。これは上記のfread
とほぼ同じように機能します。リンクのreadmeは、2つの関数の違いを説明しています(readr
は、現在data.table::fread
より「1.5-2x遅い」と主張しています)。
read.csv.raw
from iotools
は、CSVファイルをすばやく読み取るための3番目のオプションを提供します。
フラットファイルではなく、データベースにできるだけ多くのデータを保存しようとしています。 (より良い永続的な記憶媒体であるだけでなく、データはRとの間でバイナリ形式で受け渡しされます。これは高速です。) read.csv.sql
in sqldf
パッケージ( JD Longのanswer で説明)は、データを一時SQLiteデータベースにインポートし、Rに読み込みます。参照: RODBC
パッケージ、およびその逆は DBI
package ページのセクションに依存します。 MonetDB.R
は、データフレームのふりをするデータ型を提供しますが、実際にはその下にあるMonetDBであるため、パフォーマンスが向上します。 monetdb.read.csv
関数を使用してデータをインポートします。 dplyr
を使用すると、いくつかのタイプのデータベースに保存されているデータを直接操作できます。
データをバイナリ形式で保存することも、パフォーマンスの向上に役立ちます。 saveRDS
/readRDS
(下記参照)、HDF5形式の h5
または rhdf5
パッケージ、または fst
のwrite_fst
/read_fst
を使用します パッケージ。
元の回答
Read.tableを使用する場合でもscanを使用する場合でも、試してみるのは簡単なことです。
nrows
=データ内のレコード数を設定します(nmax
のscan
)。
comment.char=""
がコメントの解釈をオフにすることを確認してください。
read.table
のcolClasses
を使用して、各列のクラスを明示的に定義します。
multi.line=FALSE
を設定すると、スキャンのパフォーマンスも向上する場合があります。
これらのいずれも機能しない場合は、 プロファイリングパッケージ のいずれかを使用して、どの行が遅くなっているのかを判断します。おそらく、結果に基づいてread.table
の削減バージョンを作成できます。
もう1つの方法は、Rに読み込む前にデータをフィルタリングすることです。
または、定期的に読み取る必要があるという問題がある場合は、これらのメソッドを使用してデータを一度に読み取り、データフレームをバイナリblobとして保存します save
saveRDS
、それから次回、より速くそれを取得できる load
readRDS
。
これは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]
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
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
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
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
私は最初この質問を見なかったし、数日後に同様の質問をした。私は前の質問を取り下げるつもりですが、私はこれを行うために私が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'))
data.frame
sは単に正しい属性を持つリストなので、リストにas.data.frame
などを使用したくない場合は、奇妙なことに、何年も質問の最後の部分に答えてくれませんでした。 。リストをその場でデータフレームに単純に「変換」する方がはるかに高速です。
attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"
これによってデータのコピーが作成されないため、即座にデータがコピーされます(他の方法とは異なり)。それに応じて、すでにリストにnames()
が設定されていると仮定しています。
[大きなデータをRにロードすることに関して - 個人的には、私はそれらをバイナリでバイナリファイルにダンプしてreadBin()
を使う - これは(mmapping以外の)最も速い方法であり、ディスク速度によってのみ制限される。 ASCIIファイルの解析は、バイナリデータに比べて(C言語でも)本質的に低速です。]
これは以前に 質問されたものです 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がより良いアプローチである理由です。
言及する価値があるマイナーな追加ポイント。非常に大きなファイルがある場合は、(ヘッダーがない場合は)次のように(ヘッダーがない場合は)行数をオンザフライで計算できます(bedGraph
は作業ディレクトリ内のファイルの名前です)。
>numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/'"), intern=T))
その後、それをread.csv
、read.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
別の方法は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
多くの場合、私はデータベースの中にもっと大きなデータベースを置くことがちょうど良い習慣だと思います(例えば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)
従来の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"))