【问题标题】:In R how do I read a CSV file line by line and have the contents recognised as the correct data type?在 R 中,如何逐行读取 CSV 文件并将内容识别为正确的数据类型?
【发布时间】:2011-09-01 10:58:30
【问题描述】:

我想读入一个 CSV 文件,它的第一行是变量名,后续行是这些变量的内容。有些变量是数字,有些是文本,有些甚至是空的。

file = "path/file.csv"
f = file(file,'r')
varnames = strsplit(readLines(f,1),",")[[1]]
data = strsplit(readLines(f,1),",")[[1]]

既然数据包含了所有变量,我该如何制作它,以便数据可以识别正在读取的数据类型,就像我做read.csv一样。

我需要逐行(或一次n行)读取数据,因为整个数据集太大而无法读入R。

【问题讨论】:

  • 我不清楚为什么 read.csv('file.csv') 不适合你?
  • 假设我正在阅读一个 100G 的文件。我想一次读取 500MB,做一些处理,然后读取下一个 500MB。我只是无法容纳 100G 的内存。
  • read.* 系列函数允许您只读取这么多行并跳过块,因此您应该使用这些参数而不是尝试解析行。
  • 我同意下面@xiaodai 的评论,这里真正需要的还没有解决,因为建议的功能非常慢:在一个有 N 行的文件中一次读取一行将花费 $O(N^2)$ 时间。如果愿意编写一些低级代码,并且如果 R 有随机访问文件,那么只要不丢失调用之间的随机访问位置,就应该可以将其降低到 $O(N)$。

标签: r csv


【解决方案1】:

我认为使用disk.framecsv_to_disk.frame 和设置in_chunk_size 对于这个用例来说会很棒。例如

library(disk.frame)
csv_to_disk.frame("/path/to/file.csv", in_chunk_size = 1e7)

【讨论】:

    【解决方案2】:

    如果您不介意稍微修改一下以写出您的数据,您可以使用chunkeddisk.frame

    两者都有让您逐块读取数据的选项

    【讨论】:

      【解决方案3】:

      我会试试LaF package:

      快速访问大型 ASCII 文件的方法...假设 文件太大而无法放入内存...提供了方法 以块方式访问和处理文件。此外,打开的文件可以 像普通的data.frame一样被访问...

      我能够让它看起来与下面的示例代码一起工作,并且它似乎具有您期望从流式实现中获得的性能。但是,我建议您也运行自己的时间测试。

      library('LaF')
      
      model <- detect_dm_csv('data.csv', header = TRUE, nrows = 600)  # read only 600 rows for type detection
      
      mylaf <- laf_open(model)
      
      print(mylaf[1000])  # print 1000th row
      

      【讨论】:

        【解决方案4】:

        根据 DWin 的评论,您可以尝试以下操作:

        read.clump <- function(file, lines, clump){
            if(clump > 1){
                header <- read.csv(file, nrows=1, header=FALSE)
                p = read.csv(file, skip = lines*(clump-1), 
               #p = read.csv(file, skip = (lines*(clump-1))+1 if not a textConnection           
                    nrows = lines, header=FALSE)
        
                names(p) = header
            } else {
                p = read.csv(file, skip = lines*(clump-1), nrows = lines)
            }
            return(p)
        }
        

        您也应该在函数中添加一些错误处理/检查。

        然后用

        x = "letter1, letter2
        a, b
        c, d
        e, f
        g, h
        i, j
        k, l"
        
        
        >read.clump(textConnection(x), lines = 2, clump = 1)
          letter1 letter2
        1       a       b
        2       c       d
        
        > read.clump(textConnection(x), lines = 2, clump = 2)
          letter1  letter2
        1       e        f
        2       g        h
        
        > read.clump(textConnection(x), lines = 3, clump = 1)
          letter1 letter2
        1       a       b
        2       c       d
        3       e       f
        
        
        > read.clump(textConnection(x), lines = 3, clump = 2)
          letter1  letter2
        1       g        h
        2       i        j
        3       k        l
        

        现在你只需要*应用在团块上

        【讨论】:

        • 我认为那里有一个小错误:如果你没有传递一个 textConnection(或另一个连接)而是一个文件名,你需要在第一个 p = read.csv 中再跳过一行,因为那时读取标题行后重新打开文件。
        • 我认为这个解决方案很好。但是您是否考虑过使用 skip = 的含义?要跳到第 1 百万和第 1 条记录,比方说,在后台 R 正在读取第 1 百万条记录!所以本质上它真的很慢,即使它没有太多的内存。有没有办法读取 100 万条记录,比如说,然后停止,对读取的数据执行一些操作,从内存中丢弃数据然后读取接下来的 100 万条记录,而不必先读取前一百万条(或更多)!
        【解决方案5】:

        附带说明:如果您确实拥有如此庞大的数据,那么(在 SQLite 解决方案旁边)有不同的软件包可以帮助您处理这些问题,而无需求助于这些答案中描述的技巧。

        ffbigmemory 与朋友biganalyticsbigtabulate 'biglm' 等的包。有关概述,请参见例如。

        【讨论】:

          【解决方案6】:

          只是为了好玩(我在这里等待长时间运行的计算 :-)),该版本允许您使用任何 read.* 类型的函数,并且可以解决 \ 中的一个小错误格雷格的代码:

          read.clump <- function(file, lines, clump, readFunc=read.csv,
              skip=(lines*(clump-1))+ifelse((header) & (clump>1) & (!inherits(file, "connection")),1,0),
              nrows=lines,header=TRUE,...){
              if(clump > 1){
                      colnms<-NULL
                      if(header)
                      {
                          colnms<-unlist(readFunc(file, nrows=1, header=FALSE))
                          print(colnms)
                      }
                p = readFunc(file, skip = skip,
                    nrows = nrows, header=FALSE,...)
                      if(! is.null(colnms))
                      {
                  colnames(p) = colnms
                      }
              } else {
                  p = readFunc(file, skip = skip, nrows = nrows, header=header)
              }
              return(p)
          }
          

          现在您可以将相关函数作为参数 readFunc 传递,也可以传递额外的参数。元编程很有趣。

          【讨论】:

            【解决方案7】:

            之前已在此处讨论过处理非常大(例如 > 1e7ish 单元格)CSV 文件的替代策略是:

            1. 将 CSV 文件读入 SQLite 数据库。
            2. 使用read.csv.sqlsqldf 包中导入数据库中的数据。

            这样做的主要优点是它通常更快,并且您可以轻松过滤内容以仅包含您需要的列或行。

            请参阅how to import CSV into sqlite using RSqlite? 了解更多信息。

            【讨论】:

              猜你喜欢
              • 2022-11-13
              • 1970-01-01
              • 2022-09-22
              • 2014-11-11
              • 1970-01-01
              • 1970-01-01
              • 2021-05-13
              • 2013-08-31
              • 1970-01-01
              相关资源
              最近更新 更多