将 data.table 拆分为大致相等的部分

Posted

技术标签:

【中文标题】将 data.table 拆分为大致相等的部分【英文标题】:Split data.table into roughly equal parts 【发布时间】:2015-08-20 18:34:03 【问题描述】:

为了并行化一项任务,我需要将一个大数据表拆分为大致相等的部分, 将由列id 定义的组保持在一起。假设:

N是数据的长度

kid 的不同值的数量

M 是所需零件的数量

这个想法是 M id 分割是不好的。

library(data.table)
library(dplyr)

set.seed(1)
N <- 16 # in application N is very large
k <- 6  # in application k << N
dt <- data.table(id = sample(letters[1:k], N, replace=T), value=runif(N)) %>%
      arrange(id)
t(dt$id)

#     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16]
# [1,] "a"  "b"  "b"  "b"  "b"  "c"  "c"  "c"  "d"  "d"   "d"   "e"   "e"   "f"   "f"   "f"  

在此示例中,M=3 的所需拆分为 a,b, c,d, e,f 对于M=4a,b, c, d,e, f

更一般地说,如果 id 是数字,则分界点应该是quantile(id, probs=seq(0, 1, length.out = M+1), type=1) 或类似的拆分为大致相等的部分。

什么是有效的方法?

【问题讨论】:

【参考方案1】:

初步评论

我建议阅读 the main author of data.table has to say 关于并行化的内容。

我不知道您对 data.table 有多熟悉,但您可能忽略了它的by 参数...?从下面引用@eddi 的评论...

不要从字面上拆分数据 - 创建一个新的“parallel.id”列,然后调用

dt[, parallel_operation(.SD), by = parallel.id] 

回答,假设你不想使用by

按大小对 ID 进行排序:

ids   <- names(sort(table(dt$id)))
n     <- length(ids)

重新排列,以便我们在大 ID 和小 ID 之间交替,following Arun's interleaving trick:

alt_ids <- c(ids, rev(ids))[order(c(1:n, 1:n))][1:n]

按顺序拆分 id,每组中的 ID 数量大致相同(如zero323's answer):

gs  <- split(alt_ids, ceiling(seq(n) / (n/M)))

res <- vector("list", M)
setkey(dt, id)
for (m in 1:M) res[[m]] <- dt[J(gs[[m]])] 
# if using a data.frame, replace the last two lines with
# for (m in 1:M) res[[m]] <- dt[id %in% gs[[m]],] 

检查尺寸是否太差:

# using the OP's example data...

sapply(res, nrow)
# [1] 7 9              for M = 2
# [1] 5 5 6            for M = 3
# [1] 1 6 3 6          for M = 4
# [1] 1 4 2 3 6        for M = 5

虽然我在顶部强调了data.table,但这也应该适用于data.frame

【讨论】:

关注的不是数据拆分的速度,而是拆分到M核的需求。 @selig 我同意。我引用的部分建议(对我而言)您认为效率是这个问题的背景下的一个问题(据我所知,这只是关于拆分数据)。 而不是从字面上拆分数据 - 创建一个新的“parallel.id”列,然后调用dt[, parallel_operation(.SD), by = parallel.id] @eddi 好的,我已将您的评论复制到答案中。【参考方案2】:

如果 id 的分布不是病态的,最简单的方法就是这样:

split(dt, as.numeric(as.factor(dt$id)) %% M)

它使用 factor-value mod number-of bucketsid 分配给存储桶。

对于大多数应用程序来说,获得相对平衡的数据分布就足够了。不过,您应该小心时间序列等输入。在这种情况下,您可以在创建因子时简单地强制执行随机级别的顺序。为 M 选择一个素数是一种更稳健的方法,但很可能不太实用。

【讨论】:

太好了,这就是我想要的 @selig 我认为Frank's 的答案要好得多,更不用说data.table 具体了。【参考方案3】:

如果 k 足够大,你可以使用这个想法将数据分成组:

首先,让我们找出每个 id 的大小

group_sizes <- dt[, .N, by = id]

然后创建 2 个长度为 M 的空列表,用于检测组的大小以及它们将包含哪些 id

grps_vals <- list()
grps_vals[1 : M] <- c(0)

grps_nms <- list()
grps_nms[1 : M] <- c(0)

(这里我特别添加了零值以便能够创建大小为 M 的列表)

然后在每次迭代中使用循环将值添加到最小的组。这将使组大致相等

for ( i in 1:nrow(group_sizes))
   sums <- sapply(groups, sum) 
   idx <- which(sums == min(sums))[1]
   groups[[idx]] <- c(groups[[idx]], group_sizes$N[i])
   

最后,从名称列表中删除第一个零元素:)

grps_nms <- lapply(grps_nms, function(x)x[-1])

> grps_nms
[[1]]
[1] "a" "d" "f"

[[2]]
[1] "b"

[[3]]
[1] "c" "e"

【讨论】:

我想也许你想对组大小进行排序,以便首先添加最大的。如果最后添加最大的,则尺寸可能会非常不平衡。 做排序也是个好主意!另一方面,我在每次迭代中找到最小的组。考虑到 M 你添加到最小的组,但你添加到它的不一定是小的。假设 M = 2,组大小为 (2,2,4)。因为您直接在组大小上循环,所以随着循环的进行,分区大小将依次为 2,0、2,2、6,2。但是,如果您先将组大小排序到 (4,2,2),您将有 4,0、4,2、4,4,这更好。我不确定我是否在这里说清楚...... 是的,我明白了你的想法。会更好!谢谢【参考方案4】:

只是使用 dplyr 的另一种方法。逐步运行链式脚本,以可视化数据集在每个步骤中的变化情况。这是一个简单的过程。

    library(data.table)
    library(dplyr)

    set.seed(1)
    N <- 16 # in application N is very large
    k <- 6  # in application k << N
    dt <- data.table(id = sample(letters[1:k], N, replace=T), value=runif(N)) %>%
      arrange(id)



dt %>% 
  select(id) %>%
  distinct() %>%                   # select distinct id values
  mutate(group = ntile(id,3)) %>%  # create grouping 
  inner_join(dt, by="id")          # join back initial information

PS:根据以前的答案,我学到了很多有用的东西。

【讨论】:

我认为这与零的答案基本相同;它有效地忽略了N(当你使用distinct时)。 我假设(仅通过查看 a,b,c,... 示例)我们需要分组的 ID 在其中具有某种顺序。如果没有,那么当我得到不同的 ID 时,我可以将它们打乱,以防前 2-3 个 ID 有大量的观察结果,并使用打乱的版本进行分组。如果需要,我可以更新我的答案。 另外,我认为目标不是通过将 ID 分配给组来拆分数据集,而是在其中一个组中保持相同的 ID。正如他所提到的:“M=3 的期望拆分是 a,b, c,d, e,f,而 M=4 是 a,b, c, d,e, f”。我只在其中一个组中看到每个 ID。我错过了什么吗? 是的,关键是将ID分成组;所有的答案都是这样做的,我的评论并没有与之矛盾,所以我真的不知道你在说什么。我不是要你更新它。你可以自己决定。只是指出它与零答案中的操作大致相同。 不,我没有说你让我做进一步的事情。我只是一时糊涂了。

以上是关于将 data.table 拆分为大致相等的部分的主要内容,如果未能解决你的问题,请参考以下文章

如何将大文本文件拆分为行数相等的小文件?

将列表分成相等的部分?

通过R中的列的cumsum拆分data.table

根据模式将data.table列拆分为许多未知数量的列

在 data.table 中拆分字符串并转换为长格式的快速方法

将数组拆分为部分[重复]