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

有更好的办法吗?或者完全不同的解决问题的方法?


当前回答

而不是传统的阅读。我觉得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"))

其他回答

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

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

这不会复制数据,所以它是即时的(不像所有其他方法)。它假设您已经相应地在列表中设置了names()。

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

Often times I think it is just good practice to keep larger databases inside a database (e.g. Postgres). I don't use anything too much larger than (nrow * ncol) ncell = 10M, which is pretty small; but I often find I want R to create and hold memory intensive graphs only while I query from multiple databases. In the future of 32 GB laptops, some of these types of memory problems will disappear. But the allure of using a database to hold the data and then using R's memory for the resulting query results and graphs still may be useful. Some advantages are:

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

这是几年后的一次更新

这个答案是旧的,而R已经继续了。调整阅读。表跑得快一点没有什么好处。你的选择是:

Using vroom from the tidyverse package vroom for importing data from csv/tab-delimited files directly into an R tibble. See Hector's answer. Using fread in data.table for importing data from csv/tab-delimited files directly into R. See mnel's answer. Using read_table in readr (on CRAN from April 2015). This works much like fread above. The readme in the link explains the difference between the two functions (readr currently claims to be "1.5-2x slower" than data.table::fread). read.csv.raw from iotools provides a third option for quickly reading CSV files. Trying to store as much data as you can in databases rather than flat files. (As well as being a better permanent storage medium, data is passed to and from R in a binary format, which is faster.) read.csv.sql in the sqldf package, as described in JD Long's answer, imports data into a temporary SQLite database and then reads it into R. See also: the RODBC package, and the reverse depends section of the DBI package page. MonetDB.R gives you a data type that pretends to be a data frame but is really a MonetDB underneath, increasing performance. Import data with its monetdb.read.csv function. dplyr allows you to work directly with data stored in several types of database. Storing data in binary formats can also be useful for improving performance. Use saveRDS/readRDS (see below), the h5 or rhdf5 packages for HDF5 format, or write_fst/read_fst from the fst package.


原来的答案

无论你是否使用read,都有一些简单的事情可以尝试。表或扫描。

设置nrows=数据中的记录数量(扫描中的nmax)。 确保你的评论。Char =""关闭注释的解释。 使用read.table中的colClasses显式定义每列的类。 设置多。line=FALSE也可以提高扫描的性能。

如果这些都不起作用,那么使用一个概要包来确定哪些行减慢了速度。也许你可以写一个删减版的read。表基于结果。

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

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

这个问题之前在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)

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

我正在阅读数据非常快地使用新的箭头包。它似乎还处于相当早期的阶段。

具体来说,我使用的是拼花柱状格式。这将转换回R中的data.frame,但如果不这样做,您可以获得更大的加速。这种格式很方便,因为它也可以从Python中使用。

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

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

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

文件大小

也就是说,Parquet文件甚至是gzip的CSV的一半大。Parquet文件如此小的原因之一是字典编码(也称为“字典压缩”)。字典压缩可以产生比使用LZ4或ZSTD(以FST格式使用)等通用字节压缩器更好的压缩效果。Parquet的设计目的是生成非常小的文件,以便快速读取。

读取速度

当通过输出类型控制时(例如,比较所有R data.frame输出),我们看到Parquet、Feather和FST的性能之间的差距相对较小。熊猫也是如此。DataFrame输出。数据。table::fread与1.5 GB文件大小的竞争令人印象深刻,但在2.5 GB CSV上落后于其他文件。


独立测试

我在一个1,000,000行的模拟数据集上执行了一些独立的基准测试。基本上,我打乱了一堆东西,试图挑战压缩。我还添加了一个随机单词和两个模拟因素的简短文本字段。

Data

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。拼花地板(14.9 MB压缩更高) 拼花:test_data。拼花(40.7 MB) fst2: test_data2。fst (27.9 MB高压缩) 置:test_data。fst (768 MB) fread2: test_data.csv.gz (23.6MB) test_data.csv (987 mb) feather_arrow: test_data。羽毛(157.2 MB带箭头读取) 羽毛:test_data。羽毛(157.2 MB用羽毛阅读)

观察

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

这里fst也是一个很好的选择。我要么使用高度压缩的fst格式,要么使用高度压缩的parquet格式,这取决于我是否需要在速度或文件大小方面进行权衡。