R 高效地合并大型数据表

R 高效地合并大型数据表,r,performance,memory,merge,data.table,R,Performance,Memory,Merge,Data.table,我有两个相当大的数据.table对象要合并 dt1在5列上有500.000.000个观察值 dt2在两列上有300000个观察值 两个对象都有相同的键,称为id 我想将信息从dt2加入dt1 例如: dt1 <- data.table(id = c(1, 2, 3, 4), x1 = c(12, 13, 14, 15), x2 = c(5, 6, 7, 8), x3 = c(33, 44, 55

我有两个相当大的
数据.table
对象要合并

  • dt1
    在5列上有500.000.000个观察值
  • dt2
    在两列上有300000个观察值
两个对象都有相同的
,称为
id

我想将
信息从
dt2
加入
dt1

例如:

dt1  <- data.table(id = c(1, 2, 3, 4),
               x1 = c(12, 13, 14, 15),
               x2 = c(5, 6, 7, 8),
               x3 = c(33, 44, 55, 66),
               x4 = c(123, 123, 123, 123))

dt2 <- data.table(id = c(1, 2, 3, 4),
              x5 = c(555, 666, 777, 888))
setkey(dt1, id)
setkey(dt2, id)

dt2[dt1, on="id"] 

> dt2[dt1, on="id"]
   id  x5 x1 x2 x3  x4
1:  1 555 12  5 33 123
2:  2 666 13  6 44 123
3:  3 777 14  7 55 123
4:  4 888 15  8 66 123

dt1如果您必须使用拆分-合并方法,并且以下操作在内存中工作,请确保尽可能多地预分配,以加快迭代。因此,在处理类似问题时,这是我能想到的最有效的解决方案:

dt1  <- data.table(id = c(1, 2, 3, 4),
                   x1 = c(12, 13, 14, 15),
                   x2 = c(5, 6, 7, 8),
                   x3 = c(33, 44, 55, 66),
                   x4 = c(123, 123, 123, 123))

dt2 <- data.table(id = c(1, 2, 3, 4),
                  x5 = c(555, 666, 777, 888))

dt1_id <- sort(unique(dt1$id)) # extract all ids that are in dt1
dt1_l_split <- length(dt1_id) # get number of iterations
dt2_l_split <- length(unique(dt2[id %in% dt1_id]$id))

split_dt1 <- vector(mode = "list", length = length(unique(dt1$id))) # preallocate vector
split_dt1 <- lapply(1:dt1_l_split, function(x) dt1[id %in% dt1_id[[x]]]) # fill list with splits

rm(dt1); gc() # remove the large data table to save memory and clean up RAM

dt1 <- lapply(1:dt1_l_split, function(i) {
  print(Sys.time())
  print(i)

  tmp <- dt2[id %in% dt1_id[[i]]] # load relevant parts from dt2
  merge(tmp, split_dt1[[i]], all = TRUE) # merge dt1 and dt2
})
rbindlist(dt1)
dt1键控分配应节省内存

dt1[dt2, on = "id", x5 := x5]
我们应该使用数据库库来完成这项工作吗

那可能是个好主意。如果设置和使用数据库对您来说很痛苦,请尝试该软件包。这很简单


我的实验

tl;dr:例如,与合并和替换相比,键控分配使用的内存减少了55%

我编写了两个脚本,每个脚本都源于一个设置脚本,
dt setup.R
来创建
dt1
dt2
。第一个脚本,
dt merge.R
,通过“merge”方法更新了
dt1
。第二个,
dt keyed assign.R
,使用键控赋值。两个脚本都使用
rprofm()
函数记录内存分配

为了不折磨我的笔记本电脑,我让
dt1
be 500000行和
dt2
3000行

脚本:

# dt-setup.R
library(data.table)

set.seed(9474)
id_space <- seq_len(3000)
dt1  <- data.table(
  id = sample(id_space, 500000, replace = TRUE),
  x1 = runif(500000),
  x2 = runif(500000),
  x3 = runif(500000),
  x4 = runif(500000)
)
dt2 <- data.table(
  id = id_space,
  x5 = 11 * id_space
)
setkey(dt1, id)
setkey(dt2, id)
在我的工作目录中有三个脚本,我在一个单独的R进程中运行了每个连接脚本

system2("Rscript", "dt-merge.R")
system2("Rscript", "dt-keyed-assign.R")
我认为输出文件中的行通常遵循模式
:“
”。我还没有找到这方面的好文档。但是,前面的数字从来都不低于128,这是默认的最小字节数,对于向量,低于该值R不
malloc

请注意,并非所有这些分配都会添加到R使用的总内存中。R可能会在垃圾收集后重新使用它已经拥有的一些内存。因此,这不是衡量在任何特定时间使用了多少内存的好方法。然而,如果我们假设垃圾收集行为是独立的,它确实可以作为脚本之间的比较

内存报告的一些示例行:

cat(readLines("dt-merge.out", 5), sep = "\n")
# 90208 :"get" "[" 
# 528448 :"get" "[" 
# 528448 :"get" "[" 
# 1072 :"get" "[" 
# 20608 :"get" "["
还有类似于
newpage的行:“get”“[”
用于页面分配

幸运的是,这些都很容易解析

parse_memory_report <- function(path) {
  report <- readLines(path)
  new_pages <- startsWith(report, "new page:")
  allocations <- as.numeric(gsub(":.*", "", report[!new_pages]))
  total_malloced <- sum(as.numeric(allocations))
  message(
    "Summary of ", path, ":\n",
    sum(new_pages), " new pages allocated\n",
    sum(as.numeric(allocations)), " bytes malloced"
  )
}

parse_memory_report("dt-merge.out")
# Summary of dt-merge.out:
# 12 new pages allocated
# 32098912 bytes malloced

parse_memory_report("dt-keyed-assign.out")
# Summary of dt-keyed-assign.out:
# 13 new pages allocated
# 14284272 bytes malloced

parse\u memory\u报告您在完整数据集上遇到了哪些具体的内存问题?您认为合并的表适合您的RAM吗,而R只是在合并过程中使用了太多内存?@Marius:合并的表很容易适合我的RAM。但是在合并过程中,它无法再分配。可能的重复:不是abov的重复提到的e as OP正在寻找在资源约束下操作与大型数据集的左连接的方法,这真的很有趣。我很想知道这是否解决了OPs问题,因为我已经遇到过这个问题一百万次了,但不知何故从未费心设置键。这个解决方案似乎在一小部分数据上运行良好,但仍然会遇到tr在我的64GB RAM笔记本电脑上出现问题。我将此问题留给其他人提出解决方案。很好。是的,join上的赋值不需要创建新的data.table,而第一种方法会创建全新的DT。这是最节省内存的方法。可以采用的其他技巧是确保尽可能使用较小的列:int而不是double将使内存需求减少2倍。将平台更改为Linux最终可能也会有所帮助。@wake\u wake测试我发布的代码片段。它应该可以工作(至少它一直对我有效),但不能告诉你有多快(或者更好的慢)是的。如果它能工作,请在数据子集上用McLappy再试一次,看看它是否能加快计算速度(我想会的,因为排列是相互独立的)。还请注意,至少根据我的经验,如果您无法访问performant群集,那么具有此类维度的DB可能不允许您更快地执行这些操作。
cat(readLines("dt-merge.out", 5), sep = "\n")
# 90208 :"get" "[" 
# 528448 :"get" "[" 
# 528448 :"get" "[" 
# 1072 :"get" "[" 
# 20608 :"get" "["
parse_memory_report <- function(path) {
  report <- readLines(path)
  new_pages <- startsWith(report, "new page:")
  allocations <- as.numeric(gsub(":.*", "", report[!new_pages]))
  total_malloced <- sum(as.numeric(allocations))
  message(
    "Summary of ", path, ":\n",
    sum(new_pages), " new pages allocated\n",
    sum(as.numeric(allocations)), " bytes malloced"
  )
}

parse_memory_report("dt-merge.out")
# Summary of dt-merge.out:
# 12 new pages allocated
# 32098912 bytes malloced

parse_memory_report("dt-keyed-assign.out")
# Summary of dt-keyed-assign.out:
# 13 new pages allocated
# 14284272 bytes malloced