549

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

これを行うより良い方法はありますか?それとも、問題に対する完全に異なるアプローチでしょうか?

4

12 に答える 12

479

数年ぶりのアップデート

この答えは古く、R は先に進みました。少し速く実行するように微調整read.tableしても、ほとんどメリットはありません。オプションは次のとおりです。

  1. vroomtidyverse パッケージを使用して、 vroomcsv/タブ区切りファイルから直接 R tibble にデータをインポートします。ヘクターの答えを参照してください。

  2. freadinを使用しdata.tableて、csv/タブ区切りファイルから直接 R にデータをインポートします。mnel の回答を参照してください。

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

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

  5. フラットファイルではなくデータベースにできるだけ多くのデータを保存しようとしています。(より優れた永続的なストレージ メディアであるだけでなく、データはバイナリ形式で R との間でやり取りされるため、より高速です。)パッケージでは、JD Long の回答で説明されているように、read.csv.sqlデータ一時的な SQLite データベースにインポートしてから読み取ります。 R に。以下も参照してください:パッケージ、およびその逆はパッケージページのセクションに依存します。データ フレームのふりをしているが、実際にはその下にある MonetDB であるデータ型を提供し、パフォーマンスを向上させます。その関数を使用してデータをインポートします。 いくつかのタイプのデータベースに保存されているデータを直接操作できます。sqldfRODBCDBIMonetDB.Rmonetdb.read.csvdplyr

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


元の答え

read.table を使用するかスキャンを使用するかに関係なく、いくつかの簡単な方法を試すことができます。

  1. Set nrows=データ内のレコード数( nmaxin scan)。

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

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

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

これらのいずれも機能しない場合は、プロファイリング パッケージの 1 つを使用して、速度が低下している行を特定します。read.tableおそらく、結果に基づいて の縮小版を書くことができます。

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

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

于 2009-11-13T10:35:05.553 に答える
294

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

fread例は、私の Windows XP Core 2 duo E8400 でのタイミングと共に、ヘルプ ページからに掲載されています。

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
于 2013-02-25T01:07:39.163 に答える
257

最初はこの質問を見ていませんでしたが、数日後に同様の質問をしました。以前の質問を削除しますが、ここに回答を追加して、これをどのように行ったかを説明するとsqldf()思いました.

2GB 以上のテキスト データを R データ フレームにインポートする最善の方法については、少し議論がありました。昨日、データをステージング領域として SQLite にインポートし、それを SQLite から R に吸い込む方法についてのブログ記事sqldf()を書きました。これは私にとって非常にうまく機能します。2 GB (3 列、40 mm 行) のデータを 5 分未満で取り込むことができました。対照的に、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'))
于 2009-11-30T15:48:11.487 に答える
79

不思議なことに、これは重要なものですが、何年もの間、質問の下部に答えた人は誰もいませんでした。これはdata.frame単に適切な属性を持つリストであるため、大きなデータがある場合は、リストに使用したくない、as.data.frameまたは類似したものです。リストをインプレースのデータフレームに単純に「変換」する方がはるかに高速です。

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

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

[大きなデータをRにロードすることに関しては、個人的には、列ごとにバイナリファイルにダンプして使用しますreadBin()。これは、(mmappingを除く)はるかに高速な方法であり、ディスク速度によってのみ制限されます。ASCIIファイルの解析は、バイナリデータと比較して本質的に低速です(Cでも)。]

于 2012-12-20T04:01:40.743 に答える
33

これは以前にR-Helpで質問されたので、確認する価値があります。

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

ここでメモリが問題になるかどうかはわかりませんがHadoopStreamingパッケージも参照してください。これは、大規模なデータ セットを処理するために設計された MapReduce フレームワークであるHadoopを使用します。これには、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 がより良いアプローチである理由です。

于 2009-11-13T15:18:57.513 に答える
10

arrow新しいパッケージを使用して、データを非常に高速に読み取っています。かなり初期の段階にあるようです。

具体的には、寄木細工の柱形式を使用しています。これdata.frameは R の a に変換されますが、変換しない場合はさらに高速化できます。この形式は Python からも使用できるので便利です。

これの私の主な使用例は、かなり制限された RShiny サーバーです。これらの理由から、私はデータをアプリに添付したままにする (つまり、SQL から除外する) ことを好みます。したがって、小さなファイル サイズと速度が求められます。

このリンクされた記事は、ベンチマークと優れた概要を提供します。以下に興味深い点をいくつか引用しました。

https://ursalabs.org/blog/2019-10-columnar-perf/

ファイルサイズ

つまり、Parquet ファイルは、gzip 圧縮された CSV の半分の大きさです。Parquet ファイルが非常に小さい理由の 1 つは、辞書エンコード (「辞書圧縮」とも呼ばれます) によるものです。辞書圧縮は、LZ4 や ZSTD (FST 形式で使用される) などの汎用バイト圧縮を使用するよりも大幅に優れた圧縮を実現できます。Parquet は、読み取りが高速な非常に小さなファイルを生成するように設計されています。

読み取り速度

出力タイプで制御する場合 (たとえば、すべての R data.frame 出力を相互に比較する場合)、Parquet、Feather、および FST のパフォーマンスは、相互の比較的小さなマージン内に収まることがわかります。同じことが pandas.DataFrame 出力にも当てはまります。data.table::fread は、1.5 GB のファイル サイズでは非常に優れていますが、2.5 GB の CSV では他のファイルに後れを取っています。


独立したテスト

1,000,000 行のシミュレートされたデータセットに対して、いくつかの独立したベンチマークを実行しました。基本的に、圧縮に挑戦するために、たくさんのものをシャッフルしました。また、ランダムな単語と 2 つのシミュレートされた要因の短いテキスト フィールドを追加しました。

データ

library(dplyr)
library(tibble)
library(OpenRepGrid)

n <- 1000000

set.seed(1234)
some_levels1 <- sapply(1:10, function(x) paste(LETTERS[sample(1:26, size = sample(3:8, 1), replace = TRUE)], collapse = ""))
some_levels2 <- sapply(1:65, function(x) paste(LETTERS[sample(1:26, size = sample(5:16, 1), replace = TRUE)], collapse = ""))


test_data <- mtcars %>%
  rownames_to_column() %>%
  sample_n(n, replace = TRUE) %>%
  mutate_all(~ sample(., length(.))) %>%
  mutate(factor1 = sample(some_levels1, n, replace = TRUE),
         factor2 = sample(some_levels2, n, replace = TRUE),
         text = randomSentences(n, sample(3:8, n, replace = TRUE))
         )

読み書き

データの書き込みは簡単です。

library(arrow)

write_parquet(test_data , "test_data.parquet")

# you can also mess with the compression
write_parquet(test_data, "test_data2.parquet", compress = "gzip", compression_level = 9)

データの読み取りも簡単です。

read_parquet("test_data.parquet")

# this option will result in lightning fast reads, but in a different format.
read_parquet("test_data2.parquet", as_data_frame = FALSE)

いくつかの競合するオプションに対してこのデータの読み取りをテストしたところ、上記の記事とはわずかに異なる結果が得られましたが、これは予想どおりです。

ベンチマーク

このファイルはベンチマーク記事ほど大きくないので、おそらくそれが違いです。

テスト

  • rds : test_data.rds (20.3 MB)
  • parquet2_native: (高圧縮で 14.9 MB as_data_frame = FALSE)
  • parquet2: test_data2.parquet (より高い圧縮で 14.9 MB)
  • 寄木細工: test_data.parquet (40.7 MB)
  • fst2: test_data2.fst (高圧縮で 27.9 MB)
  • fst : test_data.fst (76.8 MB)
  • fread2: test_data.csv.gz (23.6MB)
  • fread: test_data.csv (98.7MB)
  • feather_arrow: test_data.feather (157.2 MB で読み取りarrow)
  • フェザー: test_data.feather (157.2 MB で読み取りfeather)

観察

この特定のファイルの場合、fread実際には非常に高速です。parquet2高度に圧縮されたテストの小さいファイル サイズが気に入っています。data.frame本当にスピードアップが必要な場合は、ネイティブ データ形式ではなく、ネイティブ データ形式で作業することに時間を費やすかもしれません。

ここfstも素晴らしい選択です。速度またはファイルサイズのトレードオフが必要かどうかに応じてfst、高度に圧縮された形式または高度に圧縮された形式のいずれかを使用します。parquet

于 2019-11-12T18:31:21.120 に答える
6

言及する価値のあるマイナーな追加ポイント。非常に大きなファイルがある場合は、(ヘッダーがない場合) を使用してその場で行数を計算できます (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
于 2013-11-28T17:20:55.193 に答える
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"))
于 2015-04-18T07:22:01.190 に答える