it-swarm.dev

매우 큰 테이블을 데이터 프레임으로 빠르게 읽습니다.

저는 R에서 데이터 프레임으로로드하고 싶은 매우 큰 테이블 (3 천만 행)을 가지고 있습니다. read.table()에는 많은 편리한 기능이 있지만 구현 속도가 느려지는 많은 로직이있는 것처럼 보입니다. 필자의 경우, 미리 열의 유형을 알고 있다고 가정하고 테이블에 열 머리글이나 행 이름이 포함되어 있지 않으며 염려해야 할 병적 특성이 없습니다.

scan()을 사용하여 목록에서 테이블을 읽는 것이 매우 빠를 수 있음을 압니다. 예 :

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

그러나이를 데이터 프레임으로 변환하려는 시도 중 일부는 위의 성능을 6 배로 낮추는 것으로 나타납니다.

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

이 작업을 수행하는 더 좋은 방법이 있습니까? 아니면 완전히 다른 접근 방식으로 문제를 해결할 수 있을까요?

471
eytan

몇 년 후 업데이트

이 대답은 오래되었고 R은 계속 움직였습니다. read.table 조금 조정하면 약간의 이익이 있습니다. 선택할 수있는 옵션은 다음과 같습니다.

  1. Csv/tab-delimited 파일에서 R로 직접 가져 오는 경우 freaddata.tablemnel 's answer 를 참조하십시오.

  2. readr (CRAN의 2015 년 4 월)에서 read_table 사용 위의 fread과 매우 유사합니다. 링크의 readme 는 두 함수의 차이점을 설명합니다 (readr은 현재 data.table::fread보다 "1.5-2x 느림"이라고 주장합니다).

  3. read.csv.raw from iotools 는 CSV 파일을 빠르게 읽을 수있는 세 번째 옵션을 제공합니다.

  4. 플랫 파일이 아닌 데이터베이스에 가능한 한 많은 데이터를 저장하려고합니다. (더 나은 영구 저장 매체 일뿐만 아니라 데이터는 R과 바이너리 형식으로 전달되기 때문에 더 빠릅니다. --- read.csv.sql --- sqldf 패키지에 설명 된대로 JD Long의 대답 , 임시 SQLite 데이터베이스로 데이터를 가져온 다음 R로 읽어들입니다. 참고 : RODBC 패키지이고, 그 역은 DBI 패키지 페이지. MonetDB.R 는 데이터 프레임 인 것처럼 보이는 데이터 유형을 제공하지만 실제로는 MonetDB 아래에 있으므로 성능이 향상됩니다. monetdb.read.csv 함수를 사용하여 데이터를 가져옵니다. dplyr 을 사용하면 여러 유형의 데이터베이스에 저장된 데이터로 직접 작업 할 수 있습니다.

  5. 바이너리 형식으로 데이터를 저장하면 성능을 향상시키는데도 유용 할 수 있습니다. saveRDS/readRDS (아래 참조), h5 또는 rhdf5 패키지, 또는 write_fst/read_fstfst 패키지를 사용하십시오.


원래 답변

Read.table을 사용하든 스캔을 사용하든 두 가지 간단한 시도가 있습니다.

  1. nrows = 데이터의 레코드 수 (nmaxscan)를 설정하십시오.

  2. 주석 해석을 해제하려면 comment.char=""를 확인하십시오.

  3. read.table에서 colClasses을 사용하여 각 열의 클래스를 명시 적으로 정의하십시오.

  4. multi.line=FALSE를 설정하면 검색 성능이 향상 될 수도 있습니다.

이들 중 아무 것도 작동하지 않는다면, 프로파일 링 패키지 중 하나를 사용하여 어떤 라인이 느려지는지 결정하십시오. 아마도 결과에 따라 read.table 버전을 줄일 수 있습니다.

다른 대안은 R로 읽어 오기 전에 데이터를 필터링하는 것입니다.

또는 문제를 정기적으로 읽어야하는 경우이 방법을 사용하여 한 번에 데이터를 읽은 다음 데이터 프레임을 이진 Blob로 저장하십시오. save saveRDS , 다음 번에 더 빨리 검색 할 수 있습니다. load readRDS.

390
Richie Cotton

다음은 data.table에서 fread을 사용하는 예제입니다. 1.8.7

예제는 fread에 대한 도움말 페이지에서, 내 창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
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 도움말 , 그래서 그것은 가치가있다.

한 가지 제안은 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)

여기서의 기본 아이디어는 데이터 가져 오기를 청크로 분해하는 것입니다. 병렬 프레임 워크 (예 : snow) 중 하나를 사용하고 파일을 세분화하여 병렬로 데이터 가져 오기를 실행할 수도 있지만 메모리 제약 조건에 부딪 힐 후 도움이되지 않는 대용량 데이터 세트의 경우 가능성이 높습니다. 이것이 map-reduce가 더 나은 접근 방식 인 이유입니다.

30
Shane

언급 할 가치가있는 작은 추가 포인트. 매우 큰 파일을 가지고 있다면 (헤더가 없다면) 행의 수를 계산할 수 있습니다 (여기서 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
5
Stephen Henderson

또 다른 방법은 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
Hector Haffenden

자주 나는 데이터베이스 (예 : Postgres) 내에 더 큰 데이터베이스를 유지하는 것이 우수 사례라고 생각합니다. 나는 (nrow * ncol) ncell = 10M보다 큰 것을 사용하지 않는다. 이것은 꽤 작다. 하지만 나는 종종 여러 개의 데이터베이스에서 쿼리를하는 동안 R이 메모리 집약적 인 그래프를 만들고 유지하기를 원한다. 32GB 랩탑의 미래에는 이런 종류의 메모리 문제가 사라질 것입니다. 그러나 데이터베이스를 사용하여 데이터를 보유하고 결과 쿼리 결과 및 그래프에 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