如何加速400万集交叉口?

时间:2016-07-25 03:23:37

标签: python set bioinformatics

我是一名缺乏经验的程序员,正在使用Python进行大量的生物信息学练习。

一个问题区域计算名称组之间的集合交集中的元素,以及计算在字典中的存储。每个列表有两个2000个名称组;名称组中的名称是物种的拉丁名称。例如:

list__of_name_groups_1 = [
    ['Canis Lupus', 'Canis Latrans'],
    ['Euarctos Americanus', 'Lynx Rufus'],
    ...
]
list__of_name_groups_2 = [
    ['Nasua Narica', 'Odocoileus Hemionus'],
    ['Felis Concolor', 'Peromyscus Eremicus'],
    ['Canis Latrans', 'Cervus Canadensis']
    ...
]

我需要一个字典,其中包含名称组之间的所有交叉点大小,例如

>>> intersections
{ (0, 0): 0, (0, 1): 0, (0, 2): 1, (1, 0): 0, (1, 1): 0, (2, 1): 0,
  (2, 0): 1, (2, 1): 0, (2, 2): 0 }

'Canis Latrans'出现在第一个列表中的元素0中,第二个列表中的元素2。)

我有一个可行的算法实现,但运行速度太慢。

overlap = {}
    for i in list_of_lists_of_names_1:            
        for j in list_of_lists_of_names_2:
            overlap[(i,j)] = len(set(i) & set(j))

是否有更快的方法来计算集合交叉点中的元素数量?

(你好主持人......尼克,这个修改过的帖子实际上问的问题与我正在处理的问题略有不同。虽然你的答案对于解决这个问题非常好,但我担心这个方法你建议实际上对我正在尝试做的事情没有用。我非常感谢你在答案中投入的时间和精力,以及编辑这篇文章,但我会要求将这篇文章还原为原文。) / p>

4 个答案:

答案 0 :(得分:1)

首先, Python set擅长查找交叉点(它们使用散列),但您的代码反复构造相同的set。例如。如果两个list每个包含2000个元素[你的意思是外部或内部list是那么长吗?],只有4000个不同的set要计算,但你的代码计算2000 x 2000 x 2 = 800万set s。

所以一次计算那4000套:

list_of_name_tuples_1 = [("a", "aa"), ("b", "bbb"), ("c", "cc", "ccc")]
list_of_name_tuples_2 = [("a", "AA"), ("b", "BBB"), ("c", "cc", "CCC")]
name_sets_1 = [set(i) for i in list_of_name_tuples_1]
name_sets_2 = [set(i) for i in list_of_name_tuples_2]

overlap = {}
for l1, s1 in zip(list_of_name_tuples_1, name_sets_1):
    for l2, s2 in zip(list_of_name_tuples_2, name_sets_2):
        overlap[(l1, l2)] = len(s1 & s2)

Python list是不可用的,因此它们不能用于dict键,因此我将名单列表更改为元组列表 - 名。

(此代码假设您正在使用Python 3,其中zip()返回迭代器。如果您使用的是Python 2,则调用itertools.izip()以获取配对元素的迭代器。)< / p>

其次,考虑将overlap重组为dict dict,而不是由元组编制索引的dict

list_of_name_tuples_1 = [("a", "aa"), ("b", "bbb"), ("c", "cc", "ccc")]
list_of_name_tuples_2 = [("a", "AA"), ("b", "BBB"), ("c", "cc", "CCC")]
name_sets_1 = [set(i) for i in list_of_name_tuples_1]
name_sets_2 = [set(i) for i in list_of_name_tuples_2]

overlap = {}
for l1, s1 in zip(list_of_name_tuples_1, name_sets_1):
    d = overlap.setdefault(l1, {})
    for l2, s2 in zip(list_of_name_tuples_2, name_sets_2):
        d[l2] = len(s1 & s2)

这可以在后续代码中节省大量工作,后者代码将通过overlap[l1][l2]而不是overlap[(l1, l2)]访问它(没有元组构造或哈希生成),并且嵌套循环可以获取{{1然后在外部循环中访问内部循环中的d = overlap[l1]

答案 1 :(得分:0)

根据您的数据的具体情况,替代选项是,对于每个可能的数据项,您记录它所包含的列表。

使用这样的数据结构,您可以为每个数据项快速确定哪些列表包含它,并增加overlap的相应条目。

答案 2 :(得分:0)

实际上你可以用一个长整数表示每个列表。例如,用第一个元素,第二个元素设置,但是没有第三个元素可以表示为(0 <&lt;&lt; 3)+(1 << 2 )+(1 << 1)= 6。

那么你可以通过计算整数&amp;来计算集合交集。操作

答案 3 :(得分:0)

这取决于您的数据的性质,但如果两个超级列表共有相对较少的拉丁名称,则可能会给您带来很大的节省。方法是:

  1. 查找列表之间的通用名称
  2. 仅在包含其中一个常用名称的名称组之间计算集合交集
  3. 其余的交集计数将为0
  4. 由于您执行的设置操作数,您自己的解决方案很慢:2,000 x 2,000 == 4,000,000。无论Python如何快速执行每一项,都需要时间。我的方法减少了因子1000计算的集合交叉点的数量,但代价是其他一些较小的计算。

    我的封套计算是,如果普通名称相对较少,您可以将性能提高4倍或更高。这种改进将越少,通用名称就越少。

    我在这里使用了一些可能对您不熟悉的内容:列表推导和enumerate()defaultdict,使用in列出成员资格和itertools方法。那让你陷入了困境。快乐的研究,如果你想要一些解释,请告诉我。

    from collections import defaultdict
    import itertools
    
    list_of_name_groups_1 = [
        ['Canis Lupus', 'Canis Latrans'],
        ['Euarctos Americanus', 'Lynx Rufus'],
    ]
    list_of_name_groups_2 = [
        ['Nasua Narica', 'Odocoileus Hemionus'],
        ['Felis Concolor', 'Peromyscus Eremicus'],
        ['Canis Latrans', 'Cervus Canadensis']
    ]
    
    def flatten(list_of_lists):
        return itertools.chain.from_iterable(list_of_lists)
    
    
    def unique_names(list_of_name_groups):
        return set(flatten(list_of_name_groups))
    
    
    def get_matching_name_groups(name, list_of_name_groups):
        return (list_index for list_index, name_group
                in enumerate(list_of_name_groups)
                if name in name_group)
    
    list1_candidates = set()
    list2_candidates = set()
    common_names = unique_names(list_of_name_groups_1) & unique_names(list_of_name_groups_2)
    for name in common_names:
        list1_candidates.update(tuple(get_matching_name_groups(name, list_of_name_groups_1)))
        list2_candidates.update(tuple(get_matching_name_groups(name, list_of_name_groups_2)))
    intersections = defaultdict(lambda: 0)
    for i, j in itertools.product(list1_candidates, list2_candidates):
            intersections[(i, j)] = len(set(list_of_name_groups_1[i]) & set(list_of_name_groups_2[j]))
    print(intersections)
    
    >>> python intersections.py
    defaultdict(<function <lambda> at 0x0000000000DC7620>, {(0, 2): 1})