【问题标题】:How can I read lines from a csv file only if a variable equals to a certain value (20Gb+ csv file)仅当变量等于某个值(20Gb+ csv 文件)时,如何从 csv 文件中读取行
【发布时间】:2020-09-26 03:17:14
【问题描述】:

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

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

我更喜欢使用 Stata。 R和python也不错。

【问题讨论】:

  • 这两个建议(到目前为止)都很强:对于蛮力阅读性能,与data.table::fread 相比几乎没有。对于仔细挑选特定数据,我认为vroom::vroom 总体上提供了很大的潜力,我很高兴看到它的去向。 然而,当数据开始变得这么大(不是“大数据”,请注意,虽然它在我的经验中遇到了类似的问题),我建议你真的应该考虑正式化您的数据存储/访问,例如 SQL 或可以很好地处理部分拉取、索引等的东西。
  • 它不必是 big DBMS 或 big SQL 甚至任何分布式数据系统(例如 HADOOP)受益于其中的索引和效率。正如another answer 所言,SQLite 在这里可能有一些足够强大的功能(因此您不必“投资”大型 DBMS)。

标签: python r csv stata


【解决方案1】:

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, \'$2 == "a"\'', tmp))
})
#    user  system elapsed 
# 350.170   3.775 354.638 

后者要慢得多,因为前者是并行运行的。优点是第一种方法内存效率不高——您首先占用整个文件的所有内存,然后过滤到较小的表。 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 

请注意vroom 的“标价”——如前所述,它只索引您的数据,因此比较仅读取您的数据的时间可能会产生误导——您必须计时实际处理数据需要很长时间,因为这会触发数据加载。这是一个比较:

# 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 

(跳过第三步的比较大致相同)

【讨论】:

  • 感谢您将vroom 添加到基准测试中-我所看到的文档对阅读量和时间的具体细节很清楚,这证实了我的怀疑。数据越宽(并且您使用的列的百分比越低)可能越有益。
  • @GregorThomas 是的——我专门使用所有列进行了基准测试,因为我认为这是最公平的。当然 fread 有 select= 如果您不打算使用某些列,并且 IINM 在读取过程中会跳过这些列(即,不会像行一样先读取然后删除)
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    我和 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, '$2 ~ /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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-09
      • 2019-04-21
      • 2016-02-19
      • 1970-01-01
      • 1970-01-01
      • 2019-05-27
      • 2019-01-30
      相关资源
      最近更新 更多