合并列表与共同元素

Posted

技术标签:

【中文标题】合并列表与共同元素【英文标题】:Merging list with common elements 【发布时间】:2018-04-29 13:07:30 【问题描述】:

我有一个清单

[[1]]
[1] 7

[[2]]
[1] 10 11 12 211 446 469

[[3]]
[1] 10 11 12 13

[[4]]
[1] 11 12 13 215

[[5]]
[1] 15 16

[[6]]
[1] 15 17 216 225

我想合并具有共同元素的列表切片,并索引哪些列表切片已合并。我想要的输出如下。

$`1`
[1] 7

$`2`, `3`, `4`
[1] 10 11 12 13 211 215 446 469

$`5`,`6`
[1] 15 16 17 216 225

(我已将原始列表切片索引作为新列表名称,但任何形式的输出都可以。)

可重现的数据:

mylist <- list(7, c(10, 11, 12, 211, 446, 469), c(10, 11, 12, 13), c(11, 
12, 13, 215), c(15, 16), c(15, 17, 216, 225))

【问题讨论】:

这可能是igraph 包的一个很好的用例。 【参考方案1】:

这是使用“Matrix”和“igraph”包的另一种方法。

首先,我们需要提取连接了哪些元素的信息。使用稀疏矩阵可以潜在地节省大量内存使用:

library(Matrix)
i = rep(1:length(mylist), lengths(mylist)) 
j = factor(unlist(mylist))
tab = sparseMatrix(i = i, j = as.integer(j), x = TRUE, dimnames = list(NULL, levels(j)))
#as.matrix(tab)  ## just to print colnames
#         7    10    11    12    13    15    16    17   211   215   216   225   446   469
#[1,]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#[2,] FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE  TRUE
#[3,] FALSE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#[4,] FALSE FALSE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
#[5,] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#[6,] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE

查找每个元素是否相互连接:

connects = tcrossprod(tab, boolArith = TRUE)
#connects
#6 x 6 sparse Matrix of class "lsCMatrix"
#                
#[1,] | . . . . .
#[2,] . | | | . .
#[3,] . | | | . .
#[4,] . | | | . .
#[5,] . . . . | |
#[6,] . . . . | |

然后,使用图表,我们可以对“mylist”的索引进行分组:

library(igraph)
# 'graph_from_adjacency_matrix' seems to not work with the "connects" object directly. 
# An alternative to coercing "connects" here would be to build it as 'tcrossprod(tab) > 0'

group = clusters(graph_from_adjacency_matrix(as(connects, "lsCMatrix")))$membership
#group
#[1] 1 2 2 2 3 3

最后,连接:

tapply(mylist, group, function(x) sort(unique(unlist(x))))
#$`1`
#[1] 7
#
#$`2`
#[1]  10  11  12  13 211 215 446 469
#
#$`3`
#[1]  15  16  17 216 225

tapply(1:length(mylist), group, toString)
#        1         2         3 
#      "1" "2, 3, 4"    "5, 6" 

【讨论】:

【参考方案2】:

对解决方案不满意,但我认为这给出了答案。仍有改进的余地:

unique(sapply(lst, function(x) 
       unique(unlist(lst[sapply(lst, function(y) 
                         any(x %in% y))]))))


#[[1]]
#[1] 7

#[[2]]
#[1]  10  11  12 211 446 469  13 215

#[[3]]
#[1]  15  16  17 216 225

这基本上是一个双循环来检查任何列表元素是否存在于任何另一个列表中。如果您找到任何此类元素,则将它们合并在一起,仅从中取出 unique 值。

数据

lst <- list(7, c(10 ,11 ,12, 211, 446, 469), c(10, 11, 12, 13),c(11 ,12, 13 ,215), 
               c(15, 16), c(15, 17 ,216 ,225))

【讨论】:

第一列为“基因”和剩余值的文件列表。我想将它映射到我有一个注释文件的基因名称,其中包含第一列作为“基因”,另一列作为符号。现在我必须映射每个文件,以便它获得相应探针的符号。 filelist = list.files(pattern = ".*.txt") datalist = lapply(filelist, function(x)read.table(x,header=T)) lapply(datalist, function(x) inner_join(ANNOTATION_FILE, x) ) 错误:需要by,因为数据源没有公共变量调用rlang::last_error()查看回溯【参考方案3】:

这是一个完成任务的递归函数(尽管现在它会产生一堆警告)。

mylist <- list(7, c(10, 11, 12, 211, 446, 469), c(10, 11, 12, 13), c(11, 12, 13, 215), c(15, 16), c(15, 17, 216, 225))

commonElements = function(l,o=list(l[[1]]))
  if(length(l) == 0)return(o)
  match = which(unlist(lapply(lapply(o,intersect,l[[1]]),any)))
  if(length(match) == 0) o[[length(o)+1]] = l[[1]]
  if(length(match) == 1) o[[match]] = unique(c(o[[match]],l[[1]]))
  if(length(match) > 1)
    o[[match[1]]] = unique(unlist(o[match]))
    p[rev(match)[-1]] = NULL
  
  l[[1]] = NULL
  commonElements(l,o)


commonElements(mylist)

基本上,传入一个列表并使用l 的第一个元素实例化输出o。然后针对o中的每个组检查l的每个值,如果不匹配,则在o中创建一个新元素,如果匹配一个,则保留唯一集,如果匹配多个1,则连接组在o 中删除额外内容。

【讨论】:

【参考方案4】:

这是一种基于 purrr 的方法:

library(purrr)

mylist <- list(7, 
               c(10, 11, 12, 211, 446, 469), 
               c(10, 11, 12, 13), 
               c(11, 12, 13, 215), 
               c(15, 16), 
               c(15, 17, 216, 225))

result <- mylist %>% 
    # check whether any numbers of an element are in any of the elements
    map(~map_lgl(mylist, compose(any, `%in%`), .x)) %>% 
    unique() %>%    # drop duplicated groups
    map(~reduce(mylist[.x], union))    # subset lst by group and collapse subgroups

str(result)
#> List of 3
#>  $ : num 7
#>  $ : num [1:8] 10 11 12 211 446 469 13 215
#>  $ : num [1:5] 15 16 17 216 225

这里的逻辑与 Ronak 的回答类似;我只是觉得这更容易阅读。如果您愿意,可以将最后一行写成map(~unique(flatten_dbl(mylist[.x]))) 或拆分成map(~mylist[.x]) %&gt;% simplify_all() %&gt;% map(unique)

对于哪个元素聚合到哪个组的索引,只需在用于子集的元素上调用which

mylist %>% 
    map(~map_lgl(mylist, compose(any, `%in%`), .x)) %>% 
    unique() %>% 
    map(which) %>% 
    str()
#> List of 3
#>  $ : int 1
#>  $ : int [1:3] 2 3 4
#>  $ : int [1:2] 5 6

整个事情的另一种逻辑是使列表嵌套而不是调用,这意味着自连接是前面的(cross2),后面没有子集,大部分函数只是设置操作:

mylist %>% 
    map(cross2, mylist) %>% 
    modify_depth(2, reduce, ~if(length(intersect(.x, .y)) > 0) sort(union(.x, .y))) %>% 
    map(reduce, union) %>% 
    unique()

或者使用cross2.filter参数,

mylist %>% 
    map(cross2, mylist, ~length(intersect(.x, .y)) == 0) %>% 
    map(compose(sort, unique, unlist)) %>% 
    unique()

可以压缩成

mylist %>% 
    map(function(element) sort(unique(unlist(cross2(element, mylist, ~length(intersect(.x, .y)) == 0))))) %>%
    unique()

不过,这些方法直到最后都不会删除重复的组,因此它们的效率可能较低。

【讨论】:

以上是关于合并列表与共同元素的主要内容,如果未能解决你的问题,请参考以下文章

基于共同的第一个元素合并二维列表中的元素

如何将列表与常见元素合并,这些元素本身就是列表/元组?

将特定值的元素与列表中的相邻元素合并

Leetcode(712)-账户合并

基于共同元素合并 2 个数组

Python元组列表将第二个元素与唯一的第一个元素合并