问题 将多个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
起源
答案:
假设您的文件是传统的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
正如@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
您正在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
建立在 尼克肯尼迪的回答 运用 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