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

时间:2015-08-20 18:34:03

标签: r parallel-processing data.table

要并行化任务,我需要将一个大的data.table拆分为大致相等的部分, 将列组合在一起,id。假设:

N是数据的长度

kid

的不同值的数量

M是所需部件的数量

想法是M< k<< N,因此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=4{{a,b}, {c}, {d,e}, {f}}

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

有效的方法是什么?

4 个答案:

答案 0 :(得分:5)

如果id的分布没有病态偏差,那么最简单的方法就是这样:

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

它使用 factor-value mod 数量的桶id分配给存储桶。

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

答案 1 :(得分:4)

初步评论

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

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

  

而不是按字面意思分割数据 - 创建一个新的&#34; parallel.id&#34;列,然后调用

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

答案,假设您不想使用by

按大小对ID进行排序:

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

重新排列,以便我们在大小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

答案 2 :(得分:1)

如果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"

答案 3 :(得分:1)

使用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:我已经根据以前的答案学到了很多有用的东西。