Dataframe 列表中的快速半连接

vuktfyat  于 2023-04-09  发布在  其他
关注(0)|答案(1)|浏览(122)

我正在尝试对数据框列表执行半连接。我有大约1000个数据框,共50,000行,我想根据另一个数据框选择其中的行子集。
请考虑以下示例:

df1 <- data.frame(id = 50:150)
set.seed(1)
l <- replicate(1000,
                 data.frame(id = sample(1:100, 50000, replace = TRUE),
                  id2 = sample(letters, 50000, replace = TRUE),
                  info = runif(50000, 200, 300)),
                 simplify = FALSE)

我想根据df1的id对l的每个 Dataframe 进行子集化。我可以通过执行半连接或使用%in%进行子集化来实现这一点:

library(dplyr)
semi_join(l[[1]], df1, "id")

l[[1]][l[[1]]$id %in% df1$id, ]

我正在寻找一个可以扩展到数千个 Dataframe 的快速解决方案。到目前为止,我将其 Package 在lapply中(但可能有更快的矢量化解决方案)。

lapply(l, \(x) semi_join(x, df1, "id"))

下面是一个包含解决方案的初始基准:

bc <- bench::mark(lapply(l, \(x) semi_join(x, df1, "id")),
            lapply(l, \(x) x[x$id %in% df1$id, ]), iterations = 1, check = FALSE)

> bc
# A tibble: 2 × 13
#  expression                                            min  median itr/s…¹ mem_a…² gc/se…³ n_itr
#  <bch:expr>                                       <bch:tm> <bch:t>   <dbl> <bch:b>   <dbl> <int>
#1 lapply(l, function(x) semi_join(x, df1, "id"))    9.34s   9.34s  0.107   1.97GB   0.428     1
#2 lapply(l, function(x) x[x$id %in% df1$id, ])     14.19s  14.19s  0.0705   2.5GB   0.282     1
sczxawaw

sczxawaw1#

更新

感谢@jblood94的评论,我们有了另一个更有效的参数,而不是使用rep来添加grp列,它确实提高了性能(参见dtsplit1

microbenchmark(
  "dtsplit1" = split(rbindlist(l, idcol = "grp"), by = "grp", keep.by = FALSE),
  "dtsplit" = split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE),
  times = 10
)

给予

Unit: seconds
     expr      min       lq     mean   median       uq      max neval
 dtsplit1 1.779087 1.843032 1.989136 1.957378 2.041067 2.391234    10
  dtsplit 2.059481 2.150111 2.190301 2.176397 2.215940 2.387897    10

如果您已经为所有“真实的的”1000个 Dataframe 修复了50000行,希望下面的代码可以提供一些帮助

library(data.table)
split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE)

基准测试

microbenchmark(
  "semijoin" = lapply(l, \(x) semi_join(x, df1, "id")),
  "in" = lapply(l, \(x) x[x$id %in% df1$id, ]),
  "dtsplit" = split(rbindlist(l)[, grp := rep(seq_along(l), each = 50000)], by = "grp", keep.by = FALSE),
  times = 10
)

显示

Unit: seconds
     expr      min       lq     mean   median       uq      max neval
 semijoin 2.679046 3.122411 3.589847 3.375409 3.544307 5.084460    10
       in 4.357969 4.789931 6.271855 6.207127 7.651371 8.430053    10
  dtsplit 1.680729 2.685881 3.377203 2.922039 3.239485 7.833068    10

相关问题