问题 将多个csv文件更快地读入data.table R.


我有900000个csv文件,我想组合成一个大的 data.table。对于这种情况,我创建了一个 for loop 它逐个读取每个文件并将它们添加到 data.table。问题是它的执行速度变慢,所用的时间呈指数级增长。如果有人可以帮助我让代码运行得更快,那就太棒了。每个csv文件都有300行和15列。 我到目前为止使用的代码:

library(data.table)
setwd("~/My/Folder")

WD="~/My/Folder"
data<-data.table(read.csv(text="X,Field1,PostId,ThreadId,UserId,Timestamp,Upvotes,Downvotes,Flagged,Approved,Deleted,Replies,ReplyTo,Content,Sentiment"))

csv.list<- list.files(WD)
k=1

for (i in csv.list){
  temp.data<-read.csv(i)
  data<-data.table(rbind(data,temp.data))

  if (k %% 100 == 0)
    print(k/length(csv.list))

  k<-k+1
}

4958
2017-07-09 11:39


起源

R可能不是正确的工具;例如,请参阅Spacedman的答案 stackoverflow.com/a/11433740/210673 - Aaron
它可能是一种亵渎 [R 问题,但是 csvstack 可以快速完成组合: csvkit.readthedocs.org/en/0.9.1/scripts/csvstack.html ( pip install csvkit )。你肯定想要使用 data.table::fread 但是,在那个结果上,GIANT CSV文件。 - hrbrmstr
两点:即使每个条目的大小仅为4字节,内存中的最终大小将为4字节* 15列* 300行* 900000文件/ 1024 ^ 3> = 15 GB。运用 rbind() 和其他记忆强烈的复制技术将使数量翻倍 - Christian Borck
也许你可以合并所有csv文件,如 cat *.csv > merged.csv 然后只导入生成的merged.csv文件。 - Daniel Fischer
首先,你为什么要使用 data.table 而不是使用 fread?接下来,不要重新分配 <- 运营商。这会将您的表复制到每个循环周期的新实例。 - Serban Tanasa


答案:


假设您的文件是传统的csv,我会使用 data.table::fread 因为它更快。如果你在类似Linux的操作系统上,我会使用它允许shell命令的事实。假设您的输入文件是我要执行的文件夹中唯一的csv文件:

dt <- fread("tail -n-1 -q ~/My/Folder/*.csv")

之后您需要手动设置列名称。

如果你想把东西放在R里,我会用 lapply 和 rbindlist

lst <- lapply(csv.list, fread)
dt <- rbindlist(lst)

你也可以使用 plyr::ldply

dt <- setDT(ldply(csv.list, fread))

这有一个优点,你可以使用 .progress = "text" 了解阅读进度。

以上所有假设文件都具有相同的格式并具有标题行。


7
2017-07-09 13:09



rbindlist(lst) 应该超越 do.call("rbind", lst) - GSee
@GSee谢谢。编辑如上 - Nick Kennedy
@Frank哎呀。谢谢你选择了。 - Nick Kennedy
IIRC rbindlist 只是在 1.9.5 (目前,开发版) - MichaelChirico
@MichaelChirico rbindlist 在开发版本中有新功能,但已经出现了许多版本。 - Dean MacGregor


正如@Repmat所建议的那样,使用rbind.fill。正如@Christian Borck所建议的那样,使用fread来获得更快的读取速度。

require(data.table)
require(plyr)

files <- list.files("dir/name")
df <- rbind.fill(lapply(files, fread, header=TRUE))

或者你可以使用do.call,但rbind.fill更快(http://www.r-bloggers.com/the-rbinding-race-for-vs-do-call-vs-rbind-fill/

df <- do.call(rbind, lapply(files, fread, header=TRUE))

或者您可以使用data.table包, 看到这个


3
2017-07-09 12:54



fread和rbind.fill很棒!我使用这个精确的组合来从网上抓取大量文件,所以我可以保证! - Serban Tanasa
是 rbind.fill 比...更好 rbind(..., fill=TRUE)? - GSee
是的,你能想到的几乎所有东西都比rbind快。但是你不会注意到小文件或少量文件的区别。 - Repmat
@Repmat既然如此 data.table在这种情况下合并,我怀疑 rbind.fill 是比较快的。该 data.table 包装有些神奇 rbind - GSee
我还没有对它进行测试,但我认为你需要做rbindlist才能从数据包data.table获得速度。至少包本身将rbindlist描述为:“与do.call(”rbind“,l)相同,但速度要快得多。” - 但我可能错了。 - Repmat


您正在for循环中增长数据表 - 这就是为什么它需要永远。如果要保持for循环不变,首先要创建一个空数据框(在循环之前),它具有您需要的尺寸(行x列),并将其放在RAM中。

然后在每次迭代中写入此空帧。

否则使用包plyr中的rbind.fill - 并避免循环altogehter。 要使用rbind.fill:

require(plyr)
data <- rbind.fill(df1, df2, df3, ... , dfN)

要传递df的名称,您可以/应该使用apply函数。


2
2017-07-09 12:07



你是对的,更新了。 - Repmat
你能解释一下如何正确使用rbind.fill吗? - Carlo
如果存在约束,初始化最终数据帧会更好 data.table 放弃data.table不会是最好的解决方案。 - Dean MacGregor


建立在 尼克肯尼迪的回答 运用 plyr::ldply 通过启用大约50%的速度增加 .parallel 选项同时读取400个csv文件,每个大约30-40 MB。

带进度条的原始答案

dt <- setDT(ldply(csv.list, fread, .progress="text")

启用 .parallel 还有一个文本进度条

library(plyr)
library(data.table)
library(doSNOW)

cl <- makeCluster(4)
registerDoSNOW(cl)

pb <- txtProgressBar(max=length(csv.list), style=3)
pbu <- function(i) setTxtProgressBar(pb, i)
dt <- setDT(ldply(csv.list, fread, .parallel=TRUE, .paropts=list(.options.snow=list(progress=pbu))))

stopCluster(cl)

2
2017-12-16 23:46





我使用@Repmat作为您当前的解决方案 rbind() 每次调用时都会在内存中复制整个data.table(这就是时间呈指数增长的原因)。虽然另一种方法是首先创建一个只包含头文件的空csv文件,然后简单地将所有文件的数据附加到此csv文件中。

write.table(fread(i), file = "your_final_csv_file", sep = ";",
            col.names = FALSE, row.names=FALSE, append=TRUE, quote=FALSE)

这样您就不必担心将数据放入data.table中的正确索引。另外作为提示: fread() 是data.table文件阅读器,它比read.csv快得多。

在generell中,R不会是我数据修改任务的首选。


1
2017-07-09 12:36





一个建议是首先将它们合并为10个左右,然后合并这些组,依此类推。这样做的好处是,如果单个合并失败,您不会失去所有工作。你现在这样做的方式不仅会导致执行速度呈指数级增长,而且每次失败都会让你不得不从一开始就重新开始。

这种方式也会降低数据帧的平均大小 rbind 调用,因为它们中的大多数将被附加到小数据帧,并且最后只有几个大数据帧。这应该消除指数增长的大部分执行时间。

我想无论你做什么,都会有很多工作要做。


0
2017-07-09 11:47





在假设您可以信任所有输入数据并且每条记录肯定是唯一的情况下要考虑的一些事项:

  • 考虑创建导入的表而不使用索引。随着索引变得越来越大,在导入过程中管理它们的时间越来越长 - 所以听起来这可能正在发生。如果这是您的问题,以后创建索引仍需要很长时间。

  • 或者,根据您正在讨论的数据量,您可能需要考虑一种分区数据的方法(通常通过日期范围完成)。根据您的数据库,您可以拥有单独索引的分区 - 简化索引工作。

  • 如果演示代码无法解析为数据库文件导入实用程序,则使用此类实用程序。

  • 在导入文件之前,可能需要将文件处理为更大的数据集。例如,您可以在加载前将100个文件合并为一个较大的文件并比较时间来试验这一点。

如果您无法使用分区(取决于环境和数据库人员的经验),您可以使用自制的方法将数据分隔到各种表中。例如data201401到data201412。但是,您必须使用自己的实用程序来跨边界查询。

虽然绝对不是一个更好的选择,但它可以在紧要关头做一些事情 - 它可以让你轻松退休/过期老年记录而无需调整相关指数。如果需要,它还允许您通过“分区”加载预处理的传入数据。


0
2017-07-09 12:24