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
对象要合并
在5列上有500.000.000个观察值李>dt1
在两列上有300000个观察值dt2
键
,称为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