簡體   English   中英

僅當變量等於某個值(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 的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 

后者要慢得多,因為前者是並行運行的。 優點是第一種方法不節省內存——在過濾到較小的表之前,您首先占用了整個文件的所有 memory。 awk方法僅將過濾后的文件加載到 memory 中。

(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的“標價”——如前所述,它只索引你的數據,所以比較讀取數據的時間可能會產生誤導——你必須計算實際處理數據需要多長時間,因為觸發數據加載。 這是一個比較:

# 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 

(大致相同的比較適用於跳過第三步)

讀取所有行和將整個內容實際加載到位於 memory 中的數據結構中是有區別的。

在 R 中, vroom package 將對列進行索引,然后如果您過濾第二列中的值,它將僅讀取第二列以找出何時滿足條件,然后僅從其他列中讀取相關值。 在這里閱讀

一般來說,像 SED 或 AWK 這樣的命令行工具將非常擅長預處理您的數據。 它們通過一次一行地流式傳輸文件來工作,因此整個文件永遠不會在 memory 中。 您可以使用它來創建一個僅包含您感興趣的行的較小文件,然后使用您選擇的程序正常使用它。

我和 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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM