仅当变量等于某个值(20Gb+ csv 文件)时,如何从 csv 文件中读取行

How can I read lines from a csv file only if a variable equals to a certain value (20Gb+ csv file)

我有一个大约 25Gb 的 csv 文件。我有 64GB 的内存。虽然我的 ram 可以处理这个大文件,但它花费的时间太长了。此外,我不需要数据中的每一行。所以我想

  1. 如果可以只读取第二列等于某个值的行
  2. 另外,不知是否真的会节省时间。因为我估计软件还是会读入整个文件,然后去掉不满足条件的行。

我比较喜欢用Stata。 R 和 python 也不错。

读取所有行和实际将整个内容加载到位于内存中的数据结构之间是有区别的。

在 R 中,vroom 包将对列进行索引,然后如果您过滤第二列中的值,它将仅读取第二列以找出何时满足条件,然后仅从其他列中读取相关值。 Read about it here.

一般来说,SED 或 AWK 等命令行工具非常适合预处理数据。它们通过一次一行地流式传输文件来工作,因此整个文件永远不会在内存中。您可以使用它来创建一个仅包含您感兴趣的行的较小文件,然后使用您选择的程序正常使用它。

R 的 data.table::fread 非常适合这个。让我们写一个示例文件:

library(data.table)
set.seed(39439)

NN = 3e8
DT = data.table(
  ID1 = sample(LETTERS, NN, TRUE), 
  ID2 = sample(letters, NN, TRUE),
  V1 = rnorm(NN)
)
DT
#            ID1 ID2         V1
#         1:   O   h  0.1580064
#         2:   K   l -2.4281532
#         3:   F   z  1.7353759
#         4:   B   f -1.0911407
#         5:   M   w  0.7187998
#        ---                   
# 299999996:   D   u -0.8221716
# 299999997:   F   f -2.4881300
# 299999998:   W   t  0.0371132
# 299999999:   I   h -1.2020380
# 300000000:   L   s -2.2284455

# smaller than your data, but still large
format(object.size(DT), 'Gb')
# [1] "6.7 Gb"

# write to test file
fwrite(DT, tmp <- tempfile())

# size on disk about the same
file.info(tmp)$size/1024^3
# [1] 6.191435

两种选择:(1)在R中读取然后过滤:

rm(DT)
system.time({
  DT = fread(tmp)
  DT = DT[ID2 == 'a']
})
#    user  system elapsed 
#  50.390  25.662  40.004 

大约 40 秒

(2)用awk过滤,然后读取:

rm(DT)
system.time({
  DT = fread(cmd = paste('awk -F, \' == "a"\'', tmp))
})
#    user  system elapsed 
# 350.170   3.775 354.638 

后者要慢得多,因为前者是并行运行的。优点是第一种方法的内存效率不高——您首先占用整个文件的所有内存,然后再过滤到较小的 table。 awk 方法只会将过滤后的文件加载到内存中。

(2*) 在这种情况下,您实际上也可以使用 grep,但请注意,这仅适用,因为此文件中只有一列可以包含 a

rm(DT)
system.time({
  DT = fread(cmd = paste('grep -F ",a,"', tmp))
})
#    user  system elapsed 
# 164.587   2.500 167.165 

PS 请注意 vroom 的 "sticker price" -- 如前所述,它只会索引您的数据,因此比较 的时间只是阅读 您的数据可能会产生误导——您必须计算实际对您的数据执行操作需要多长时间,因为这会触发数据加载。这是一个比较:

# to offset some re-reading optimizations in fread
file.copy(tmp, tmp <- tempfile())

rm(DT)
system.time({
  DT = fread(tmp)
  DT = DT[ID2 == 'a']
  DT[ , .(mean(V1)), by = .(ID1, ID2)]
})
 #   user  system elapsed 
 # 61.930  31.740  52.958

library(dplyr)
rm(DT)
system.time({
  DT = vroom::vroom(tmp)
  DT = DT %>% filter(ID2 == 'a')
  DT %>% group_by(ID1, ID2) %>% summarize(mean(V1))
})
#    user  system elapsed 
# 122.605  56.562 129.957 

(跳过第三步大致相同)

我和 Gregor Thomas 一样倾向于达到 awk,但它实际上似乎比 Stata 的 import delimited 慢。这是显示此内容的模拟:

#delimit;
version 16.1;
set more off;
clear all;
timer clear;

/* Fake CSV Data */
set seed 1234;

set obs 1000000;
gen id = _n;
gen keeper  = mod(id,10);

forvalues i=1/2000 {;
    gen x`i' = rnormal();
};

export delimited using "big_file.csv", replace;
!ls -lh "big_file.csv";

/* (1) import delimited */
timer on 1;
import delimited "big_file.csv", clear;
keep if keeper == 5;
timer off 1;

/* (2) awk + import Delimited */
timer on 2;
/* Grab all the data for obs where the second column equal to 5 */
!awk -F, ' ~ /5/' big_file.csv > smaller_file.csv;
import delimited "smaller_file.csv", clear;
timer off 2;

timer list;

!rm "big_file.csv" "smaller_file.csv";

这产生了一个 20G 的 csv 文件,但是 import 需要 622.3250 秒,awk + ​​import 需要 1193.1510。