【问题标题】:How to read only lines that fulfil a condition from a csv into R?如何仅将满足条件的行从 csv 读取到 R 中?
【发布时间】:2014-06-05 12:12:27
【问题描述】:

我正在尝试将一个大型 csv 文件读入 R。我只想读取和处理一些满足特定条件的行(例如 Variable2 >= 3)。这是一个小得多的数据集。

我想将这些行直接读入数据帧,而不是将整个数据集加载到数据帧中,然后根据条件进行选择,因为整个数据集不容易放入内存。

【问题讨论】:

  • 您的文件有多大?您不能开始阅读 1000 行并在每个循环中将它们子集到您的条件吗?
  • 是的,我试过了。我一次阅读 100 万行。每次迭代大约需要 15 秒,包括将生成的“过滤”数据集添加到现有数据帧。但是考虑到我正在处理的数据集的大小,这个解决方案需要 1 个多小时。正如我在下面所写的,我实际使用的解决方案 (sqldf) 只用了不到一小时。 Dirk 对我的问题的 awk 建议大约需要 2 个小时。我将研究 Python 以加速其中一些任务。如果有人有好的指点,请告诉我。谢谢大家。
  • 谢谢。很高兴知道它与 sqldf 一起工作得更好。绝对值得保留。
  • 这是一个非常常见的问题,但每个用例的“最佳”答案取决于什么样的条件,是一个或多个字段上的简单数字/字符串匹配(使用 grep/ awk),还是需要评估多个字段(例如V2*V3 < mean(V4) & !is.na(V5))?如果一个简单的 grep/awk 完成了 90% 以上的粗略尺寸缩减,那么这是一种不错的方法。

标签: r large-data read.csv


【解决方案1】:

您可以使用函数file(例如file("mydata.csv", open = "r"))以读取模式打开文件。

您可以使用带有选项n = 1l = readLines(fc, n = 1) 的函数readLines 一次读取一行文件。

然后你必须使用诸如strsplit、正则表达式之类的函数来解析你的字符串,或者你可以尝试包stringr(可从CRAN获得)。

如果该行满足导入数据的条件,则导入。

总而言之,我会这样做:

df = data.frame(var1=character(), var2=int(), stringsAsFactors = FALSE)
fc = file("myfile.csv", open = "r")

i = 0
while(length( (l <- readLines(fc, n = 1) ) > 0 )){ # note the parenthesis surrounding l <- readLines..

   ##parse l here: and check whether you need to import the data.

   if (need_to_add_data){
     i=i+1
     df[i,] = #list of data to import
  }

}

【讨论】:

  • 这会起作用,但速度会相对较慢。在读入R 之前编辑源文件几乎总是最快的,例如使用简单的文本编辑器或 sedawk 等工具
  • 他确实做到了,但有时……“让它流血”,第 9 轨。
  • 您可以通过一次读取(例如)10,000 行来加快速度。
  • @hadley 打败了我。你可以分块阅读。您还应该预先分配df,否则会花费很长时间,基本上每次迭代(数百万次)都会重写整个数据。我添加了一个概念验证解决方案。
【解决方案2】:

您可以使用sqldf 包中的read.csv.sql 函数并使用SQL 选择进行过滤。来自read.csv.sql的帮助页面:

library(sqldf)
write.csv(iris, "iris.csv", quote = FALSE, row.names = FALSE)
iris2 <- read.csv.sql("iris.csv", 
    sql = "select * from file where `Sepal.Length` > 5", eol = "\n")

【讨论】:

  • 这如何解决“文件对于当前内存来说太大”的问题?
  • 在后台使用的 sqlite 数据库默认是一个临时文件,因此我想没有内存问题。
  • 这是在 R 中解决我的问题的最快方法。大约花了 1 小时。谢谢!
  • 至少在我在 Linux 上运行的 R (3.4.2) 版本中,如果没有在Sepal.Length 周围添加撇号,上面的示例将无法工作,即我需要使用`Sepal.Length`
【解决方案3】:

您可以分块读取文件,处理每个块,然后仅将子集拼接在一起。

这是一个最小的示例,假设文件有 1001 行(包括标题),并且只有 100 行可以放入内存。数据有 3 列,我们预计最多 150 行满足条件(这是为最终数据预先分配空间所需要的:

# initialize empty data.frame (150 x 3)
max.rows <- 150
final.df <- data.frame(Variable1=rep(NA, max.rows=150), 
                       Variable2=NA,  
                       Variable3=NA)

# read the first chunk outside the loop
temp <- read.csv('big_file.csv', nrows=100, stringsAsFactors=FALSE)
temp <- temp[temp$Variable2 >= 3, ]  ## subset to useful columns
final.df[1:nrow(temp), ] <- temp     ## add to the data
last.row = nrow(temp)                ## keep track of row index, incl. header

for (i in 1:9){    ## nine chunks remaining to be read
  temp <- read.csv('big_file.csv', skip=i*100+1, nrow=100, header=FALSE,
                   stringsAsFactors=FALSE)
  temp <- temp[temp$Variable2 >= 3, ]
  final.df[(last.row+1):(last.row+nrow(temp)), ] <- temp
  last.row <- last.row + nrow(temp)    ## increment the current count
}

final.df <- final.df[1:last.row, ]   ## only keep filled rows
rm(temp)    ## remove last chunk to free memory

编辑:在 cmets 中 @lucacerone 的建议中添加了 stringsAsFactors=FALSE 选项。

【讨论】:

  • 出于好奇:说在导入时我意识到预分配 150 行是不够的,有没有一种有效的方法来扩展最终 data.frame 的行(比如其他 150 行)?
  • 只是一个小问题:当您导入数据时,我会使用选项 stringsAsFactors = FALSE:对于分类变量,您可能不会读取包含第一个块的所有类别,而 R 不会'不要让你添加你没有类别的数据......
  • 很好,谢谢!我将编辑解决方案。据我所知,没有 R 重写整个内容就没有有效的方法来添加行,但如果你确实扩展它(比如再扩展 50 个),它仍然只是一次重写。
  • 我认为 stringsAsFactors = FALSE 也应该添加到 final.df 中。我很惊讶没有基本功能可以让你做类似的事情......
  • 我认为没有必要。它将作为character 值传递,并且不会与其他块冲突,也不会与character 冲突。
【解决方案4】:

到目前为止,最简单的(在我的书中)是使用预处理。

R> DF <- data.frame(n=1:26, l=LETTERS)
R> write.csv(DF, file="/tmp/data.csv", row.names=FALSE)
R> read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($1 > 20) print $0}' /tmp/data.csv"),
+           header=FALSE)
  V1 V2
1 21  U
2 22  V
3 23  W
4 24  X
5 25  Y
6 26  Z
R> 

这里我们使用awk。我们告诉awk 使用逗号作为字段分隔符,然后使用条件“如果第一个字段大于20”来决定是否打印(整行通过$0)。

R 可以通过pipe() 读取该命令的输出。

这将比将所有内容读入 R 更快、更节省内存。

【讨论】:

  • 这也适用于百万行 DF 吗?
  • 是的,当然。您只读取满足条件的百分比。 awk 一次处理这一行。
  • 这很有趣,但我无法通过您的简单示例从 RStudio 中得到它。带有 awk 过滤器的东西,但不确定(输入中没有可用的行)...
  • 你需要在你的路径中awk,我在这里假设了一个带有/tmp的Unix布局。您需要根据需要进行调整。
  • 当然,这也有效。其实在我的Linux系统上awk其实就是gawk
【解决方案5】:

当我看到这个问题并认为我会做一些基准测试时,我正在研究readr::read_csv_chunked。对于这个例子,read_csv_chunked 做得很好,增加块大小是有益的。 sqldf 只比 awk 快一点。

library(tidyverse)
library(sqldf)
library(data.table)
library(microbenchmark)

# Generate an example dataset with two numeric columns and 5 million rows
tibble(
  norm = rnorm(5e6, mean = 5000, sd = 1000),
  unif = runif(5e6, min = 0, max = 10000)
) %>%
  write_csv('medium.csv')

microbenchmark(
  readr  = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F),
  readr2 = read_csv_chunked('medium.csv', callback = DataFrameCallback$new(function(x, pos) subset(x, unif > 9000)), col_types = 'dd', progress = F, chunk_size = 1000000),
  sqldf  = read.csv.sql('medium.csv', sql = 'select * from file where unif > 9000', eol = '\n'),
  awk    = read.csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv")),
  awk2   = read_csv(pipe("awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv"), col_types = 'dd', progress = F),
  fread  = fread(cmd = "awk 'BEGIN {FS=\",\"} {if ($2 > 9000) print $0}' medium.csv"),
  check  = function(values) all(sapply(values[-1], function(x) all.equal(values[[1]], x))),
  times  = 10L
)

# Updated 2020-05-29

# Unit: seconds
#   expr   min    lq  mean  median    uq   max neval
#  readr   2.6   2.7   3.1     3.1   3.5   4.0    10
# readr2   2.3   2.3   2.4     2.4   2.6   2.7    10
#  sqldf  14.1  14.1  14.7    14.3  15.2  16.0    10
#    awk  18.2  18.3  18.7    18.5  19.3  19.6    10
#   awk2  18.1  18.2  18.6    18.4  19.1  19.4    10
#  fread  17.9  18.0  18.2    18.1  18.2  18.8    10

# R version 3.6.2 (2019-12-12)
# macOS Mojave 10.14.6        

# data.table 1.12.8
# readr      1.3.1 
# sqldf      0.4-11

【讨论】:

  • 您介意添加data.table::fread(cmd="awk ...") 进行基准测试吗?
  • @zx8754 当然!不过,对于这个例子,awk 完成了繁重的工作。 data.table 中是否有分块读取功能?
  • 我认为来自bioinf.shenwei.me/csvtk 的另一个名为csvtk 的外部工具也可以与fread 一起用于测试。
猜你喜欢
  • 2021-10-07
  • 1970-01-01
  • 2015-10-13
  • 2020-08-29
  • 1970-01-01
  • 1970-01-01
  • 2011-10-02
  • 1970-01-01
相关资源
最近更新 更多