在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是:
-
在
data.table
使用fread
将csv /制表符分隔文件中的数据直接导入R.请参阅mnel的答案 。 -
read_table
中使用read_table
(从2015年4月起在CRAN上)。 这很像上面的fread
。 链接中的readme解释了两个函数之间的区别(readr
目前声称比data.table::fread
慢data.table::fread
)。 -
来自
iotools
为快速读取CSV文件提供了第三个选项。 -
试图在数据库中存储尽可能多的数据而不是平面文件。 (作为一个更好的永久性存储介质,数据以二进制格式传递给R,速度更快)。如JD Long的答案所述,
sqldf
包中的sqldf
将数据导入临时SQLite数据库,然后将其读入R.另请参阅:RODBC
包,反向取决于DBI
包页面的部分。MonetDB.R
给你一个数据types,假装是一个数据框架,但实际上是一个MonetDB下面,提高性能。 用monetdb.read.csv
函数导入数据。dplyr
允许您直接处理存储在几种types的数据库中的数据。 -
以二进制格式存储数据对提高性能也是有用的。 使用
saveRDS
/readRDS
(见下文),或HDF5格式的h5
或rhdf5
软件包。
原来的答案
有几个简单的事情可以尝试,无论是使用read.table还是scan。
-
设置
nrows
= 数据中的logging数 (scan
nmax
)。 -
确保
comment.char=""
closures评论的解释。 -
在
read.table
使用colClasses
显式地定义每个列的类。 -
设置
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.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从多个数据库查询的时候创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"))