python - 寻找不匹配模式的效率

标签 python python-3.x algorithm performance bioinformatics

我正在研究一个简单的生物信息学问题。我有一个可行的解决方案,但效率低得离谱。我怎样才能提高我的效率?


问题:

在字符串 g 中查找长度为 k 的模式,给定 k-mer 最多可以有 d 个不匹配。

这些字符串和模式都是基因组的——所以我们的可能字符集是{A, T, C, G}

我将调用函数 FrequentWordsMismatch(g, k, d)

所以,这里有一些有用的例子:

FrequentWordsMismatch('AAAAAAAAAA', 2, 1)['AA', 'CA', 'GA', 'TA', 'AC', 'AG', '在']

这里有一个更长的例子,如果你实现它并想测试:

FrequentWordsMisMatch('CACAGTAGGCGCCGGCACACACAGCCCCGGGCCCCGGGCCGCCCCGGGCCGGCGGCCGCCGGCGCCGGCACACCGGCACAGCCGTACCGGCACAGTAGTACCGGCCGGCCGGCACACCGGCACACCGGGTACACACCGGGGCGCACACACAGGCGGGCGCCGGGCCCCGGGCCGTACCGGGCCGCCGGCGGCCCACAGGCGCCGGCACAGTACCGGCACACACAGTAGCCCACACACAGGCGGGCGGTAGCCGGCGCACACACACACAGTAGGCGCACAGCCGCCCACACACACCGGCCGGCCGGCACAGGCGGGCGGGCGCACACACACCGGCACAGTAGTAGGCGGCCGGCGCACAGCC', 10, 2)['GCACACAGAC', 'GCGCACACAC']

使用我天真的解决方案,第二个示例很容易花费大约 60 秒,但第一个示例非常快。


简单的解决方案:

我的想法是,对于 g 中的每个 k 长度段,找到每个可能的“邻居”(例如,其他 k 长度段,最多 d 不匹配)并将这些邻居作为键添加到字典中。然后我计算这些邻居 kmers 中的每一个出现在字符串 g 中的次数,并将它们记录在字典中。

显然,这样做有点糟糕,因为邻居的数量随着 kd 的增加而疯狂地增加,并且必须扫描每个字符串这些邻居使得这个实现非常缓慢。但是,唉,这就是我寻求帮助的原因。

我会把我的代码放在下面。肯定有很多新手错误需要解包,所以感谢您的时间和关注。

def FrequentWordsMismatch(g, k, d):
    '''
    Finds the most frequent k-mer patterns in the string g, given that those 
    patterns can mismatch amongst themselves up to d times

    g (String): Collection of {A, T, C, G} characters
    k (int): Length of desired pattern
    d (int): Number of allowed mismatches
    '''
    counts = {}
    answer = []

    for i in range(len(g) - k + 1):
        kmer = g[i:i+k]
        for neighborkmer in Neighbors(kmer, d):
            counts[neighborkmer] = Count(neighborkmer, g, d)

    maxVal = max(counts.values())

    for key in counts.keys():
        if counts[key] == maxVal:
            answer.append(key)

    return(answer)


def Neighbors(pattern, d):
    '''
    Find all strings with at most d mismatches to the given pattern

    pattern (String): Original pattern of characters
    d (int): Number of allowed mismatches
    '''
    if d == 0:
        return [pattern]

    if len(pattern) == 1:
        return ['A', 'C', 'G', 'T']

    answer = []

    suffixNeighbors = Neighbors(pattern[1:], d)

    for text in suffixNeighbors:
        if HammingDistance(pattern[1:], text) < d:
            for n in ['A', 'C', 'G', 'T']:
                answer.append(n + text)
        else:
            answer.append(pattern[0] + text)

    return(answer)


def HammingDistance(p, q):
    '''
    Find the hamming distance between two strings

    p (String): String to be compared to q
    q (String): String to be compared to p
    '''
    ham = 0 + abs(len(p)-len(q))

    for i in range(min(len(p), len(q))):
        if p[i] != q[i]:
            ham += 1

    return(ham)


def Count(pattern, g, d):
    '''
    Count the number of times that the pattern occurs in the string g, 
    allowing for up to d mismatches

    pattern (String): Pattern of characters
    g (String): String in which we're looking for pattern
    d (int): Number of allowed mismatches
    '''
    return len(MatchWithMismatch(pattern, g, d))

def MatchWithMismatch(pattern, g, d):
    '''
    Find the indicies at which the pattern occurs in the string g, 
    allowing for up to d mismatches

    pattern (String): Pattern of characters
    g (String): String in which we're looking for pattern
    d (int): Number of allowed mismatches
    '''
    answer = []
    for i in range(len(g) - len(pattern) + 1):
        if(HammingDistance(g[i:i+len(pattern)], pattern) <= d):
            answer.append(i)
    return(answer)

更多测试

FrequentWordsMismatch('ACGTTGCATGTCGCATGATGCATGAGAGCT', 4, 1) → ['ATGC', 'ATGT', 'GATG']

FrequentWordsMismatch('AGTCAGTC', 4, 2) → ['TCTC', 'CGGC', 'AAGC', 'TGTG', 'GGCC', 'AGGT', 'ATCC', 'ACTG', 'ACAC', 'AGAG', 'ATTA', 'TGAC', 'AATT', 'CGTT', 'GTTC', 'GGTA', 'AGCA', 'CATC']

FrequentWordsMismatch('AATTAATTGGTAGGTAGGTA', 4, 0) → ["GGTA"]

FrequentWordsMismatch('ATA', 3, 1) → ['GTA', 'ACA', 'AAA', 'ATC', 'ATA', 'AGA', 'ATT', 'CTA', 'TTA', 'ATG']

FrequentWordsMismatch('AAT', 3, 0) → ['AAT']

FrequentWordsMismatch('TAGCG', 2, 1)  → ['GG', 'TG']

最佳答案

问题描述在几个方面是模棱两可的,所以我将通过示例进行说明。你似乎想要所有k -来自字母表的长度字符串 (A, C, G, T}这样与 g 的连续子串的匹配数是最大的 - 其中“匹配”表示逐个字符最多等于 d字符不等式。

我忽略了你的 HammingDistance()即使输入具有不同的长度,函数也会产生一些东西,主要是因为它对我来说没有多大意义;-),但部分是因为在你给出的任何例子中不需要它来获得你想要的结果。

以下代码在所有示例中生成您想要的结果,即生成您提供的输出列表的排列。如果您想要规范的输出,我建议在返回之前对输出列表进行排序。

该算法非常简单,但依赖于 itertools “以 C 速度”进行重型组合提升。所有示例的运行速度都低于第二个总数。

对于每个长度- k g 的连续子串, 考虑所有 combinations(k, d)d不同的索引位置。有4**d{A, C, G, T} 中的字母填充这些索引位置的方法,并且每一种这样的方式都是“一个模式”,最多匹配子字符串 d差异。通过记住已经生成的模式来剔除重复项;这比一开始就只生成独特的模式做出巨大的努力要快。

所以,总的来说,时间要求是O(len(g) * k**d * 4**d) = O(len(g) * (4*k)**d , 其中k**d是,对于 k 的相当小的值和 d ,二项式系数的夸大代表 combinations(k, d) .需要注意的重要一点是 - 毫不奇怪 - 它在 d 中呈指数增长。 .

def fwm(g, k, d):
    from itertools import product, combinations
    from collections import defaultdict

    all_subs = list(product("ACGT", repeat=d))
    all_ixs = list(combinations(range(k), d))
    patcount = defaultdict(int)

    for starti in range(len(g)):
        base = g[starti : starti + k]
        if len(base) < k:
            break
        patcount[base] += 1
        seen = set([base])
        basea = list(base)
        for ixs in all_ixs:
            saved = [basea[i] for i in ixs]
            for newchars in all_subs:
                for i, newchar in zip(ixs, newchars):
                    basea[i] = newchar
                candidate = "".join(basea)
                if candidate not in seen:
                    seen.add(candidate)
                    patcount[candidate] += 1
            for i, ch in zip(ixs, saved):
                basea[i] = ch

    maxcount = max(patcount.values())
    return [p for p, c in patcount.items() if c == maxcount]

编辑:唯一地生成模式

与其保留一组到目前为止看到的重复项来剔除重复项,不如直接从一开始就防止生成重复项。事实上,下面的代码更短更简单,尽管有些微妙。作为减少冗余工作的返回,有对 inner() 的递归调用层。功能。哪种方式更快似乎取决于具体的输入。

def fwm(g, k, d):
    from collections import defaultdict

    patcount = defaultdict(int)
    alphabet = "ACGT"
    allbut = {ch: tuple(c for c in alphabet if c != ch)
              for ch in alphabet}

    def inner(i, rd):
        if not rd or i == k:
            patcount["".join(base)] += 1
            return
        inner(i+1, rd)
        orig = base[i]
        for base[i] in allbut[orig]:
            inner(i+1, rd-1)
        base[i] = orig

    for i in range(len(g) - k + 1):
        base = list(g[i : i + k])
        inner(0, d)

    maxcount = max(patcount.values())
    return [p for p, c in patcount.items() if c == maxcount]

关于python - 寻找不匹配模式的效率,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50890336/

相关文章:

Python 编程 : add years to current time

python - 如何替换 Pandas 数据框中拼写错误的单词

python - 名称错误 : global name 'myExample2' is not defined # modules

algorithm - 给定节点对的最低公共(public)祖先

sql - 如何根据兴趣找到相似用户

algorithm - Knuth随机播放的变体

python - 如何从 txt 文件中的拆分行制作嵌套列表?

python - 将值列表映射到 float 时 mypy 出错

python - 无法访问传递给 NASM 64 位 DLL 的数组指针

python - 拆分 pandas 数据框行时出现问题?