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

eyt*_*tan 489 import r dataframe r-faq

我有非常大的表(3000万行),我想加载为R中的数据帧 read.table()有很多方便的功能,但似乎实现中有很多逻辑会减慢速度.在我的情况下,我假设我提前知道列的类型,表不包含任何列标题或行名称,并且没有任何我必须担心的病态字符.

我知道在表格中阅读作为列表使用scan()可能非常快,例如:

datalist <- scan('myfile',sep='\t',list(url='',popularity=0,mintime=0,maxtime=0)))
Run Code Online (Sandbox Code Playgroud)

但是我将此转换为数据帧的一些尝试似乎将上述性能降低了6倍:

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

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

Ric*_*ton 408

几年后的更新

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

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

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

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

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

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


原来的答案

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

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

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

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

  4. 设置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秒. (4认同)
  • “feather”包有一种新的二进制格式,可以很好地与 Python 的 Pandas 数据帧配合使用 (2认同)
  • 我想也许你需要再次更新你的帖子关于包'羽毛`.对于读取数据,"羽毛"比"fread"快得多.例如,在4GB数据集中,我刚刚加载的`read_feather`比`fread`快4.5倍.为了保存数据,`fwrite`仍然更快.https://blog.dominodatalab.com/the-r-data-io-shootout/ (2认同)
  • 但羽毛的文件大小比RDS大得多.我不认为它支持压缩.RDS文件为216 MB,羽毛文件为4 GB.所以`feather`的阅读速度更快,但它使用了更多的存储空间. (2认同)
  • @Zboson如果您需要将数据帧存储在可以从 R 和 Python 访问的文件中,那么 `feather` 是一个不错的选择。如果您只关心能够读取 R 中的数据,那么“rds”是更好的选择。 (2认同)

mne*_*nel 272

这里是利用一个例子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]
Run Code Online (Sandbox Code Playgroud)

标准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
Run Code Online (Sandbox Code Playgroud)

优化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
Run Code Online (Sandbox Code Playgroud)

FREAD

require(data.table)
system.time(DT <- fread("test.csv"))                                  
 ##    user  system elapsed 
##    3.12    0.01    3.22
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

ff/ffdf

 require(ff)

 system.time(FFDF <- read.csv.ffdf(file="test.csv",nrows=n))   
 ##    user  system elapsed 
 ##   10.85    0.10   10.99
Run Code Online (Sandbox Code Playgroud)

综上所述:

##    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
Run Code Online (Sandbox Code Playgroud)

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

JD *_*ong 249

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

我在运行以下导入例程之前重新启动了R:

library(sqldf)
f <- file("bigdf.csv")
system.time(bigdf <- sqldf("select * from f", dbname = tempfile(), file.format = list(header = T, row.names = F)))
Run Code Online (Sandbox Code Playgroud)

我让以下一行整夜运行,但它从未完成:

system.time(big.df <- read.csv('bigdf.csv'))
Run Code Online (Sandbox Code Playgroud)


Sim*_*nek 73

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

attr(df, "row.names") <- .set_row_names(length(df[[1]]))
class(df) <- "data.frame"
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sha*_*ane 31

这是之前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)
Run Code Online (Sandbox Code Playgroud)

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


Ada*_*dam 9

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

具体来说,我使用的是镶木地板柱状格式。这将转换回data.frameR 中的 a,但如果不这样做,您可以获得更深的加速。这种格式很方便,因为它也可以从 Python 中使用。

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

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

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

文件大小

也就是说,Parquet 文件的大小只有 gzip 压缩的 CSV 文件的一半。Parquet 文件如此小的原因之一是因为字典编码(也称为“字典压缩”)。与使用通用字节压缩器(如 LZ4 或 ZSTD(用于 FST 格式))相比,字典压缩可以产生明显更好的压缩。Parquet 旨在生成非常小的文件,可以快速读取。

读取速度

当按输出类型进行控制时(例如,将所有 R 数据帧输出相互比较),我们看到 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))
         )
Run Code Online (Sandbox Code Playgroud)

读和写

写入数据很容易。

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)
Run Code Online (Sandbox Code Playgroud)

读取数据也很容易。

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)
Run Code Online (Sandbox Code Playgroud)

我针对几个竞争选项测试了读取这些数据,并且确实得到了与上面文章略有不同的结果,这是预期的。

标杆

这个文件远没有基准文章那么大,所以也许这就是区别。

测试

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

观察

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

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


Hec*_*den 8

另一种方法是使用该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
Run Code Online (Sandbox Code Playgroud)


Ste*_*son 5

还有一点值得一提.如果您有一个非常大的文件,您可以动态计算使用的行数(如果没有标题)(bedGraph您的工作目录中文件的名称):

>numRow=as.integer(system(paste("wc -l", bedGraph, "| sed 's/[^0-9.]*\\([0-9.]*\\).*/\\1/'"), intern=T))
Run Code Online (Sandbox Code Playgroud)

然后你可以使用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
Run Code Online (Sandbox Code Playgroud)


rfe*_*isx 5

很多时候我认为将较大的数据库保存在数据库中是一种很好的做法(例如 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)
Run Code Online (Sandbox Code Playgroud)