在 Windows 中使用 R data.table fread 按第 N 列过滤行

问题描述 投票:0回答:2

我有一个大型数据集,我想通过仅使用

cmd
参数过滤某些行来对其进行预处理。不幸的是,原始文件的过滤信息位于第三列,而不是第一列。

我可以使用它,但是太慢了:

fread(cmd='findstr /r "^.*,.*,4[0-9]," data.csv')

如何有效地做到这一点,最好是让 findstr 或等效函数仅从第三列开始读取?

这是完整的 MWE。

library(data.table)
library(reprex)
library(rstudioapi)
library(styler)
#> Warning: package 'styler' was built under R version 4.2.3

data <- data.frame(
  "A" = c(11, 33, 34, 11),
  "B" = c("aaa","bbb","ccc","ddd"),
  "C" = c(13,45,46,57),
  "D" = c(1,2,3,4)
)

# Writing data to a CSV file
write.csv(data, "data.csv",row.names=FALSE)

#whole file
fread("data.csv")
#>     A   B  C D
#> 1: 11 aaa 13 1
#> 2: 33 bbb 45 2
#> 3: 34 ccc 46 3
#> 4: 11 ddd 57 4

#filter based on first column(ok)
fread(cmd='findstr /r "^3[0-9]" data.csv')
#>    V1  V2 V3 V4
#> 1: 33 bbb 45  2
#> 2: 34 ccc 46  3

#filter based on third column but reads the first two columns as well (works as intended, but too slow)
fread(cmd='findstr /r "^.*,.*,4[0-9]," data.csv')
#>    V1  V2 V3 V4
#> 1: 33 bbb 45  2
#> 2: 34 ccc 46  3

#filter based on third column (does not work)
#fread(cmd='for /f "tokens=3 delims=," %i in (findstr /r "4[0-9]" data.csv) do echo %i')

# in cmd, this would print the numbers 45,46 as seperate outputs - and not return the filtered csv
#for /f "tokens=3 delims=," %i in ('findstr /r "4[0-9]" data.csv') do echo %i

创建于 2024-02-28,使用 reprex v2.0.2

r windows csv cmd data.table
2个回答
2
投票

替代建议:使用

mlr
,命令行 CSV 实用程序。

演示:我有一个 2.2Mi 行 CSV,有 57 列(7 个字符串,其余数字)。我想过滤名为

number
的一列,保留等于 22 的值。

library(data.table)
ps::ps_memory_info()["rss"] / 1024^2
#      rss 
# 75.64062 
Sys.which("mlr")
#            mlr 
# "/usr/bin/mlr" 
system.time(obj <- fread(cmd = "mlr --csv  filter '$number == 22' largedata.csv"))
#    user  system elapsed 
#  21.526   1.317  13.809 
dim(obj)
# [1] 64537    57
object.size(obj) # megabytes
# 29.3095 bytes
ps::ps_memory_info()["rss"] / 1024^2
#      rss 
# 114.8398 

如果我使用比较做同样的事情

read.csv("largedata.csv")
fread("largedata.csv")
arrow::read_csv_arrow("largedata.csv", as_data_frame=TRUE) # all data
arrow::read_csv_arrow("largedata.csv", as_data_frame=FALSE) |>
  filter(number == 22) |>
  collect()

使用

mlr
,统计数据清晰可见。

| Method                | Time   | Size      | RSS       |
|:----------------------|:-------|:----------|:----------|
| `read.csv`            | 73.767 | 1045.60MB | 2850.20MB |
| `fread`               |  3.085 | 1039.80MB | 1339.57MB |
| `arrow` true (=22)    |  3.559 |   29.32MB |  427.64MB |
| `arrow` false (=22)   | <2     |   29.31MB | 1460.91MB |
| `sqldf`               | 27.72  |   52.80MB |   91.75MB |
| `fread(cmd='mlr...')` | 13.8   |   29.31MB |  114.84MB |

他们有二进制文件,甚至适用于 Windows。

(我在 RuiBarradas 的答案中加入了

sqldf
建议,适应了我的更大数据。)


以下是使用

arrow
包的原始答案。

我不知道你会得到一个万无一失的方法,这样只使用

fread
(尽管我很高兴在这里错了。但是,另一种选择是以“懒惰”的方式使用
arrow::read_csv_arrow
)使用
dplyr
动词对数据进行子集化。在内存中后,您可以根据需要使用
as.data.table

如果您提前知道相关列名称,则相当直接:

arr <- arrow::read_csv_arrow("data.csv", as_data_frame = FALSE)
arr |>
  filter(between(C, 40, 49)) |>
  collect()
# # A tibble: 2 × 4
#       A B         C     D
#   <int> <chr> <int> <int>
# 1    33 bbb      45     2
# 2    34 ccc      46     3

如果你知道这是第三个字段,但仅此而已,我们可以在这里使用一些“使用 dplyr 进行编程”:

fld3 <- as.symbol(names(arr)[3])
arr |>
  filter(between(!!fld3, 40, 49)) |>
  collect()
# # A tibble: 2 × 4
#       A B         C     D
#   <int> <chr> <int> <int>
# 1    33 bbb      45     2
# 2    34 ccc      46     3

并不是所有的R表达式都能执行,但是很多简单的函数和逻辑过滤都可以快速完成。

请注意,虽然

arr
对象

arr
# Table
# 4 rows x 4 columns
# $A <int64>
# $B <string>
# $C <int64>
# $D <int64>

不建议任何数据已加载到内存中,只是有关文件的一些“元”。例如,您可以看到它“知道”哪些是字符串,哪些是整数,等等。它保持“惰性”,直到您最终

collect()
它。

查看 https://arrow.apache.org/docs/r/articles/dataset.html 了解有关功能的一些演练,以及 https://arrow.apache.org/docs/dev/r /reference/acero.html 获取受支持的 R 函数的详细列表。


1
投票

使用包

sqldf
,您可以使用 SQL
SELECT
语句进行过滤。

library(sqldf)
#> Loading required package: gsubfn
#> Loading required package: proto
#> Loading required package: RSQLite

data <- data.frame(
  "A" = c(11, 33, 34, 11),
  "B" = c("aaa","bbb","ccc","ddd"),
  "C" = c(13,45,46,57),
  "D" = c(1,2,3,4)
)

# Writing data to a CSV file
write.csv(data, "data.csv",row.names=FALSE)
rm(data)

sql <- "select * from file where C between 40 and 49"
read.csv.sql("data.csv", sql)
#>    A     B  C D
#> 1 33 "bbb" 45 2
#> 2 34 "ccc" 46 3

# or simply
sqldf::read.csv.sql("data.csv", "select * from file where C between 40 and 49")
#>    A     B  C D
#> 1 33 "bbb" 45 2
#> 2 34 "ccc" 46 3

创建于 2024-02-28,使用 reprex v2.0.2

© www.soinside.com 2019 - 2024. All rights reserved.