it-source

매우 큰 테이블을 데이터 프레임으로 신속하게 읽기

criticalcode 2023. 6. 15. 21:55
반응형

매우 큰 테이블을 데이터 프레임으로 신속하게 읽기

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

이것을 하는 더 좋은 방법이 있습니까?아니면 문제에 대한 완전히 다른 접근법?

몇 년 후의 업데이트

이 대답은 오래되었고, R은 넘어갔습니다.조금 더 빨리 달리도록 조정하는 것은 귀중한 이점이 거의 없습니다.옵션은 다음과 같습니다.

  1. 깔끔한 버전의 패키지에서 사용vroomcsv/tab으로 구분된 파일에서 Rtible로 직접 데이터를 가져오는 데 사용됩니다.헥터의 대답을 보세요.

  2. csv/tab으로 구분된 파일에서 R로 직접 데이터를 가져오기 위해 in을 사용합니다.씨넬의 대답.

  3. 에서 사용(2015년 4월부터 CRAN에서).이것은 다음과 같이 작동합니다.fread위. 링크의 readme는 두 기능의 차이를 설명합니다.readr "1.5-2"보다 고하고 있습니다.data.table::fread).

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

  5. 플랫 파일이 아닌 데이터베이스에 가능한 한 많은 데이터를 저장하려고 합니다.JD Long의 답변에 설명된 대로 패키지에서 데이터를 임시 SQLite 데이터베이스로 가져온 다음 R로 읽어 들입니다.참고 항목: 패키지 및 그 반대는 패키지 페이지의 섹션에 따라 다릅니다.MonetDB.R 데이터 프레임인 것처럼 가장하지만 실제로는 MonetDB인 데이터 유형을 제공하여 성능을 향상시킵니다.데이터를 해당 기능과 함께 가져옵니다.dplyr 에서는 여러 유형의 데이터베이스에 저장된 데이터로 직접 작업할 수 있습니다.

  6. 데이터를 이진 형식으로 저장하는 것도 성능 향상에 유용할 수 있습니다.사용하다saveRDS/readRDS(아래 참조), 또는 HDF5 형식의 패키지 또는write_fst/read_fst포장에서.


원답

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

  1. nrows= 데이터의 레코드 수(nmaxscan).

  2. 반시를 확인하세요.comment.char=""설명의 해석을 해제합니다.

  3. 다음을 사용하여 각 열의 클래스를 명시적으로 정의합니다.colClassesread.table.

  4. multi.line=FALSE검색 성능도 향상시킬 수 있습니다.

이 중 어느 것도 작동하지 않는 경우 프로파일링 패키지 중 하나를 사용하여 속도를 늦추는 회선을 결정합니다.아마도 당신은 축소된 버전을 쓸 수 있을 것입니다.read.table결과에 근거하여

또 다른 방법은 데이터를 R로 읽기 전에 필터링하는 것입니다.

또는 정기적으로 데이터를 읽어야 하는 것이 문제인 경우 이러한 방법을 사용하여 데이터를 한 번에 읽은 다음 데이터 프레임을 이진 블롭으로 저장하고 다음 번에는 를 사용하여 더 빠르게 검색할 수 있습니다. readRDS.

를 활용한 예가 있습니다.freaddata.table1.8.7

도움말 페이지의 예는 다음과 같습니다.freadWindows 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]

표준 읽기 테이블

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

저는 처음에 이 질문을 보지 못했고 며칠 후에 비슷한 질문을 했습니다.는 앞의 적으려고 , 가 어떻게 했습니다.sqldf()이를 위해.

2GB 이상의 텍스트 데이터를 R 데이터 프레임으로 가져오는 가장 좋은 방법에 대한 논의는 거의 없었습니다.어제 나는 사용에 대한 블로그 게시물을 썼습니다.sqldf()데이터를 준비 영역으로 SQLite로 가져온 다음 SQLite에서 R로 빨아들입니다.이것은 저에게 정말 잘 맞습니다. 행 수 .2GB(3열, 40mm 행)의 데이터를 5분 이내에 가져올 수 있었습니다. 으로적그조대, 그▁by▁theread.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()이는 (매핑을 제외한) 가장 빠른 방법이며 디스크 속도에 의해서만 제한됩니다.ASCII 파일을 구문 분석하는 것은 이진 데이터에 비해 본질적으로 느립니다(C에서도 마찬가지입니다.

이것이전에 R-Help에서 요청한 사항이므로 검토해 볼 가치가 있습니다.

한 가지 제안은 사용하는 것이었습니다.readChar()그런 다음 결과에 대한 문자열 조작을 수행합니다.strsplit()그리고.substr()이 readreadChar보다 적은 것을 알수.

메모리가 문제인지는 모르겠지만 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)

여기서 기본 아이디어는 데이터 가져오기를 청크로 분할하는 것입니다.병렬 프레임워크(예: Snow) 중 하나를 사용하여 파일을 분할하여 데이터 가져오기를 병렬로 실행할 수도 있지만, 메모리 제약에 부딪힐 수 있으므로 도움이 되지 않는 대규모 데이터 세트의 경우가 대부분이므로 맵 축소가 더 나은 방법입니다.

다른 은 대은다사것입다니는용하을음안▁the다것입니▁an▁use를 사용하는 것입니다.vroom이제 CRAN에서. vroom전체 파일을 로드하지 않고 각 레코드가 있는 위치를 인덱싱하며 나중에 파일을 사용할 때 읽습니다.

당신이 사용하는 것에 대해서만 지불하세요.

vroom 소개, vroomvroom 벤치마크 시작참조하십시오.

기본적인 개요는 대용량 파일의 초기 읽기 속도가 훨씬 빨라지고 이후의 데이터 수정 속도가 약간 느려질 수 있다는 것입니다.따라서 용도에 따라 가장 적합한 옵션이 될 수 있습니다.

아래 vroom 벤치마크의 단순화된 예를 참조하십시오. 주요 부분은 읽기 속도가 매우 빠르지만 Aggregate 등의 작업이 약간 느려지는 것입니다.

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

새로운 기능을 사용하여 데이터를 매우 빠르게 읽고 있습니다.arrow인 것 .상당히 초기 단계인 것으로 보입니다.

구체적으로 저는 parquet columnar 형식을 사용하고 있습니다.이것은 다시 변환됩니다.data.frameR에서, 하지만 그렇지 않으면 훨씬 더 깊은 속도 향상을 얻을 수 있습니다.이 형식은 파이썬에서도 사용할 수 있어 편리합니다.

이것에 대한 나의 주요 사용 사례는 상당히 제한된 RShiny 서버에 있습니다.이러한 이유로, 저는 데이터를 앱에 첨부(즉, SQL 밖)하는 것을 선호하기 때문에 파일 크기와 속도가 작아야 합니다.

이 링크된 기사는 벤치마킹과 좋은 개요를 제공합니다.아래에 몇 가지 흥미로운 점을 인용하였습니다.

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

파일 크기

즉, Parquet 파일은 gzip된 CSV의 절반 크기입니다.Parquet 파일이 작은 이유 중 하나는 사전 인코딩("사전 압축"이라고도 함) 때문입니다.사전 압축은 LZ4 또는 ZSTD(FST 형식으로 사용됨)와 같은 범용 바이트 압축기를 사용하는 것보다 훨씬 더 나은 압축을 생성할 수 있습니다.Parquet은 읽기 빠른 매우 작은 파일을 생성하도록 설계되었습니다.

읽기 속도

출력 유형별로 제어할 때(예: 모든 R data.frame 출력을 서로 비교) Parquet, Feather 및 FST의 성능이 서로 비교적 작은 마진 내에 있음을 알 수 있습니다.판다들도 마찬가지입니다.DataFrame outputs.data.table::fread는 1.5GB 파일 크기와 매우 경쟁력이 있지만 2.5GB CSV에서는 다른 파일 크기에 비해 뒤처집니다.


독립 테스트

저는 1,000,000개 행의 시뮬레이션된 데이터 세트에 대해 독립적인 벤치마킹을 수행했습니다.기본적으로 저는 압축에 도전하기 위해 많은 것들을 섞었습니다.또한 저는 무작위 단어의 짧은 텍스트 필드와 두 개의 시뮬레이션된 요인을 추가했습니다.

데이터.

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)

저는 이 데이터를 몇 가지 경쟁적인 옵션과 비교하여 읽어 보았는데, 예상되는 위의 기사와 약간 다른 결과를 얻었습니다.

benchmarking

이 파일은 벤치마크 기사만큼 크지 않기 때문에 아마도 그것이 차이일 것입니다.

테스트

  • rds: test_data.rds(20.3MB)
  • parquet2_native: (14.9MB, 더 높은 압축률 및as_data_frame = FALSE)
  • parquet2: test_data2.parquet(14.9MB, 더 높은 압축률)
  • parquet: test_data.parquet(40.7MB)
  • fst2: test_data2.fst(27.9MB, 더 높은 압축률)
  • fst: test_data.fst(76.8MB)
  • fread2: test_data.csv.gz (23.6)MB)
  • fread: test_data.csv(98.7)MB)
  • feather_message: test_data.페더(157.2MB 읽기)arrow)
  • feather: test_data.페더(157.2MB 읽기)feather)

관찰

는, 특파경우의일정이,fread사실 매우 빠릅니다.파일 크기가 작은 것이 매우 압축된 것이 좋습니다.parquet2 하는 데 할 수 . 시간을 투자하여 네이티브 데이터 형식으로 작업할 수 있습니다.data.frame정말로 속도를 높여야 한다면요.

여기서fst또한 훌륭한 선택입니다.로도▁the▁highly▁use다▁comp▁either▁i것니▁would입ressed저를 사용할 것입니다.fst 고도로 된 포맷또로압된축도고.parquet속도 또는 파일 크기의 트레이드오프가 필요한지 여부에 따라 달라집니다.

언급할 가치가 있는 사소한 추가 사항.파일 큰 (파이 큰경헤우를, (없경우는다매니))를를 참조하십시오.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

종종 데이터베이스(예: Postgres) 안에 더 큰 데이터베이스를 보관하는 것이 좋은 방법이라고 생각합니다.저는 (nrow * ncol) ncell = 10M보다 너무 큰 것은 사용하지 않습니다. 이는 꽤 작은 것입니다. 하지만 여러 데이터베이스에서 쿼리하는 동안에만 R이 메모리 집약적인 그래프를 만들고 보관하기를 원하는 경우가 많습니다.32GB 노트북의 미래에는 이러한 유형의 메모리 문제 중 일부가 사라질 것입니다.그러나 데이터베이스를 사용하여 데이터를 저장한 다음 결과 쿼리 결과와 그래프에 R의 메모리를 사용하는 매력은 여전히 유용할 수 있습니다.몇 가지 이점은 다음과 같습니다.

데이터는 데이터베이스에 로드된 상태로 유지됩니다.노트북을 다시 켤 때 pgadmin에서 원하는 데이터베이스에 다시 연결하기만 하면 됩니다.

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)

Spark 기반 솔루션을 가장 단순한 형태로 제공하고자 했습니다.

# Test Data ---------------------------------------------------------------

set.seed(123)
bigdf <-
    data.frame(
        dim = sample(letters, replace = T, 4e7),
        fact1 = rnorm(4e7),
        fact2 = rnorm(4e7, 20, 50)
    )
tmp_csv <- fs::file_temp(pattern = "big_df", ext = ".csv")
readr::write_csv(x = bigdf, file = tmp_csv)

# Spark -------------------------------------------------------------------

# Installing if needed
# sparklyr::spark_available_versions()
# sparklyr::spark_install()

library("sparklyr")
sc <- spark_connect(master = "local")

# Uploading CSV
system.time(tbl_big_df <- spark_read_csv(sc = sc, path = tmp_csv))

스파크 발생 시 상당히 양호한 결과:

>> system.time(tbl_big_df <- spark_read_csv(sc = sc, path = tmp_csv))
   user  system elapsed 
  0.278   0.034  11.747 

이것은 32GB RAM이 장착된 MacBook Pro에서 테스트되었습니다.

언급

스파크는 일반적으로 속도에 최적화된 패키지에 대해 "승리"할 수 없습니다.그럼에도 불구하고 Spark를 사용하여 답변을 제공하고 싶었습니다.

  • 프로세스가 작동하지 않았던 일부 의견과 답변의 경우 스파크를 사용하는 것이 실행 가능한 대안일 수 있습니다.
  • 장기적으로 볼 때, 가능한 한 많은 데이터를 입력합니다.data.frame 다른 가 발생할 수 .

그런 질문에 대해서는 1e7개 이상의 행을 처리하는 작업이 어디에 있는지 스파크를 고려해야 한다고 생각합니다. 로 "할지라도.data.frame그냥 기분이 좋지 않아요.모델 등을 배포할 때 해당 개체는 작업하기 어렵고 문제가 발생할 가능성이 높습니다.

기존의 read.table 대신 fread가 더 빠른 기능이라고 생각합니다.필요한 열만 선택하고 콜 클래스 및 문자열을 요인으로 지정하는 등의 추가 속성을 지정하면 파일을 가져오는 데 걸리는 시간이 단축됩니다.

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

저는 위의 모든 것을 시도했고 [독자][1]이 최고의 일을 했습니다.RAM이 8GB밖에 없습니다.

20개 파일, 각 5GB, 7개 열에 대한 루프:

read_fwf(arquivos[i],col_types = "ccccccc",fwf_cols(cnpj = c(4,17), nome = c(19,168), cpf = c(169,183), fantasia = c(169,223), sit.cadastral = c(224,225), dt.sitcadastral = c(226,233), cnae = c(376,382)))

언급URL : https://stackoverflow.com/questions/1727772/quickly-reading-very-large-tables-as-dataframes

반응형