【问题标题】:Improving code design of DNA alignment degapping改进 DNA 比对去间隙的代码设计
【发布时间】:2016-01-15 17:23:33
【问题描述】:

这是一个关于更有效的代码设计的问题:

假设三个对齐的 DNA 序列(seq1、seq2 和 seq3;它们都是字符串)代表两个基因(gene1 和gene2)。这些基因的起始和终止位置相对于比对的 DNA 序列是已知的。

# Input
align = {"seq1":"ATGCATGC", # In seq1, gene1 and gene2 are of equal length
         "seq2":"AT----GC",
         "seq3":"A--CA--C"}
annos = {"seq1":{"gene1":[0,3], "gene2":[4,7]},
         "seq2":{"gene1":[0,3], "gene2":[4,7]},
         "seq3":{"gene1":[0,3], "gene2":[4,7]}}

我希望从比对中删除间隙(即破折号)并保持基因开始和停止位置的相对关联。

# Desired output
align = {"seq1":"ATGCATGC", 
         "seq2":"ATGC",
         "seq3":"ACAC"}
annos = {"seq1":{"gene1":[0,3], "gene2":[4,7]},
         "seq2":{"gene1":[0,1], "gene2":[2,3]},
         "seq3":{"gene1":[0,1], "gene2":[2,3]}}

获得所需的输出并不像看起来那么简单。下面我为这个问题写了一些(行号)伪代码,但肯定有更优雅的设计。

1  measure length of any aligned gene  # take any seq, since all seqs aligned
2  list_lengths = list of gene lengths  # order is important
3  for seq in alignment
4      outseq = ""
5      for each num in range(0, length(seq))  # weird for-loop is intentional
6          if seq[num] == "-"
7              current_gene = gene whose start/stop positions include num
8              subtract 1 from length of current_gene
9              subtract 1 from lengths of all genes following current_gene in list_lengths
10         else
11             append seq[num] to outseq
12     append outseq to new variable
13     convert gene lengths into start/stop positions and append ordered to new variable

谁能给我建议/示例,让我设计更短、更直接的代码?

【问题讨论】:

  • 寻找 Pythonian 解决方案,最好将其标记为 python - 我会放弃伪代码。您已经将数组从 1 重新“重新定位”到 0:考虑表示范围/切片,包括排除 [from, to)annoalign 通过“标签”的“关联”看起来很轻微。 - 您需要指定基因之间允许的重叠或间隙,如果有的话。在 annos 中保持 genes 有序应该会有所帮助 - 指定! 8&9 可能太详细了。有根据的猜测:根据表示,13 大约是您复杂性的一半 - 扩展。 (一旦您获得了代码并且仍然存在问题,请考虑在 CODE REVIEW 中展示此内容。)
  • @greybeard 我按照您的建议更改了标签。即将根据您的建议(尤其是第 13 行)更改伪代码。
  • 澄清一下,这就是你的数据的意思吗?对于序列AT----GC"gene1":[0,3], "gene2":[4,7]表示gene1为AT--,可简写为AT,gene2为--GC,可简写为GC
  • 后续问题,输入/输出格式是固定的还是只需要包含相同的数据?如果格式灵活,编写 Pythonic 解决方案会更容易。
  • @Pausbrak 关于您的第一个问题:是的,您对“缩短”(生物信息学家将其称为 degapping)的说明是正确的。关键是在这个去间隙步骤之后注释仍然是正确的。

标签: python algorithm optimization dna-sequence


【解决方案1】:

此答案处理您更新的 annos 字典,从评论到 cdlanes 答案。该答案使annos 字典的seq2 gene2 的索引为[2,1] 不正确。如果序列包含该区域中的所有间隙,我提出的解决方案将从字典中删除 gene 条目。还要注意的是,如果一个基因在最后的align 中只包含一个字母,那么anno[geneX] 将具有相同的开始和停止索引--> 请参阅seq3 gene1 来自您的评论annos

align = {"seq1":"ATGCATGC",
         "seq2":"AT----GC",
         "seq3":"A--CA--C"}

annos = {"seq1":{"gene1":[0,3], "gene2":[4,7]},
         "seq2":{"gene1":[0,3], "gene2":[4,7]},
         "seq3":{"gene1":[0,3], "gene2":[4,7]}}


annos3 = {"seq1":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}, 
          "seq2":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}, 
          "seq3":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}}

import re
for name,anno in annos.items():
    # indices of gaps removed usinig re
    removed = [(m.start(0)) for m in re.finditer(r'-', align[name])]

    # removes gaps from align dictionary
    align[name] = re.sub(r'-', '', align[name])

    build_dna = ''
    for gene,inds in anno.items():

        start_ind = len(build_dna)+1

        #generator to sum the num '-' removed from gene
        num_gaps = sum(1 for i in removed if i >= inds[0] and i <= inds[1])

        # build the de-gapped string
        build_dna+= align[name][inds[0]:inds[1]+1].replace("-", "")

        end_ind = len(build_dna)

        if num_gaps == len(align[name][inds[0]:inds[1]+1]): #gene is all gaps
            del annos[name][gene] #remove the gene entry
            continue
        #update the values in the annos dictionary
        annos[name][gene][0] = start_ind-1
        annos[name][gene][1] = end_ind-1

结果:

In [3]: annos
Out[3]:  {'seq1': {'gene1': [0, 3], 'gene2': [4, 7]},
          'seq2': {'gene1': [0, 1], 'gene2': [2, 3]},
          'seq3': {'gene1': [0, 1], 'gene2': [2, 3]}}

来自上述 3 基因annos 的结果。只需替换 annos 变量:

In [5]: annos3
Out[5]:  {'seq1': {'gene1': [0, 2], 'gene2': [3, 4], 'gene3': [5, 7]},
          'seq2': {'gene1': [0, 1], 'gene3': [2, 3]},
          'seq3': {'gene1': [0, 0], 'gene2': [1, 2], 'gene3': [3, 3]}}

【讨论】:

  • 看起来首屈一指。非常明智的评论。
  • @Kevin 自动删除仅包含间隙的注释不是我开始考虑的功能。然而,这种情况并不少见,好的代码应该能够处理它。感谢您指出。
【解决方案2】:

以下匹配两个测试用例的示例程序的输出:

align = {"seq1":"ATGCATGC",
         "seq2":"AT----GC",
         "seq3":"A--CA--C"}

annos = {"seq1":{"gene1":[0,3], "gene2":[4,7]},
         "seq2":{"gene1":[0,3], "gene2":[4,7]},
         "seq3":{"gene1":[0,3], "gene2":[4,7]}}

(START, STOP) = (0, 1)

for alignment, sequence in align.items():
    new_sequence = ''
    gap = 0

    for position, codon in enumerate(sequence):
        if '-' == codon:
            for gene in annos[alignment].values():
                if gene[START] > (position - gap):
                    gene[START] -= 1
                if gene[STOP] >= (position - gap):
                    gene[STOP] -= 1
            gap += 1
        else:
            new_sequence += codon

    align[alignment] = new_sequence

运行结果:

python3 -i test.py
>>> align
{'seq2': 'ATGC', 'seq1': 'ATGCATGC', 'seq3': 'ACAC'}
>>> 
>>> annos
{'seq1': {'gene1': [0, 3], 'gene2': [4, 7]}, 'seq2': {'gene1': [0, 1], 'gene2': [2, 3]}, 'seq3': {'gene1': [0, 1], 'gene2': [2, 3]}}
>>> 

我希望这仍然足够优雅、直接、简短和 Pythonic!

【讨论】:

  • @cdlane 你的解决方案肯定比我复杂的尝试更直接和 Pythonic。如果您可以解决一个错误(这似乎是系统性的;见下文),我很乐意选择您的答案。至于系统错误:请使用以下示例 annos 测试您的代码,并查看 seq2seq3gene3 的错误下限:@ 987654323@
  • @MichaelGruenstaeudl 我想我已经解决了一个错误,并且输出与您的代码生成的相匹配。享受吧!
【解决方案3】:

我自己对上述问题的解决方案既不是优雅的也不是 Pythonic,而是达到了所需的输出。非常欢迎任何改进建议

import collections
import operator
# measure length of any aligned gene  # take any seq, since all seqs aligned
align_len = len(align.itervalues().next())
# initialize output
align_out, annos_out = {}, {}
# loop through annos
for seqname, anno in annos.items():
# operate on ordered sequence lengths instead on ranges
    ordseqlens = collections.OrderedDict()
# generate ordered sequence lengths
    for k,v in sorted(anno.items(), key=operator.itemgetter(1)):
        ordseqlens[k] = v[1]-v[0]+1
# start (and later append to) sequence output
    align_out[seqname] = ""
# generate R-style for-loop
    for pos in range(0, len(align[seqname])):
        if align[seqname][pos] == "-":
            try:
                current_gene = next(key for key, a in anno.items() if a[0] <= pos <= a[1])
            except StopIteration:
                print("No annotation provided for position", pos, "in sequence", seqname)
# subtract 1 from lengths of current_gene
            ordseqlens[current_gene] = ordseqlens[current_gene]-1
# append nucleotide unless a gap
        else:
            align_out[seqname] += align[seqname][pos]
# convert modified ordered sequence lengths back into start/stop positions
    summ = 0
    tmp_dict = {}
    for k,v in ordseqlens.items():
        tmp_dict[k] = [summ, v+summ-1]
        summ = v+summ
# save start/stop positions to correct annos
    annos_out[seqname] = tmp_dict

这段代码的输出是:

>>> align_out
{'seq3': 'ACAC',
 'seq2': 'ATGC',
 'seq1': 'ATGCATGC'}

>>> annos_out
{'seq3': {'gene1': [0, 1], 'gene2': [2, 3]},
 'seq2': {'gene1': [0, 1], 'gene2': [2, 3]},
 'seq1': {'gene1': [0, 3], 'gene2': [4, 7]}}

【讨论】:

    【解决方案4】:

    所以,我认为尝试将每个序列分解为基因然后删除破折号的方法会导致大量不必要的簿记。相反,直接查看破折号然后根据它们的相对位置更新所有索引可能更容易。这是我编写的一个似乎运行正常的函数:

    from copy import copy
    
    def rewriteGenes(align, annos):
        alignments = copy(align)
        annotations = copy(annos)
    
        for sequence, alignment in alignments.items():
            while alignment.find('-') > -1:
                index = alignment.find('-')
                for gene, (start, end) in annotations[sequence].items():
                    if index < start: 
                        annotations[sequence][gene][0] -= 1
                    if index <= end: 
                        annotations[sequence][gene][1] -= 1
                alignment = alignment[:index] + alignment[index+1:]
            alignments[sequence] = alignment
    
        return (alignments, annotations)
    

    这会遍历每个比对中的破折号,并在删除基因索引时更新它们。

    请注意,这会为以下测试用例生成索引为 [2,1] 的基因:

    align = {"seq1":"ATGCATGC",
             "seq2":"AT----GC",
             "seq3":"A--CA--C"}
    annos = {"seq1":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}, 
             "seq2":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}, 
             "seq3":{"gene1":[0,2], "gene2":[3,4], "gene3":[5,7]}}
    

    这是必要的,因为您的索引设置方式不允许空基因。例如,索引[2,2] 将是从索引 2 开始的长度为 1 的序列。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-02
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 2016-02-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多