提问



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


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

最佳参考


几年后的更新


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



  1. 使用data.table中的fread将来自csv/制表符分隔文件的数据直接导入R.参见mnel的回答。[74] [75]

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

  3. 来自iotools
  4. read.csv.raw为快速阅读CSV文件提供了第三种选择。[79] [80]

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

  6. 以二进制格式存储数据对于提高性能也很有用。使用saveRDS/readRDS(见下文),h5rhdf5包用于HDF5格式,或write_fst/read_fst来自fst包。[89] [90] [91]






原始答案


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



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

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

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

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



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


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


或者,如果问题是您必须定期读取它,那么使用这些方法一次读取数据,然后将数据框保存为二进制blob,其中包含 save saveRDS,然后下次你可以用 load readRDS更快地检索它。[93] [94] [95]

其它参考1


这是一个利用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

其它参考2


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


关于将2GB或更多文本数据导入R数据框架的最佳方法,一直有一些讨论。昨天我写了一篇关于使用sqldf()将数据导入SQLite作为临时区域的博客文章然后将它从SQLite吸入R中。这对我来说非常有效。我能够在<5分钟内输入2GB(3列,40mm行)的数据。相比之下,read.csv命令运行整晚都没有完成。[96] [97]


这是我的测试代码:


设置测试数据:


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


奇怪的是,多年来没有人回答问题的底部部分,尽管这是一个重要的部分 - 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


这是之前在 R-Help 上提出来的,所以值得回顾。[98]


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


我不知道内存是否是一个问题,但您可能还想看一下 HadoopStreaming 包。它使用Hadoop,它是一个专门用于处理大型数据集的MapReduce框架。为此,您将使用hsTableReader函数。这是一个示例(但它有学习Hadoop的学习曲线):[99] [100]


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


还有一点值得一提。如果您有一个非常大的文件,您可以使用(其中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


通常我认为将更大的数据库保存在数据库(例如Postgres)中是一种很好的做法。我没有使用比(nrow * ncol)ncell=10M更大的任何东西,这是非常小的;但我经常发现我只想在我从多个数据库查询时创建并保存内存密集型图。 32 GB笔记本电脑,其中一些类型的内存问题将消失。但使用数据库来保存数据,然后使用Rs内存来生成查询结果和图形仍然可能很有用。一些优点是:


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

其它参考7


而不是传统的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"))