在 R 中组合大量数据集的更快方法?

Posted

技术标签:

【中文标题】在 R 中组合大量数据集的更快方法?【英文标题】:A faster way to combine a large number of datasets in R? 【发布时间】:2020-08-21 13:49:13 【问题描述】:

我遇到了在 R 中组合大量数据集(准确地说是 858 个)的问题,并编写了几行代码来解决这个问题,尽可能提高内存效率。下面的代码正在运行,但我想知道是否有更快的方法来做到这一点。虽然工作正常且内存使用稳定,但随着每个数据集的添加,该过程确实会大大减慢。超过 350 左右,它变得异常缓慢。并非不可能运行,但我希望有某种方法来优化它。

每个数据集由大约 17000 行、88 列组成,数据集之间存在大量重复项。它是 Twitter 数据,因此 status_id 变量可以很好地指示重复行。我正在使用 data.table 来提高流程效率。正在清除重复项,删除刚刚添加的数据集,并在添加数据集后调用 gc()。我怀疑 unique() 函数会随着数据集的增长而减慢,因此在这里分块这个过程可能是一种选择。但也许还有一些我没有看到的东西?

filenames <- list.files(pattern = "dataset_*")

full_data <- data.table()

for (i in 1:length(filenames))
  load(filenames[i]) 
  print(paste(i, "/", length(filenames), ":", filenames[i]))  # indicates current dataset as number of total datasets
  dataset <- as.data.table(get(ls(pattern="dataset_"))) 
  full_data <- rbind(full_data, dataset)   # add new dataset
  rm(list=ls(pattern="dataset_"))  # remove dataset
  full_data  <- unique(full_data, fromLast = T, by="status_id")  #remove duplicate tweets in data
  gc(verbose = F)  # call gc to clear out RAM 

欢迎对这个问题提出任何意见,我正在努力养成使我的 R 代码尽可能高效的习惯 :)

【问题讨论】:

这些都是.Rdata文件吗?为什么不首先弄清楚每个status_id 在哪个数据集和行中看到,然后在绑定之前过滤数据集?比如:rbind(full_data, dataset[!(dataset$id %in% full_data$id)]) 除非您遇到内存问题 - 在每个循环中调用 gc() 可能需要一两秒。我会尝试使用purr::map_dfr(filenames, vroom::vroom) 之类的东西并在之后删除重复项 - 但我不是效率高手。编辑:您需要将full.names = T 作为参数添加到list.files() 才能正常工作 我对@9​​87654328@ 的内部工作原理不是很熟悉,但是您的代码结构看起来非常类似于在循环中追加之前未预先分配数组的常见陷阱.优化通常取决于数据,但我会尝试预先分配 full_data 的大小或使用 purrr::map 创建一个列表,然后批量合并它们。 purrr::reduce 可能更类似于您的代码现在所做的,也可能值得尝试 感谢您的意见!我以前没有使用过 purrr,所以我肯定会检查一下并尝试在那里进行优化。在这里预先分配大小似乎是明智之举。我对合并前的过滤有点谨慎,担心时间关键重复的数据丢失 - 例如我需要确保我拥有最新的推文实例,以确保我拥有最新的点赞数。但是由于数据集是按日期命名的,因此从后面遍历它们可能是一个简单的解决方法。 vroom 听起来很有希望,但似乎不适用于实际的 RDa 文件 - 还是我弄错了? 【参考方案1】:

执行此操作的更好方法是批量处理数据集,而不是迭代处理。您可以通过l 一个list 的多个data.tables 并将它们与rbindlist 同时绑定在一起。然后你会setkeyv 设置data.table 对象的键。使用 set 键在 data.table 上调用 unique.data.table 应该更快,但它可能相当于您使用 by

l <- list(full_data, data)
full_data <- rbindlist(l)
setkeyv(full_data, "status_id")
full_data <- unique(full_data)

【讨论】:

data.tables 的 rbind 和 rbindlist 之间是否存在实质性区别?我假设 rbind 有一个 data.table 方法,而 rbidndlist 是列表的便利函数。我对少量数据的初始方法是一次对所有数据使用 rbindlist。相当快,但我在这种情况下遇到了内存问题,所有数据一次加载。而且我会尝试使用独特的键,这听起来很有希望。谢谢! 有区别,最好使用 rbindlist 但将列表分成块,所以使用 rbindlist 几次而不是使用 n 次使用 rbind【参考方案2】:

所以,我做了一些修改并得出了第一个解决方案。使用 rbindlist 批量合并数据集确实被证明是一个很大的效率提升。

我相信这确实主要与 rbindlist 自动执行的大小预分配有关。我正在努力寻找一种优雅的方式来预分配数据集,这种方式不涉及手动命名所有 88 列并疯狂猜测数据集的最终大小(由于在此过程中删除了重复项)。因此,我没有将 rbindlist 解决方案与具有预分配数据集大小的 rbind 解决方案进行基准测试。

所以这里有一个批处理文件并通过 rbindlist 组合它们的解决方案。将所有 858 个数据集组合到一个包含约 236000 条推文的初始数据集的过程,初始搜索时间为 909.68 system.time() 和一个约 250 万行的数据集。

filenames <- list.files(pattern = "dataset_*") 
   
full_data <- as.data.table(data_init)
    
  for (i in seq(1,length(filenames),13))  # sequence into batches (here: 13)
    files <- filenames[i:(i+12)] # loop through the batched files to load 
    for (j in 1:length(files)) 
      load(files[j])
      print(paste((i+j-1), "/", length(filenames), ":", files[j])) # indicates current dataset as number of total datasets
    full_data <- rbindlist(mget(c(ls(pattern="dataset"), ls(pattern="full_data"))))   # add new datasets
    print("- batch combined -")
    rm(list=ls(pattern="dataset"))  # remove data
    full_data <- unique(full_data, fromLast = T, by="status_id")  #remove duplicate tweets in data
  

我已将它们分成 13 个批次,因为这与 858 个数据集非常吻合。一些测试表明,大约 8 个数据集的批次可能更有效。不知道什么是处理文件数量的优雅方法,但批次不均衡。

【讨论】:

以上是关于在 R 中组合大量数据集的更快方法?的主要内容,如果未能解决你的问题,请参考以下文章

在 BigQuery 中将大量数据从美国数据集迁移到欧盟数据集的最佳方法?

在R中生成大量组合[关闭]

在 R 中拆分大型数据集的有效方法

如何根据 R 中的某些模式导入大量数据集

SQL Server 中对大型数据集的慢速不同查询

es召回大量数据慢