为大量重叠区间的字典键合并值

1 投票
2 回答
851 浏览
提问于 2025-04-16 09:32

我有一个字典,里面又包含了字典,内容大概是这样的:

all={
    1:{ ('a',123,145):20, ('a',155,170):12, ('b',234,345): 34},
    2:{ ('a',121,135):10, ('a',155,175):28, ('b',230,345): 16},
    3:{ ('a',130,140):20, ('a',150,170):10, ('b',234,345): 30}, 
    ...
    n: {...}
}

补充说明:这些字典的名字是我根据最初读取的数据文件随便起的,我可以用任何我想要的名字来命名这些字典。

我想要计算每个重叠区域的值的总和。输出结果应该像这样:

 { ('a',121,122):10, ('a',123,130):30, ('a',131,135):50, 
   ('a',136,140):40,('a',141,145):20, ...}

补充说明:每个字典里的区间是互不重叠的,所以在同一个字典里不会出现 ('a',2,10) 和 ('a',3,12) 这样的情况,但不同字典之间的区间可能会重叠,因为它们的起始和结束位置不一样(也就是说,字典之间的键是不一样的)。

我不一定要使用字典这种数据结构,因为我最开始就是自己创建了这个字典。如果用列表、集合等其他结构更简单,我也可以把数据放到这些结构里,我也可以尝试用其他数据结构来解决这个问题。

谢谢你的帮助。

2 个回答

0

好的,如果这些是染色体,我们就先把它们单独列出来:

{"Chr1": {(121,122):10, (123,130):30, ...},
"Chr2": {(230,233):16, ...},
...
}

你在加的那些数字,我想应该是某种分数——表达分数或者其他的。

如果这些位置的范围(这121和130定义的区间)足够小——最多几千个位置的话,存储每个位置的总分数会让你省去很多麻烦。你只需要把一个区间的分数加到这个区间内的每个位置上就行了。

但如果这些是单独的基本位置,而且可能有几百万个位置,那么你就得继续使用区间了。这样的话,对于每个区间,你需要检查相关的染色体,找出它重叠的区间,然后把那些重叠的去掉,接着把它们拆分成更多的小区间,以便存储所有不同的总分数。

这里有一个大致的框架,但还不完整:

for (start, end), score in intervals_to_add.items():
    overlapping = {}
    for (start1, end1), score1 in current_chromosome.items():
        if start1 <= start <= end1 or start1 <= end <= end1:
            overlapping[(start1, end1)] = score1
    for interval in overlapping:
        current_chromosome.pop(interval)
    # Process overlapping into smaller intervals, adding in the current interval
    current_chromosome.update(new_intervals)
1

好的,现在我明白了:基本上,你有一堆重叠的区间,用一些条形来表示,这些条形在某个位置上有一定的厚度。你可以把这些条形一个个画在下面,然后看看在任何一个点上它们的厚度加起来有多厚。

我觉得利用你有整数位置这个特点来做这个事情是最简单和最快的:

all={
    1:{ ('a',123,145):20, ('a',155,170):12, ('b',234,345): 34},
    2:{ ('a',121,135):10, ('a',155,175):28, ('b',230,345): 16},
    3:{ ('a',130,140):20, ('a',150,170):10, ('b',234,345): 30}
}

from collections import defaultdict
summer = defaultdict(int)
mini, maxi = 0,0
for d in all.values():
    for (name, start, stop), value in d.iteritems(): 
        # im completely ignoring the `name` here, not sure if that's what you want
        # else just separate the data before doing this ...
        if mini == 0:
            mini = start
        mini, maxi = min(mini, start), max(maxi, stop)
        for i in range(start, stop+1):
            summer[i]+=value

# now we have the values at each point, very redundant but very fast so  far
print summer

# now we can find the intervals:
def get_intervals(points, start, stop):
    cstart = start
    for i in range(start, stop+1):
        if points[cstart] != points[i]: # did the value change ?
            yield cstart, i-1, points[cstart]
            cstart = i

    if cstart != i:
        yield cstart, i, points[cstart]


print list(get_intervals(summer, mini, maxi))

当只使用'a'项时,它给出的结果是:

[(121, 122, 10), (123, 129, 30), (130, 135, 50), (136, 140, 40), (141, 145, 20), (146, 149, 0), (150, 154, 10), (155, 170, 50), (171, 175, 28)]

补充:我突然想到一个非常简单的方法:

from collections import defaultdict
from heapq import heappush, heappop

class Summer(object):
    def __init__(self):
        # its a priority queue, kind of like a sorted list
        self.hq = []

    def additem(self, start, stop, value):
        # at `start` add it as a positive value
        heappush(self.hq, (start, value))
        # at `stop` subtract that value again
        heappush(self.hq, (stop, -value))

    def intervals(self):
        hq = self.hq
        start, val = heappop(hq)
        while hq:
            point, value = heappop(hq)
            yield start, point, val
            # just maintain the current value and where the interval started
            val += value
            start = point
        assert val == 0

summers = defaultdict(Summer)
for d in all.values():
    for (name, start, stop), value in d.iteritems():
        summers[name].additem(start, stop, value)

for name,s in summers.iteritems():
    print name, list(s.intervals())

撰写回答