549

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

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

4

12 回答 12

479

几年后的更新

这个答案很旧,R 已经继续前进。调整read.table跑得快一点几乎没有什么好处。您的选择是:

  1. 使用vroomtidyverse 包vroom将数据从 csv/tab 分隔文件直接导入 R tibble。见赫克托的回答

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

  3. 使用read_tablein readr(从 2015 年 4 月起在 CRAN 上使用)。这很像fread上面的工作。链接中的自述文件解释了这两个函数之间的区别(readr目前声称比“慢 1.5-2 倍” data.table::fread)。

  4. read.csv.rawfromiotools提供了快速读取 CSV 文件的第三个选项。

  5. 尝试在数据库而不是平面文件中存储尽可能多的数据。(作为一种更好的永久存储介质,数据以二进制格式传入和传出 R,速度更快。)read.csv.sqlsqldf包中,如JD Long 的回答中所述,将数据导入临时 SQLite 数据库,然后读取它进入R。另见:包,以及页面RODBC的反向依赖部分。为您提供一种伪装成数据框但实际上是底层 MonetDB 的数据类型,从而提高性能。使用其功能导入数据。 允许您直接处理存储在多种类型数据库中的数据。DBIMonetDB.Rmonetdb.read.csvdplyr

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


原来的答案

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

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

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

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

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

如果这些都不起作用,则使用其中一个分析包来确定哪些行正在减慢速度。read.table也许您可以根据结果编写一个精简版。

另一种选择是在将数据读入 R 之前对其进行过滤。

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

于 2009-11-13T10:35:05.553 回答
294

这是一个使用fread1.8.7data.table的示例

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

标准读表

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

恐惧

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
于 2013-02-25T01:07:39.163 回答
257

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

关于将 2GB 或更多文本数据导入 R 数据帧的最佳方式,已经有了一些讨论。昨天我写了一篇关于使用sqldf()将数据导入 SQLite 作为暂存区,然后将其从 SQLite 吸入 R 的博客文章。这对我来说非常有效。我能够在 5 分钟内提取 2GB(3 列,40 毫米行)的数据。相比之下,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'))
于 2009-11-30T15:48:11.487 回答
79

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

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

这不会复制数据,因此它是即时的(与所有其他方法不同)。它假定您已经names()在列表中进行了相应的设置。

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

于 2012-12-20T04:01:40.743 回答
33

这是以前在R-Help上提出的,因此值得回顾。

一个建议是使用andreadChar()对结果进行字符串操作。可以看到readChar所涉及的逻辑比read.table少很多。strsplit()substr()

我不知道这里的内存是否是个问题,但您可能还想看看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)

这里的基本思想是将数据导入分解成块。您甚至可以使用其中一种并行框架(例如 snow)并通过对文件进行分段来并行运行数据导入,但很可能对于大型数据集无济于事,因为您会遇到内存限制,这就是为什么 map-reduce 是一种更好的方法。

于 2009-11-13T15:18:57.513 回答
14

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

只需为您使用的内容付费。

请参阅vroom 简介、vroom入门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
于 2019-05-07T21:00:10.210 回答
10

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

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

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

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

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

文件大小

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

读取速度

在按输出类型进行控制时(例如,将所有 R data.frame 输出相互比较),我们看到 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))
         )

读和写

写入数据很容易。

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)

读取数据也很容易。

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)

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

基准测试

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

测试

  • 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)
  • 羽毛箭头: test_data.feather(157.2 MB 读取arrow
  • 羽毛: test_data.feather(157.2 MB 读取feather

观察

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

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

于 2019-11-12T18:31:21.120 回答
6

值得一提的一个小额外点。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
于 2013-11-28T17:20:55.193 回答
6

很多时候,我认为将较大的数据库保存在数据库中(例如 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)
于 2014-08-22T16:13:43.870 回答
0

而不是传统的 read.table 我觉得 fread 是一个更快的功能。指定附加属性,如仅选择所需的列、指定 colclasses 和字符串作为因素将减少导入文件所需的时间。

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"))
于 2015-04-18T07:22:01.190 回答
0

我已经尝试了以上所有方法,[readr][1] 做得最好。我只有 8GB 内存

循环 20 个文件,每个 5gb,7 列:

read_fwf(arquivos[i],col_types = "ccccccc",fwf_cols(cnpj = c(4,17), nome = c(19,168), cpf = c(169,183), fantasia = c(169,223), sit.cadastral = c(224,225), dt.sitcadastral = c(226,233), cnae = c(376,382)))
于 2019-12-29T00:41:32.793 回答
0

我想以最简单的形式贡献基于 Spark 的解决方案:

# Test Data ---------------------------------------------------------------

set.seed(123)
bigdf <-
    data.frame(
        dim = sample(letters, replace = T, 4e7),
        fact1 = rnorm(4e7),
        fact2 = rnorm(4e7, 20, 50)
    )
tmp_csv <- fs::file_temp(pattern = "big_df", ext = ".csv")
readr::write_csv(x = bigdf, file = tmp_csv)

# Spark -------------------------------------------------------------------

# Installing if needed
# sparklyr::spark_available_versions()
# sparklyr::spark_install()

library("sparklyr")
sc <- spark_connect(master = "local")

# Uploading CSV
system.time(tbl_big_df <- spark_read_csv(sc = sc, path = tmp_csv))

Spark 产生了相当好的结果:

>> system.time(tbl_big_df <- spark_read_csv(sc = sc, path = tmp_csv))
   user  system elapsed 
  0.278   0.034  11.747 

这是在 32GB 内存的 MacBook Pro 上测试的。

评论

Spark,通常不应该能够“赢得”针对速度优化的软件包。不过,我想使用 Spark 提供一个答案:

  • 对于一些使用 Spark 无法正常工作的评论和答案,可能是一个可行的替代方案
  • data.frame从长远来看,当尝试对该对象进行其他操作并达到架构的性能范围时,将尽可能多的数据敲入可能会在以后证明是有问题的

我认为对于这样的问题,应该考虑处理 1e7 或更多行 Spark 的任务。即使有可能将这些数据“敲入”单个数据,data.frame它也只是感觉不对。在部署模型等时,该对象可能难以使用并产生问题。

于 2021-12-18T20:19:47.603 回答