【问题标题】:How to skip invalid rows when reading data frame from file in R?从R中的文件读取数据帧时如何跳过无效行?
【发布时间】:2012-05-15 10:53:01
【问题描述】:

我有一个包含大量数据的大文件,我想将其读入数据框,但发现一些无效行。这些无效行会导致 read.table 中断。我尝试以下方法跳过无效行,但似乎性能很差。

counts<-count.fields(textConnection(lines),sep="\001")
raw_data<-read.table(textConnection(lines[counts == 34]), sep="\001")

有没有更好的方法来实现这一点?谢谢

【问题讨论】:

  • 你的定义有什么不好?
  • 有什么理由不直接使用read.table?它有很多论据来选择和忽略各种“坏”字符。如果这是您遇到的问题,还有一个“填充”不完整行的参数。

标签: r dataframe


【解决方案1】:

使用@PaulHiemstra 的样本数据:

read.table("test.csv", sep = ";", fill=TRUE)

那么您可以按照自己的意愿处理 NA。

【讨论】:

  • 我将您的答案添加为基准测试的附加选项
  • 懒我——这是我在第一条评论中的回答,但你写得更详细
  • @Carl,为您的评论业力 +1。
  • 最好将它与na.string=""结合起来,这样所有空白单元格都被识别为NA。
【解决方案2】:

您可以做的是遍历文件中的行,并且只添加具有正确长度的行。

我定义了以下测试 csv 文件:

1;2;3;4
1;2;3;4
1;2;3
1;2;3;4

使用read.table 失败:

> read.table("test.csv", sep = ";")
Error in scan(file, what, nmax, sep, dec, quote, skip, nlines, na.strings,  :                                                                       
  line 3 did not have 4 elements   

现在是一种迭代方法:

require(plyr)
no_lines = 4
correct_length = 4
file_con = file("test.csv", "r")
result = ldply(1:no_lines, function(line) {
   dum = strsplit(readLines(file_con, n = 1), split = ";")[[1]]
   if(length(dum) == correct_length) {
     return(dum)
   } else {
     cat(sprintf("Skipped line %s\n", line))
     return(NULL)
   }
  })
close(file_con)

> result
  V1 V2 V3 V4
1  1  2  3  4
2  1  2  3  4
3  1  2  3  4

当然,这是一个简单的例子,因为文件非常小。让我们创建一个更具挑战性的示例作为基准。

# First file with invalid rows
norow = 10e5    # number of rows
no_lines = round(runif(norow, min = 3, max = 4))
no_lines[1] = correct_length
file_content = ldply(no_lines, function(line) paste(1:line, collapse = ";"))
writeLines(paste(file_content[[1]], sep = "\n"), "big_test.csv")

# Same length with valid rows
file_content = ldply(rep(4, norow), function(line) paste(1:line, collapse = ";"))
writeLines(paste(file_content[[1]], sep = "\n"), "big_normal.csv")

现在进行基准测试

# Iterative approach
system.time({file_con <- file("big_test.csv", "r")
    result_test <- ldply(1:norow, function(line) {
       dum = strsplit(readLines(file_con, n = 1), split = ";")[[1]]
       if(length(dum) == correct_length) {
         return(dum)
       } else {
         # Commenting this speeds up by 30%
         #cat(sprintf("Skipped line %s\n", line))
         return(NULL)
       }
      })
    close(file_con)})
   user  system elapsed 
 20.559   0.047  20.775

# Normal read.table
system.time(result_normal <- read.table("big_normal.csv", sep = ";"))
   user  system elapsed 
  1.060   0.015   1.079 

# read.table with fill = TRUE
system.time({result_fill <- read.table("big_test.csv", sep = ";", fill=TRUE)
             na_rows <- complete.cases(result_fill)
             result_fill <- result_fill[-na_rows,]})
   user  system elapsed 
  1.161   0.033   1.203 

# Specifying which type the columns are (e.g. character or numeric)
# using the colClasses argument.
system.time({result_fill <- read.table("big_test.csv", sep = ";", fill=TRUE, 
                                       colClasses = rep("numeric", 4))
             na_rows <- complete.cases(result_fill)
             result_fill <- result_fill[-na_rows,]})
   user  system elapsed 
  0.933   0.064   1.001

所以迭代方法要慢一些,但 100 万行的 20 秒可能是可以接受的(尽管这取决于您对可接受的定义)。特别是当您只需要这样做一次,而不是使用save 保存它以供以后检索时。 @Paolo 建议的解决方案几乎与正常调用 read.table 一样快。使用complete.cases 消除包含错误数量的列(因此NA's)的行。指定列的类别进一步提高了性能,我认为当列和行的数量变大时,这种效果会更大。

总之,最好的选择是使用read.tablefill = TRUE,同时指定列的类。使用ldply 的迭代方法只是一个不错的选择,如果您希望在选择如何读取行时有更大的灵活性,例如仅当某个值高于阈值时才读取该行。但也许这可以通过将所有数据读入 R 来更快地完成,而不是创建一个子集。只有当数据大于你的 RAM 时,我才能想象迭代方法有它的优点。

【讨论】:

    猜你喜欢
    • 2018-08-20
    • 2013-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多