快速读取非常大的表作为数据帧

时间:2009-11-13 07:53:34

标签: r import dataframe r-faq

我有非常大的表(3000万行),我想在R中加载数据帧。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))))

有更好的方法吗?或者很可能完全不同的方法解决问题?

11 个答案:

答案 0 :(得分:389)

几年后的更新

这个答案很老了,R继续前进。调整read.table以更快地运行几乎没有什么好处。您的选择是:

  1. 使用fread中的data.table将数据从csv /制表符分隔的文件直接导入到R.请参阅mnel's answer

  2. read_table中使用readr(自2015年4月起在CRAN上)。这与上面的fread非常相似。链接中的自述文件解释了两个函数之间的差异(readr目前声称“比data.table::fread慢1.5-2倍”。

  3. 来自read.csv.raw
  4. iotools提供了快速阅读CSV文件的第三个选项。

  5. 尝试在数据库而不是平面文件中存储尽可能多的数据。 (除了作为更好的永久存储介质之外,数据以二进制格式传递到R和从R传递,速度更快。)read.csv.sql包中的sqldf,如JD Long's answer中所述,将数据导入临时SQLite数据库,然后将其读入R.另请参阅:RODBC包,以及DBI package页面的反向部分。 MonetDB.R为您提供了一种假装成数据框但实际上是MonetDB的数据类型,从而提高了性能。使用monetdb.read.csv函数导入数据。 dplyr允许您直接处理存储在多种类型数据库中的数据。

  6. 以二进制格式存储数据对于提高性能也很有用。使用saveRDS / readRDS(见下文),HDF5格式的h5rhdf5个套餐,或{write_fst / read_fst 3}}包。


  7. 原始答案

    无论您使用read.table还是扫描,都可以尝试一些简单的事情。

    1. 设置nrows = 数据中的记录数nmax中的scan)。

    2. 确保comment.char=""关闭评论解释。

    3. 使用colClasses中的read.table明确定义每列的类。

    4. 设置multi.line=FALSE也可以提高扫描效果。

    5. 如果这些都不起作用,那么使用其中一个fst来确定哪些行减慢了速度。也许您可以根据结果编写read.table的缩减版本。

      另一种选择是在将数据读入R之前过滤数据。

      或者,如果问题是你必须定期阅读它,那么使用这些方法一次读取数据,然后将数据框保存为带有 profiling packages save,然后下次您可以使用 saveRDS readRDS更快地检索它。

答案 1 :(得分:265)

以下是使用fread 1.8.7

中的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

的fread

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

答案 2 :(得分:246)

我最初没有看到这个问题,并在几天后问了一个类似的问题。我将把我之前的问题记下来,但我想我会在这里添加一个答案来解释我是如何使用sqldf()来做这个的。

关于将2GB或更多文本数据导入R数据帧的最佳方法,已有little bit of discussion。昨天我写了blog post关于使用sqldf()将数据导入SQLite作为临时区域,然后将其从SQLite吸入R中。这对我来说非常有用。我能够在&lt;中提取2GB(3列,40mm行)的数据。 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'))

答案 3 :(得分:71)

奇怪的是,多年来没有人回答问题的底部,即使这是一个重要的问题 - 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中)。]

答案 4 :(得分:30)

这是之前的asked on R-Help,所以值得一看。

有一项建议是使用readChar(),然后使用strsplit()substr()对结果进行字符串操作。您可以看到readChar中涉及的逻辑远小于read.table。

我不知道内存是否存在问题,但您也可能want to take a look at the HadoopStreaming package。这个uses 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)

这里的基本思想是将数据导入到块中。您甚至可以使用其中一个并行框架(例如雪)并通过分割文件并行运行数据导入,但最有可能的是大型数据集无法帮助,因为您将遇到内存限制,这就是为什么map-reduce是一种更好的方法。

答案 5 :(得分:5)

值得一提的小额外点。如果你有一个非常大的文件,你可以动态计算行数(如果没有标题)使用(其中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

答案 6 :(得分:5)

一种替代方法是使用vroom包。现在在CRAN上。 vroom不会加载整个文件,它会索引每个记录的位置,并在以后使用时读取。

  

仅按使用量付费。

请参见Introduction to vroomGet started with vroomvroom benchmarks

基本概述是,初次读取大文件会更快,而对数据的后续修改可能会稍慢。因此,根据您的用途,这可能是最佳选择。

请参见下面的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

答案 7 :(得分:4)

我认为通常将较大的数据库保存在数据库(例如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)

答案 8 :(得分:2)

我正在使用新的arrow包非常快速地读取数据。它似乎还处于早期阶段。

具体地说,我使用的是镶木地板列格式。这将转换回R中的data.frame,但如果不这样做,则可以实现更深的加速。这种格式很方便,因为它也可以在Python中使用。

我主要的用例是在相当受限制的RShiny服务器上。由于这些原因,我更喜欢将数据附加到应用程序(即,不使用SQL),因此需要较小的文件大小和速度。

此链接文章提供了基准测试和良好的概述。我在下面引用了一些有趣的观点。

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

文件大小

  

也就是说,Parquet文件的大小甚至是压缩后的CSV的一半。 Parquet文件如此之小的原因之一是由于字典编码(也称为“字典压缩”)。与使用通用字节压缩程序(如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行的模拟数据集执行了一些独立的基准测试。基本上,我整理了一堆东西来尝试挑战压缩。另外,我添加了一个由随机单词和两个模拟因子组成的短文本字段。

数据

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.3 MB)
  • parquet2_native:(具有更高压缩比和as_data_frame = FALSE的14.9 MB)
  • 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)
  • 读取: test_data.csv(98.7MB)
  • feather_arrow: test_data.feather(使用arrow读取了157.2 MB)
  • 羽毛: test_data.feather(用feather读取157.2 MB)

观察

对于这个特定文件,fread实际上非常快。我喜欢高度压缩的parquet2测试中的小文件大小。如果我确实需要提高速度,我可能会花一些时间来处理本机数据格式而不是data.frame

这里fst也是一个不错的选择。我会使用高度压缩的fst格式还是高度压缩的parquet格式,这取决于我是否需要权衡速度或文件大小。

答案 9 :(得分: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"))

答案 10 :(得分:0)

我已经尝试了以上所有方法,iotools做得最好。我只有8GB RAM

循环20个文件,每个5gb,5列:

input.file(file[1], formatter = dstrfw, col_types = c("character", "character", "character", "character", "character"), width=c(2L,15L,1L,150L,14L))

最好的部分是,循环后,RAM内存被清理,下一个循环工作得很好。