在大型数据集上按组拆分和expand.grid

时间:2017-10-25 16:44:00

标签: r split expand

我有以下格式的df并尝试获取包含每组所有成对组合的数据框

df<-structure(list(id = c(209044052, 209044061, 209044061, 209044061,209044062, 209044062, 209044062, 209044182, 209044183, 209044295), group = c(2365686, 387969, 388978, 2365686, 387969, 388978, 2365686, 2278460, 2278460, 654238)), .Names = c("id", "group"), row.names = c(NA, -10L), class = "data.frame")

虽然do.call(rbind, lapply(split(df, df$group), function(i) expand.grid(i$id, i$id)))适用于小型数据框,但我的大数据时遇到了时间问题(大约1200万个,大约150万个组)。

经过一些测试后,我发现split命令似乎是瓶颈,expand.grid可能也不是最快的解决方案。

为expand.grid Use outer instead of expand.grid找到了一些改进 这里有一些更快的拆分替代方案Improving performance of split() function in R?,但很难将它们与分组结合在一起。

输出应该类似

  Var1      Var2
209044061 209044061
209044062 209044061
209044061 209044062
209044062 209044062
209044061 209044061
209044062 209044061
209044061 209044062
209044062 209044062
209044295 209044295
209044182 209044182
209044183 209044182
....

作为额外的我想排除同一对的重复,自我引用(例如,高于209044061 209044061)并且只保留一个组合,如果它们处于不同的顺序(例如,高于209044061 209044062并且209044062 209044061)(不重复的组合)。尝试使用'combination()`尝试library(gtools),但无法弄清楚这是否会进一步降低计算速度。

1 个答案:

答案 0 :(得分:3)

避免重复使用同一对以及不同订单的一种可能解决方案是使用data.tablecombinat包:

library(data.table)
setDT(df)[order(id), data.table(combinat::combn2(unique(id))), by = group]
     group        V1        V2
1: 2365686 209044052 209044061
2: 2365686 209044052 209044062
3: 2365686 209044061 209044062
4:  387969 209044061 209044062
5:  388978 209044061 209044062
6: 2278460 209044182 209044183
这里使用

order(id)只是为了方便更好地检查结果,但可以在生产代码中跳过。

非等连接

替换combn2()

另一种方法是将combn2()的调用替换为非equi连接:

mdf <- setDT(df)[order(id), unique(id), by = group]
mdf[mdf, on = .(group, V1 < V1), .(group, x.V1, i.V1), nomatch = 0L,
    allow.cartesian = TRUE]
     group        V1        V2
1: 2365686 209044052 209044061
2: 2365686 209044052 209044062
3: 2365686 209044061 209044062
4:  387969 209044061 209044062
5:  388978 209044061 209044062
6: 2278460 209044182 209044183

请注意,非equi连接需要订购数据。

基准

第二种方法似乎要快得多

# create benchmark data
nr <- 1.2e5L # number of rows
rg <- 8L # number of ids within each group
ng <- nr / rg # number of groups
set.seed(1L)
df2 <- data.table(
  id = sample.int(rg, nr, TRUE),
  group = sample.int(ng, nr, TRUE)
)

#benchmark code
microbenchmark::microbenchmark(
  combn2 = df2[order(group, id), data.table((combinat::combn2(unique(id)))), by = group],
  nej = {
    mdf <- df2[order(group, id), unique(id), by = group]
    mdf[mdf, on = .(group, V1 < V1), .(group, x.V1, i.V1), nomatch = 0L,
        allow.cartesian = TRUE]},
  times = 1L)

对于120000行和14994组,时间为:

Unit: milliseconds
   expr        min         lq       mean     median         uq        max neval
 combn2 10259.1115 10259.1115 10259.1115 10259.1115 10259.1115 10259.1115     1
    nej   137.3228   137.3228   137.3228   137.3228   137.3228   137.3228     1

买者

正如by the OP所指出的那样,idgroup的数量在内存消耗和速度方面至关重要。组合的数量为 O(n 2 ,完全 n * (n-1) / <如果 n 是ID的数量,则em> 2 或choose(n, 2L)

可以通过

找到最大组的大小
df2[, uniqueN(id), by = group][, max(V1)]

最终结果中的总行数可以提前计算

df2[, uniqueN(id), by = group][, sum(choose(V1, 2L))]