在R中快速读取非常大的表格作为数据框

我有非常大的表(3000万行),我想在R中加载一个数据read.table()有很多方便的function,但是似乎有很多逻辑在执行,会慢事情倒了。 在我的情况下,我假设我知道列的types提前,该表不包含任何列标题或行名称,并没有任何病态字符,我不必担心。

我知道使用scan()作为列表读取表格可能会很快,例如:

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

但是,我的一些尝试将其转换为dataframe似乎将上述性能降低了6倍:

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

有没有更好的方法来做到这一点? 或者可能完全不同的方法来解决这个问题?

几年后的更新

这个答案是旧的,R已经开始了。 调整read.table运行得更快有一点宝贵的好处。 您的select是:

  1. data.table使用fread将csv /制表符分隔文件中的数据直接导入R.请参阅mnel的答案 。

  2. read_table中使用read_table (从2015年4月起在CRAN上)。 这很像上面的fread 。 链接中的readme解释了两个函数之间的区别( readr目前声称比data.table::freaddata.table::fread )。

  3. 来自iotools为快速读取CSV文件提供了第三个选项。

  4. 试图在数据库中存储尽可能多的数据而不是平面文件。 (作为一个更好的永久性存储介质,数据以二进制格式传递给R,速度更快)。如JD Long的答案所述, sqldf包中的sqldf将数据导入临时SQLite数据库,然后将其读入R.另请参阅: RODBC包,反向取决于DBI包页面的部分。 MonetDB.R给你一个数据types,假装是一个数据框架,但实际上是一个MonetDB下面,提高性能。 用monetdb.read.csv函数导入数据。 dplyr允许您直接处理存储在几种types的数据库中的数据。

  5. 以二进制格式存储数据对提高性能也是有用的。 使用saveRDS / readRDS (见下文),或HDF5格式的h5rhdf5软件包。


原来的答案

有几个简单的事情可以尝试,无论是使用read.table还是scan。

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

  2. 确保comment.char=""closures评论的解释。

  3. read.table使用colClasses显式地定义每个列的类。

  4. 设置multi.line=FALSE也可以提高扫描的性能。

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

另一种方法是在读取数据之前过滤数据。

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

这是一个利用data.table 1.8.7中的fread的例子

例子来自帮助页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 

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

关于将2GB或更多的文本数据导入R数据框的最佳方法,已经有一些讨论 。 昨天我写了一篇关于使用sqldf()将数据导入到SQLite作为暂存区域的博客文章 ,然后从SQLite中将其吸收到R中。这对我来说真的很好。 我能够在<5分钟内获得2GB(3列,40mm行)的数据。 相比之下, read.csv命令运行了一夜,从未完成。

这是我的testing代码:

设置testing数据:

 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() – 这是迄今为止最快的方法(而不是映射),只受磁盘速度的限制。 与二进制数据相比,parsingASCII文件本身就很慢(即使在C中)。]

之前在R-Help上询问过这个问题 ,值得一读。

一个build议是使用readChar() ,然后用strsplit()substr()对结果进行string处理。 你可以看到readChar所涉及的逻辑远远less于read.table。

我不知道内存是否是一个问题,但你也可能想看看HadoopStreaming包 。 这使用Hadoop ,它是一个专为处理大型数据集而devise的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是一个更好的方法。

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

很多时候,我认为将更大的数据库保存在数据库中是一种很好的做法(例如Postgres)。 我不使用任何比(nrow * ncol)ncell = 10M大很多的东西, 但是我经常发现我只想在R从多个数据库查询的时候创build并保存内存密集型graphics。 在32 GB的笔记本电脑的未来,这些types的内存问题将消失。 但是,使用数据库来保存数据,然后使用R的内存作为结果查询结果和graphics的魅力仍然可能是有用的。 一些优点是:

(1)数据保持加载到您的数据库中。 当您重新打开笔记本电脑时,只需将pgadmin重新连接到所需的数据库即可。

(2)R可以做比SQL更多的漂亮的统计和graphics操作。 但是我认为SQL更适合查询大量的数据。

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

而不是传统的read.table,我觉得fread是一个更快的function。 指定其他属性,如只select所需的列,指定colclasses和string作为因子将减less导入文件的时间。

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