如何根据一个向量属于另一个向量来合并向量列表?

5
在R语言中,我有两个包含列表列的数据框。
d1 <- data.table(
  group_id1=1:4
)
d1$Cat_grouped <- list(letters[1:2],letters[3:2],letters[3:6],letters[11:12] )

并且

d_grouped <- data.table(
  group_id2=1:4
)
d_grouped$Cat_grouped <- list(letters[1:5],letters[6:10],letters[1:2],letters[1] )

我希望能够根据d_grouped$Cat_grouped中的向量包含在d1$Cat_grouped中的向量来合并这两个数据表。
更精确地说,可能有两个匹配标准:
a)d1$Cat_grouped每个向量的所有元素必须在d_grouped$Cat_grouped的匹配向量中。
结果如下所示:
result_a <- data.table(
   group_id1=c(1,2)
   group_id2=c(1,1)
)

b) d1$Cat_grouped向量中的至少一个元素必须匹配d_grouped$Cat_grouped向量中的元素。

从而得到以下匹配结果:

result_b <- data.table(
  group_id1=c(1,2,3,3),
  group_id2=c(1,1,1,2)
)

我该如何实现a)或b)?最好用data.table的方式。
编辑1:添加了a)和b)的预期结果。
编辑2:将更多的组添加到了d_grouped中,因此分组变量重叠。这会破坏一些提出的解决方案。
3个回答

4

因此,我认为长格式更好,尽管我的答案感觉有点绕。 我敢打赌,那些在数据表方面更熟练的人可以用更少的步骤完成这个过程,但是这就是我得到的结果:

首先,让我们拆开您示例数据中的向量:

d1_long <- d1[, list(cat=unlist(Cat_grouped)), group_id1]
d_grouped_long <- d_grouped[, list(cat=unlist(Cat_grouped)), group_id2]

现在,我们可以按照单个元素进行合并:
result_b <- merge(d1_long, d_grouped_long, by='cat')

根据我们的例子,似乎您实际上不需要知道哪些元素是匹配的一部分...

result_b[, cat := NULL]

最后,我的答案包含了重复的group_id对,因为它对每个成对匹配进行了连接,而不仅仅是向量级别的匹配。 因此,我们可以将其唯一化。

result_b <- unique(result_b)

这是我的result_b结果:

   group_id.1 group_id.2
1:          1          1
2:          2          1
3:          3          1
4:          3          2

我们可以使用 b 作为 a 的中间步骤,因为拥有任何共同元素是拥有所有共同元素的子集。

让我们合并原始表格,看看在子向量和向量方面的候选项是什么。

result_a <- merge(result_b, d1, by = 'group_id1')
result_a <- merge(result_a, d_grouped, by = 'group_id2')

现在,如果Cat_grouped.x的长度与Cat_grouped.x在Cat_grouped.y中为TRUE的数量相匹配,那么就是一个bingo。

我试过几种简洁的方法,但是数据表中有列表的奇怪性质打败了最显然的尝试。不过这似乎有效:

让我们添加一个row列来操作by

result_a[, row := 1:.N]

现在让我们获取匹配项的长度和数量...
result_a[, x.length := length(Cat_grouped.x[[1]]), row]
result_a[, matches := sum(Cat_grouped.x[[1]] %in% Cat_grouped.y[[1]]), row]

筛选出长度与匹配项完全相同的行

result_a <- result_a[x.length==matches]

太好了。谢谢。你已经提供了问题b)的结果(我错放了预期输出)。你敢尝试问题a)吗? - LucasMation
哈哈,你在问题中说了“或者”,所以我就停下来了,认为这已经足够了。我一直在尝试,似乎更难了,但我会继续努力的。 - HarlandMason
针对a),我想你可以采用left-join的方式进行合并,以便保留d1_long中的所有元素,甚至是未匹配的元素。然后按group_id.x计算group_id.y的不同元素数量。然后仅保留如果此计数==1且在group_id.y中没有NA的情况。 - LucasMation
我已经编辑了这个答案,包括我的尝试。虽然你所描述的听起来可能需要较少的中间步骤,因此可能更好! - HarlandMason
您的代码无法处理OP提供的数据:*找不到对象'group_id'*。 - Uwe
啊,谢谢你指出这个问题。我想他的问题在我的回答下面被编辑了。我会修正我的回答。 - HarlandMason

2

另一种方法:

使用交叉连接获取所有组ID对:

Y = CJ(group_id1=d1$group_id1, group_id2=d_grouped$group_id2)

然后将向量合并:

Y = Y[d1, on='group_id1'][d_grouped, on='group_id2']

#    group_id1 group_id2 Cat_grouped i.Cat_grouped
# 1:         1         1         a,b     a,b,c,d,e
# 2:         2         1         c,b     a,b,c,d,e
# 3:         3         1     c,d,e,f     a,b,c,d,e
# 4:         4         1         k,l     a,b,c,d,e
# 5:         1         2         a,b     f,g,h,i,j
# 6:         2         2         c,b     f,g,h,i,j
# 7:         3         2     c,d,e,f     f,g,h,i,j
# 8:         4         2         k,l     f,g,h,i,j

现在,您可以使用mapply来按照您喜欢的方式进行过滤:
Y[mapply(function(u,v) all(u %in% v), Cat_grouped, i.Cat_grouped), 1:2]
#    group_id1 group_id2
# 1:         1         1
# 2:         2         1

Y[mapply(function(u,v) length(intersect(u,v)) > 0, Cat_grouped, i.Cat_grouped), 1:2]
#    group_id1 group_id2
# 1:         1         1
# 2:         2         1
# 3:         3         1
# 4:         3         2

这很漂亮和优雅。但在我的用例中,d1和d_grouped可以有数百万个观测值。因此,仅物理上创建交叉连接将在内存使用方面太昂贵。 - LucasMation

2

本答案重点关注问题的 a) 部分。

它遵循 Harland 的方法,但试图更好地利用 data.table 的习惯用法以提高性能,因为 OP 提到他的生产数据可能包含数百万条观测值。

样本数据

library(data.table)
d1 <- data.table(
  group_id1 = 1:4,
  Cat_grouped = list(letters[1:2], letters[3:2], letters[3:6], letters[11:12]))

d_grouped <- data.table(
  group_id2 = 1:2,
  Cat_grouped = list(letters[1:5], letters[6:10]))

结果 a)

grp_cols <- c("group_id1", "group_id2")
unique(d1[, .(unlist(Cat_grouped), lengths(Cat_grouped)), by = group_id1][
  d_grouped[, unlist(Cat_grouped), by = group_id2], on = "V1", nomatch = 0L][
    , .(V2, .N), by = grp_cols][V2 == N, ..grp_cols])

   group_id1 group_id2
1:         1         1
2:         2         1

解释

在将d1d_grouped的列表元素扩展为长格式时,使用lengths()函数确定了d1的列表元素数量。 lengths()(注意与length()的区别)获取列表每个元素的长度,并在R 3.2.0中引入。

在内部连接之后(请注意nomatch = 0L参数),对于grp_cols的每个组合,计算结果集中的行数(使用特殊符号.N)。仅考虑结果集中计数与列表原始长度匹配的行。最后返回grp_cols的唯一组合。

结果 b)

通过省略计数内容,可以从上述解决方案中得出结果 b):

unique(d1[, unlist(Cat_grouped), by = group_id1][
  d_grouped[, unlist(Cat_grouped), by = group_id2], on = "V1", nomatch = 0L][
      , c("group_id1", "group_id2")])
   group_id1 group_id2
1:         1         1
2:         2         1
3:         3         1
4:         3         2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接