【问题标题】:Fastest way to import millions of files in R?在 R 中导入数百万个文件的最快方法?
【发布时间】:2023-03-09 15:04:01
【问题描述】:

我有 1500 万个 CSV 文件,每个文件有两列(整数和浮点数),行数介于 5 到 500 行之间。每个文件看起来像:

3453,0.034
31,0.031
567,0.456
...

目前,我正在遍历所有文件,并使用read.csv() 将每个文件导入到一个大列表中。这是一个简化的版本:

allFileNames = Sys.glob(sprintf("%s/*/*/results/*/*", dir))

s$scores = list()

for (i in 1:length(allFileNames)){
        if ((i %% 1000) == 0){
            cat(sprintf("%d of %d\n", i, length(allFileNames)))
        }

        fileName = allFileNames[i]
        approachID = getApproachID(fileName) 
        bugID = getBugID(fileName)

        size = file.info(fileName)$size
        if (!is.na(size) && size > 0){ # make sure file exists and is not empty
            tmp = read.csv(fileName, header=F, colClasses=c("integer", "numeric"))
            colnames(tmp) = c("fileCode", "score")
            s$scores[[approachID]][[bugID]]  = tmp
        } else {
            # File does not exist, or is empty. 
            s$scores[[approachID]][[bugID]] = matrix(-1, ncol=2, nrow=1)
        }
    }

tmp = read.csv(fileName, header=F, colClasses=c("integer", "numeric")

稍后在我的代码中,我会返回列表中的每个矩阵,并计算一些指标。

开始此导入过程后,似乎需要大约 3 到 5 天才能完成。有没有更快的方法来做到这一点?

编辑:我添加了有关我的代码的更多详细信息。

【问题讨论】:

  • 你想做什么?用所有数据建立一个矩阵,还是分别读入和处理每个矩阵?
  • 这与一次加载多个文件有关:stackoverflow.com/questions/3764292
  • @joran 我不知道这是否是一个重复的问题,确切地说,尽管那里的许多答案在这里肯定会有用。
  • @joran:不完全是骗子,因为这涉及很多文件,但 OP 应该明确检查链接。

标签: r csv import


【解决方案1】:

我不清楚您的目标,但如果您尝试将所有这些文件读入单个 R 数据结构,那么我会发现两个主要的性能问题:

  1. 文件访问时间 - 从您请求 read.csv 的那一刻起,无数复杂的过程就会在您的计算机上启动,包括查看该文件是否存在、查找该文件在内存或磁盘中的位置(以及将数据读入内存) ,如果需要的话),然后在 R 中解释数据。我预计当您读取数百万个文件时,这将是一个几乎恒定的减速。
  2. 随着每个新文件的读取增加您的单一数据结构。每次您想向矩阵添加几行时,您可能需要重新分配类似大小的内存块以存储更大的矩阵。如果您将阵列增长 1500 万次,您肯定会注意到这里的性能下降。有了这个问题,随着您读取更多文件,性能会逐渐变差。

所以做一些快速分析,看看读取需要多长时间。如果您在阅读更多文件时它们逐渐变慢,那么让我们关注问题#2。如果它一直很慢,那么让我们担心问题 #1。

关于解决方案,我想说你可以从两件事开始:

  1. 用另一种编程语言组合 CSV 文件。如果您只是循环文件并将它们连接成一个大文件,那么一个简单的 shell 脚本可能会为您完成这项工作。正如下面 Joshua 和 Richie 所提到的,您可以通过使用更高效的 scan()readlines() 函数来优化这一点,而不必改用另一种语言。
  2. 预先调整统一数据结构的大小。例如,如果您使用矩阵,请将行数设置为 ~ 1500 万 x 100。这将确保您只需在内存中为该对象找到一次空间,其余操作将只插入数据到预先确定大小的矩阵中。

添加您的代码的更多详细信息(您正在使用的列表是什么样的?),我们可能会提供更多帮助。

【讨论】:

  • 还要注意scanread.csv 更合适,因为数据都可以存储为数字。
  • 如果 OP 只想将文件合并为一个,甚至可能是 readLines + writeLines
  • @Jeff - 感谢您提供详细的答案。我不认为我可以将所有文件合并为一个大文件,因为我需要将它们分开以进行后续分析。 (每个文件代表我的实验的执行。)至于预分配我的数据结构,有没有办法预分配我的列表的大小(s$scores)?顺便说一句-我没有看到速度越来越慢,所以我认为性能是由磁盘 IO 主导的。
  • @JoshuaUlrich:我尝试使用scan 而不是read.csv,它的运行速度似乎是原来的两倍。对于那些感兴趣的人,我使用了命令tmp <- matrix(scan(fileName, what=0, sep=",", quiet=T), ncol=2, byrow=TRUE),类似于您在下面的评论。
【解决方案2】:

正如 Jeff 提到的,这里有几件事可能需要很长时间。当 RAM 中有 1500 万个数据帧时,问题可能是文件访问、读取文件或内存不足。使问题更加复杂的是,瓶颈可能会根据您机器的规格而有所不同(例如,较慢的硬盘驱动器会减慢文件的读取速度,缺少 RAM 会导致文件数量多的问题)。要找出问题所在,您必须进行一些分析。

尝试一开始只读取 10000 个左右的文件,然后调用 system.time,或者更复杂的是,使用 rbenchmark 来查看最耗时的内容。

然后看joran的链接

Quickly reading very large tables as dataframes in R

看看那里的任何技术是否对您有帮助。

【讨论】:

    【解决方案3】:

    使用 scan(正如 Joshua 在评论中所说)可能会更快(3-4 次):

    scan(fileName, what=list(0L,0.0), sep=",", dec=".", quiet=TRUE)
    

    主要区别在于scan 返回包含两个元素的列表,read.csv 返回data.frame

    【讨论】:

    • 我在想这样的事情:List[[1]] <- matrix(scan(fileName, what=0, sep=","), ncol=2, byrow=TRUE).
    • 谢谢大家。正如我在另一条评论中提到的,通过切换到tmp <- matrix(scan(fileName, what=0, sep=",", quiet=T), ncol=2, byrow=TRUE),我的代码运行速度大约是原来的两倍。在做了system.time() 之后,似乎所需的大部分时间都在 IO 中,所以我想我只需要稍等一下。
    【解决方案4】:

    这个一般的工作流程怎么样?不过没有测试。

    my.list.of.files <- list.files(pattern = ".txt") # char vector of filenames
    my.data <- sapply(my.list.of.files, FUN = function(x) {
                # read file using scan, craft the output to two columns
             }) # result is merged
    
    #or if you use simplify= FALSE
    my.data <- sapply(my.list.of.files, FUN = function(x) {
                # read file using scan (or some other method), craft the output to two columns
             }, simplify = FALSE) #you get a list
    my.data <- do.call("rbind", my.data)
    

    【讨论】:

    • 我知道这只是一个模板,但请注意,如果您将所有 1500 万个文件都放在一个目录中,那也可能是导致性能问题的另一个原因。
    • 请注意,sapply 有点慢,因为 R 必须做额外的工作来弄清楚如何简化输出。在*apply 系列中,lapply 是最快的,因此在这里可能更合适。
    • @Jeff,这不是我们可以解释的。 @flodel,即使您指定 simplify = FALSE?
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2019-03-25
    • 1970-01-01
    • 2020-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多