使用包 data.table 中的 fread 一次读取块

Posted

技术标签:

【中文标题】使用包 data.table 中的 fread 一次读取块【英文标题】:Reading in chunks at a time using fread in package data.table 【发布时间】:2013-11-22 12:53:17 【问题描述】:

我正在尝试使用包data.table 中的fread 函数输入一个大的制表符分隔文件(大约2GB)。然而,因为它太大了,它并不完全适合内存。我尝试使用skipnrow 参数分块输入,例如:

chunk.size = 1e6
done = FALSE
chunk = 1
while(!done)

    temp = fread("myfile.txt",skip=(chunk-1)*chunk.size,nrow=chunk.size-1)
    #do something to temp
    chunk = chunk + 1
    if(nrow(temp)<2) done = TRUE

在上面的例子中,我一次读取 100 万行,对它们执行计算,然后得到下一个百万,等等。这段代码的问题是,在检索到每个块之后,@987654327 @ 需要从一开始就开始扫描文件,因为在每次循环迭代之后,skip 增加一百万。结果,在每个块之后,fread 需要越来越长的时间才能真正到达下一个块,这使得效率非常低。

有没有办法告诉fread 每说 100 万行就暂停一次,然后从那一刻开始继续阅读,而不必从头开始重新开始?任何解决方案,或者这应该是一个新的功能请求?

【问题讨论】:

有一个类似的 FR here。我也会链接到这篇文章。 感谢您指出并链接!看起来像是最优先的 FR。 我想做同样的事情,我认为它需要一个新的请求。 今天遇到了同样的问题 @Arun 新的github页面有FR吗?好像没找到 【参考方案1】:

您应该使用LaF 包。这在您的数据上引入了一种指针,从而避免了 - 对于非常大的数据 - 读取整个文件的烦人行为。据我所知fread() in data.table pckg 需要知道总行数,这需要 GB 数据的时间。 使用LaF 中的指针,您可以转到所需的每一行;并读取可以应用函数的数据块,然后继续处理下一个数据块。在我的小型 PC 上,我以 10e6 行的步长运行了一个 25 GB 的 csv 文件,并提取了所需的大约 5e6 个观察值——每个 10e6 块需要 30 秒。

更新:

library('LaF')
huge_file <- 'C:/datasets/protein.links.v9.1.txt'

#First detect a data model for your file:
model <- detect_dm_csv(huge_file, sep=" ", header=TRUE)

然后使用模型创建到您的文件的连接:

df.laf <- laf_open(model)

一旦完成,您就可以做各种事情,而无需像 data.table pckgs 中那样知道文件的大小。例如,将指针放在第 100e6 行并从此处读取 1e6 行数据:

goto(df.laf, 100e6)
data <- next_block(df.laf,nrows=1e6)

现在data 包含您的 CSV 文件的 1e6 行(从第 100e6 行开始)。

您可以读取数据块(大小取决于您的内存)并且只保留您需要的数据。例如我的示例中的huge_file 指向一个包含所有已知蛋白质序列的文件,其大小>27 GB——对我的电脑来说太大了。为了只获得人类序列,我使用有机体 id 进行过滤,人类的 9606 应该出现在变量 protein1 的开头。一种肮脏的方法是将其放入一个简单的 for 循环中,一次只读取一个数据块:

library('dplyr')
library('stringr')

res <- df.laf[1,][0,]
for(i in 1:10)
  raw <-
    next_block(df.laf,nrows=100e6) %>% 
    filter(str_detect(protein1,"^9606\\."))
  res <- rbind(res, raw)

    

现在res 包含过滤后的人类数据。但更好 - 对于更复杂的操作,例如即时计算数据 - 函数process_blocks() 将函数作为参数。因此,在函数中,您可以对每条数据执行任何您想要的操作。阅读文档。

【讨论】:

是的,请查看更新。 谢谢。我有一个 872493862 行的 61GB 文件,它运行得相当快。我使用“nrows”和“skip”尝试了与 fread() 相同的循环方法,但它在每个循环中变得越来越慢,因为它必须跳过更多行。【参考方案2】:

您可以使用阅读器的read_*_chunked 来读取数据,例如逐块过滤。示例见here 和here:

# Cars with 3 gears
f <- function(x, pos) subset(x, gear == 3)
read_csv_chunked(readr_example("mtcars.csv"), DataFrameCallback$new(f), chunk_size = 5)

【讨论】:

我试过这种方法,但对于我的 61GB 文件来说太慢了。【参考方案3】:

一个相关的选项是chunked 包。下面是一个 3.5 GB 文本文件的示例:

library(chunked)
library(tidyverse)

# I want to look at the daily page views of Wikipedia articles
# before 2015... I can get zipped log files
# from here: hhttps://dumps.wikimedia.org/other/pagecounts-ez/merged/2012/2012-12/
# I get bz file, unzip to get this: 

my_file <- 'pagecounts-2012-12-14/pagecounts-2012-12-14'

# How big is my file?
print(paste(round(file.info(my_file)$size  / 2^30,3), 'gigabytes'))
# [1] "3.493 gigabytes" too big to open in Notepad++ !
# But can read with 010 Editor

# look at the top of the file 
readLines(my_file, n = 100)

# to find where the content starts, vary the skip value, 
read.table(my_file, nrows = 10, skip = 25)

这是我们开始处理文件块的地方,我们可以以通常的方式使用大多数 dplyr 动词:

# Let the chunked pkg work its magic! We only want the lines containing 
# "Gun_control". The main challenge here was identifying the column
# header
df <- 
read_chunkwise(my_file, 
               chunk_size=5000,
               skip = 30,
               format = "table",
               header = TRUE) %>% 
  filter(stringr::str_detect(De.mw.De.5.J3M1O1, "Gun_control"))

# this line does the evaluation, 
# and takes a few moments...
system.time(out <- collect(df))

在这里我们可以像往常一样处理输出,因为它比输入文件小得多:

# clean up the output to separate into cols, 
# and get the number of page views as a numeric
out_df <- 
out %>% 
  separate(De.mw.De.5.J3M1O1, 
           into = str_glue("V1:4"),
           sep = " ") %>% 
  mutate(V3 = as.numeric(V3))

 head(out_df)
    V1                                                        V2   V3
1 en.z                                               Gun_control 7961
2 en.z Category:Gun_control_advocacy_groups_in_the_United_States 1396
3 en.z          Gun_control_policy_of_the_Clinton_Administration  223
4 en.z                            Category:Gun_control_advocates   80
5 en.z                         Gun_control_in_the_United_Kingdom   68
6 en.z                                    Gun_control_in_america   59
                                                                                 V4
1 A34B55C32D38E32F32G32H20I22J9K12L10M9N15O34P38Q37R83S197T1207U1643V1523W1528X1319
2                                     B1C5D2E1F3H3J1O1P3Q9R9S23T197U327V245W271X295
3                                     A3B2C4D2E3F3G1J3K1L1O3P2Q2R4S2T24U39V41W43X40
4                                                            D2H1M1S4T8U22V10W18X14
5                                                             B1C1S1T11U12V13W16X13
6                                                         B1H1M1N2P1S1T6U5V17W12X12

#--------------------

【讨论】:

似乎chunkedLaF 的包装。 是的,虽然 IMO 对用户更友好【参考方案4】:

fread()绝对可以帮你分块读取数据

您在代码中所犯的错误是,在循环期间更改函数中 skip 参数的大小时,您应该保持 nrow 不变。

这就是我为我的数据写的:

data=NULL

for (i in 0:20)

    data[[i+1]]=fread("my_data.csv",nrow=10000,select=c(1,2:100),skip =10000*i)






您可以在循环中插入以下代码:


start_time <- Sys.time()
#####something!!!!

end_time <- Sys.time()

end_time - start_time


检查时间——每个循环平均花费相似的时间。

然后您可以使用另一个循环将数据按行与 R 中的函数默认 rbind 函数组合。

示例代码可能是这样的:

new_data = data[[1]]

for (i in 1:20)
    new_data=rbind(new_data,data[[i+1]],use.names=FALSE)


统一成一个大数据集。

希望我的回答对您的问题有所帮助。

我使用这种方法在大约 8 分钟内加载了 2k+ 列、200k 行的 18Gb 数据。

【讨论】:

你是我的英雄,我使用了参数 rnows 但它是 nrow

以上是关于使用包 data.table 中的 fread 一次读取块的主要内容,如果未能解决你的问题,请参考以下文章

R data.table v1.9.6 中的错误 - 函数“fread”

追加多个大data.table;使用 colClasses 和 fread 的自定义数据强制;命名管道

data.table::fread 不喜欢第一列中的缺失值

来自 data.table 包的 fread 无法读取小数字

data.table fread 可以接受连接吗?

使用 data.table (with fread) 快速读取和组合多个文件