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运行速度更快一点也没有什么好处.你的选择是:
使用fread在data.table导入CSV从/制表符分隔的文件中的数据直接导入R.见MNEL的答案.
使用read_table中readr(从2015年4月在CRAN).这很像fread上面的工作.链接中的自述文件解释了两个功能之间的差异(readr目前声称"慢1.5-2倍" data.table::fread).
read.csv.rawfrom iotools提供了快速读取CSV文件的第三个选项.
尝试在数据库而不是平面文件中存储尽可能多的数据.(除了作为一种更好的永久存储介质,数据以二进制格式传递给R和从R传递,这更快.)read.csv.sql在sqldf包中,如JD Long的回答所述,将数据导入临时SQLite数据库,然后读取它进入R.参见:RODBC包,以及DBI包页面的反向部分.MonetDB.R为您提供一个数据类型,假装是一个数据框,但实际上是一个MonetDB,提高了性能.导入数据及其monetdb.read.csv功能. dplyr允许您直接处理存储在多种类型数据库中的数据.
以二进制格式存储数据对于提高性能也很有用.使用saveRDS/ readRDS(见下文),HDF5格式的包或/ h5或rhdf5包中的write_fst/ .read_fstfst
原来的答案
无论您使用read.table还是scan,都可以尝试一些简单的事情.
Set nrows= 数据中的记录数(nmaxin scan).
确保comment.char=""关闭注释的解释.
使用colClassesin 显式定义每列的类read.table.
设置multi.line=FALSE还可以提高扫描性能.
如果这些都不起作用,那么使用其中一个分析包来确定哪些行减慢了速度.也许你可以read.table根据结果编写一个减少版本.
另一种方法是在将数据读入R之前过滤数据.
或者,如果问题是您必须定期读取它,那么使用这些方法一次读取数据,然后将数据帧保存为二进制blob savesaveRDS,然后下次你可以更快地检索它 loadreadRDS.
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)
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)
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)
require(data.table)
system.time(DT <- fread("test.csv"))
## user system elapsed
## 3.12 0.01 3.22
Run Code Online (Sandbox Code Playgroud)
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)
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)
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中).
Sha*_*ane 31
一个建议有使用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是一种更好的方法.
我正在使用新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)
我针对几个竞争选项测试了读取这些数据,并且确实得到了与上面文章略有不同的结果,这是预期的。
这个文件远没有基准文章那么大,所以也许这就是区别。
as_data_frame = FALSE)arrow)feather)对于这个特定的文件,fread实际上是非常快的。我喜欢高度压缩parquet2测试的小文件大小。data.frame如果我真的需要加快速度,我可能会花时间使用本机数据格式而不是。
这里fst也是不错的选择。我会使用高度压缩的fst格式或高度压缩的格式,这parquet取决于我是否需要在速度或文件大小之间进行权衡。
另一种方法是使用该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)
还有一点值得一提.如果您有一个非常大的文件,您可以动态计算使用的行数(如果没有标题)(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)
很多时候我认为将较大的数据库保存在数据库中是一种很好的做法(例如 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)