【问题标题】:Partially read really large csv.gz in R using vroom使用 vroom 在 R 中部分读取非常大的 csv.gz
【发布时间】:2021-10-08 04:49:50
【问题描述】:

我有一个 csv.gz 文件(据我所知)压缩前的大小为 70GB。我的机器有 50GB 的 RAM,所以无论如何我永远无法在 R 中将它作为一个整体打开。

例如,我可以按如下方式加载前 10m 行:

library(vroom)

df <- vroom("HUGE.csv.gz", delim= ",", n_max = 10^7)

对于我必须做的事情,一次加载 10m 行,执行我的操作,然后继续接下来的 10m 行就可以了。我可以循环执行此操作。

因此我尝试了skip 参数。

df <- vroom("HUGE.csv.gz", delim= ",", n_max = 10^7, skip = 10^7)

这会导致错误:

Error: The size of the connection buffer (131072) was not large enough
to fit a complete line:
  * Increase it by setting `Sys.setenv("VROOM_CONNECTION_SIZE")`

我用Sys.setenv("VROOM_CONNECTION_SIZE" = 131072*1000) 增加了这个值,但是错误仍然存​​在。

有解决办法吗?

编辑:我发现随机访问 gzip 压缩的 csv (csv.gz) 是不可能的。我们必须从头开始。可能最简单的就是解压保存,然后跳过就可以了。

【问题讨论】:

    标签: r csv vroom


    【解决方案1】:

    我无法找出vroom 解决超大内存(gzip 压缩)csv 文件的解决方案。但是,以下方法对我来说效果很好,我很高兴知道查询速度更快同时还节省磁盘空间的方法。

    1. 使用xsv 中的https://github.com/BurntSushi/xsv 中的split 子命令将大的csv 文件拆分为在RAM 内舒适的块,例如10^5 行并将它们保存在一个文件夹。
    2. 使用data.table::fread 逐一读取所有块(以避免内存不足错误),使用for 循环并使用arrow 包将所有块作为压缩的parquet 文件保存到文件夹中空间并准备大表以进行快速查询。为了更快地操作,建议重新保存parquet 文件,按您需要经常过滤的字段分区
    3. 现在您可以使用arrow::open_dataset 并使用dplyr 命令查询该多文件拼花文件夹。根据我的经验,它占用最少的磁盘空间并提供最快的结果。

    我使用data.table::fread 来明确定义每个字段的列类,以实现最快速、最可靠的 csv 文件解析。 readr::read_csv 也很准确,但速度较慢。但是,通过read_csv 自动分配列类以及通过read_csv 自定义定义列类的方式实际上是最好的 - 所以更少的人工时间但更多的机器时间 - 这意味着它总体上可能会更快,具体取决于场景。其他 csv 解析器对我使用的那种 csv 文件抛出错误并浪费时间。

    您现在可以删除包含分块 csv 文件的文件夹以节省空间,除非您想尝试使用其他 csv 解析器循环读取它们。

    其他以前成功的方法:循环读取上面提到的所有csv块并将它们保存到:

    1. 使用disk.frame 包的文件夹。然后可以使用文档中解释的dplyrdata.table 命令查询该文件夹。它可以保存在压缩的fst 文件中,从而节省空间,但不如parquet 文件那么多。
    2. DuckDB 数据库中的一个表,允许使用SQLdplyr 命令进行查询。使用数据库表方法不会为您节省磁盘空间。但是DuckDB 还允许使用SQL 命令查询分区/未分区的拼花文件(这样可以节省磁盘空间)。

    编辑:- 改进方法如下

    我做了一些实验,发现了一种更好的方法来完成上述操作。使用下面的代码,大型(压缩)csv 文件将在 R 环境中自动分块(无需使用任何外部工具,如xsv),所有块将以parquet 格式写入准备查询的文件夹中。

    library(readr)
    library(arrow)
    
    fyl <- "...path_to_big_data_file.csv.gz"
    pqFolder <- "...path_to_folder_where_chunked_parquet_files_are_to_be_saved"
    
    f <- function(x, pos){
      write_parquet(x,
                    file.path(pqFolder, paste0(pos, ".parquet")),
                    compression = "gzip",
                    compression_level = 9)
    }
    
    read_csv_chunked(
      fyl,
      col_types = list(Column1="f", Column2="c", Column3="T", ...), # all column specifications
      callback = SideEffectChunkCallback$new(f),
      chunk_size = 10^6)
    

    如果您想使用 -

    而不是 parquet
    1. disk.frame,回调函数可用于创建分块压缩的fst 文件,用于dplyrdata.table 样式查询。
    2. DuckDB,回调函数可用于将append分块放入数据库表中,用于SQLdplyr样式查询。

    通过明智地选择readr::read_csv_chunked 命令的chunk_size 参数,计算机在运行查询时永远不会耗尽内存。

    PS:我对parquet 文件使用gzip 压缩,因为它们可以使用ParquetViewer 来自https://github.com/mukunku/ParquetViewer 进行预览。否则,zstdParquetViewer 目前不支持)解压速度更快,从而提高读取速度。

    编辑 2:

    我得到了一个 csv 文件,它对我的​​机器来说非常大:20 GB 压缩后扩展至大约 83 GB,而我的家用笔记本电脑只有 16 GB。原来我在前面 EDIT 中提到的read_csv_chunked 方法无法完成。它总是在一段时间后停止工作,并且不会创建所有 parquet 块。使用我之前使用xsv 拆分csv 文件然后循环创建parquet 块的方法。公平地说,我必须提到这种方式也需要多次尝试,并且我已经编写了一个检查程序,以便在连续尝试运行程序时只创建 附加 parquet 块。

    编辑 3:

    VROOM 在处理大文件时确实有困难,因为它需要将索引以及从文件中读取的任何数据存储在内存中。见开发帖https://github.com/r-lib/vroom/issues/203

    编辑 4:

    附加提示:通过上述方法创建的分块拼花文件可以非常方便地使用 SQL 和 DuckDB 方法进行查询 https://duckdb.org/docs/data/parquethttps://duckdb.org/2021/06/25/querying-parquet.html

    DuckDB 方法很重要,因为 R Arrow 方法目前存在一个非常严重的限制,官方文档页面https://arrow.apache.org/docs/r/articles/dataset.html 中提到了这一限制。

    具体来说,我引用:“在当前版本中,箭头支持dplyr动词mutate(), transmute(), select(), rename(), relocate(), filter()arrange()。还不支持聚合,所以在调用summarise()或其他具有聚合函数的动词之前,使用collect() 将选定的数据子集拉入内存中的 R 数据帧。"

    问题是,如果您在一个非常大的数据集上使用collect(),RAM 使用量会激增并且系统崩溃。然而,使用 SQL 语句在与 DuckDB 相同的大数据集上执行相同的聚合作业不会导致 RAM 使用高峰,也不会导致系统崩溃。因此,在 Arrow 将自己修复为大数据的聚合查询之前,DuckDB 中的 SQL 提供了一个很好的解决方案来查询分块拼花格式的大数据集。

    【讨论】:

    • 这是一个非常好的答案,我特别喜欢read_csv_chunked 提示以及使用arrow 包来创建镶木地板数据集。关于您在编写过程中遇到的问题:根据几个 Arrow 票证,read_csv_chunked 冻结的原因与 Windows 上 R Arrow 包中的多线程问题有关。这(据说)将在包的 6.0.0 版本中得到修复。同时,您可以通过使用options(arrow.use_threads = FALSE)set_io_thread_count(1)set_cpu_count(1) 禁用多线程来避免这些问题。
    猜你喜欢
    • 1970-01-01
    • 2016-11-23
    • 1970-01-01
    • 2020-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    相关资源
    最近更新 更多