当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《快速读取非常大的表作为数据帧》经验,为你挑选了7个好方法。

我有非常大的表(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))))

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



1> Richie Cotto..:

几年后的更新

这个答案很老了,R继续前进.调整read.table运行速度更快一点也没有什么好处.你的选择是:

    使用freaddata.table导入CSV从/制表符分隔的文件中的数据直接导入R.见MNEL的答案.

    使用read_tablereadr(从2015年4月在CRAN).这很像fread上面的工作.链接中的自述文件解释了两个功能之间的差异(readr目前声称"慢1.5-2倍" data.table::fread).

    read.csv.rawfrom iotools提供了快速读取CSV文件的第三个选项.

    尝试在数据库而不是平面文件中存储尽可能多的数据.(除了作为一种更好的永久存储介质,数据以二进制格式传递给R和从R传递,这更快.)read.csv.sqlsqldf包中,如JD Long的回答所述,将数据导入临时SQLite数据库,然后读取它进入R.参见:RODBC包,以及DBI包页面的反向部分.MonetDB.R为您提供一个数据类型,假装是一个数据框,但实际上是一个MonetDB,提高了性能.导入数据及其monetdb.read.csv功能. dplyr允许您直接处理存储在多种类型数据库中的数据.

    以二进制格式存储数据对于提高性能也很有用.使用saveRDS/ readRDS(见下文),HDF5格式的包或/ h5rhdf5包中的write_fst/ .read_fstfst


原来的答案

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

    Set nrows= 数据中的记录数(nmaxin scan).

    确保comment.char=""关闭注释的解释.

    使用colClassesin 显式定义每列的类read.table.

    设置multi.line=FALSE还可以提高扫描性能.

如果这些都不起作用,那么使用其中一个分析包来确定哪些行减慢了速度.也许你可以read.table根据结果编写一个减少版本.

另一种方法是在将数据读入R之前过滤数据.

或者,如果问题是您必须定期读取它,那么使用这些方法一次读取数据,然后将数据帧保存为二进制blob save saveRDS,然后下次你可以更快地检索它 load readRDS.


感谢Richie的提示.我做了一些测试,似乎使用read.table的nrow和colClasses选项获得的性能提升非常适中.例如,读取~7M行表需要78s而没有选项,67s带有选项.(注意:该表有1个字符列,4个整数列,我使用comment.char =''和stringsAsFactors = FALSE读取).尽可能使用save()和load()是一个很好的提示 - 一旦与save()一起存储,同一个表只需加载12秒.
我想也许你需要再次更新你的帖子关于包'羽毛`.对于读取数据,"羽毛"比"fread"快得多.例如,在4GB数据集中,我刚刚加载的`read_feather`比`fread`快4.5倍.为了保存数据,`fwrite`仍然更快.https://blog.dominodatalab.com/the-r-data-io-shootout/
但羽毛的文件大小比RDS大得多.我不认为它支持压缩.RDS文件为216 MB,羽毛文件为4 GB.所以`feather`的阅读速度更快,但它使用了更多的存储空间.

2> mnel..:

这里是利用一个例子fread,从data.table1.8.7

这些示例来自帮助页面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


很好的答案,基准测试在其他情况下也适用.只需使用`fread`读取4GB文件即可.曾尝试用基本的R函数读取它,花了大约15个小时.
@ivoWelch你可以使用`fread("test.csv",data.table = FALSE)`
@mnel你可以重新运行基准测试并包含`readr`吗?
第二个@jangorecki.另外,鉴于`fread`现在有一些真正的竞争对手,可能有助于为优化的`fread`用法添加基准 - 指定`colClasses`等.

3> JD Long..:

我最初没有看到这个问题,并在几天后问了一个类似的问题.我将把我之前的问题记下来,但我想我会在这里添加一个答案来解释我以前是怎么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'))



4> Simon Urbane..:

奇怪的是,多年来没有人回答问题的底部部分,即使这是一个重要的部分 - data.frame只是具有正确属性的列表,所以如果你有大数据,你不想使用as.data.frame或类似的列表.简单地将列表"转"为就地数据框要快得多:

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

这不会使数据副本立即生成(与所有其他方法不同).它假定您已经相应地设置names()了列表.

[至于将大数据加载到R中 - 我个人将它们按列转储到二进制文件中并使用readBin()- 这是迄今为止最快的方法(除了映射)并且仅受磁盘速度的限制.与二进制数据相比,解析ASCII文件本质上很慢(即使在C中).


使用`tracmem`表示`attr <-`和`class <-`在内部进行复制.`bit :: setattr`或`data.table :: setattr`不会.
也许你使用了错误的订单?如果使用`df = scan(...),则没有副本; 名字(DF)= ...; ATTR ...; class ...` - 参见`tracemem()`(在R 2.15.2中测试)
您能详细说明如何按列将大数据转储到二进制文件中吗?

5> Shane..:

这是之前在R-Help上提出的,所以值得回顾.

一个建议有使用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)

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



6> Hector Haffe..:

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

只支付您使用的费用。

见介绍弗鲁姆,开始使用弗鲁姆和弗鲁姆基准。

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

请从下面的vroom基准中查看一个简化的示例,要看的关键部分是超快速的读取时间,但是诸如聚合等的操作会稍微减少。

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> Stephen Hend..:

还有一点值得一提.如果您有一个非常大的文件,您可以动态计算使用的行数(如果没有标题)(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

推荐阅读
赛亚兔备_393
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有