作为我自己对基因型网络的研究的一部分,我有一段代码,我试图从字符串列表构建一个单差异网络。程序如下:
我现在拥有的代码块是这样的:
from itertools import combinations
import Levenshtein as lev # a package that wraps a C-implemented levenshtein distance
import networkx as nx
strings = [...list of strings...]
G = nx.Graph()
for (s1, s2) in combinations(strings, 2):
if s1 not in G.nodes():
G.add_node(s1)
if s2 not in G.nodes():
G.add_node(s2)
if lev.distance(s1, s2) == 1:
G.add_edge(s1, s2)
显然没有办法提高图构建过程的计算复杂度 - 它总是O(n**2)
。至少,在我有限的知识中,这就是我的想法 - 也许我错了?
那说,考虑到我需要进行的比较次数的正常比例(〜约2000-5000),如果我可以获得几个数量级的加速,那么实际的计算时间将是仍然可以接受 - 使用当前的Python实现,构建图形需要大约几天。使用正确的导入(下面未说明),我在下面尝试了一个Cython实现,但无法弄清楚如何使其更快:
cpdef cython_genotype_network(sequences):
G = nx.Graph()
cdef:
unicode s1
unicode s2
for (s1, s2) in combinations(sequences, 2):
if lev.distance(s1, s2) == 1:
G.add_edge(s1, s2)
return G
特别是,Cython期望bytes
和str
s1
而不是s2
。该代码块会引发错误。
所以......我来了两个问题:
numpy
来解决此问题?从numpy
矩阵转换为NetworkX很容易;但是,我似乎无法弄清楚如何在n-by-n空矩阵中广播Levenshtein距离函数,其中每行和每列对应一个字符串。更新1:如何生成样本数据
生成字符串:
from random import choice
def create_random_nucleotides_python(num_nucleotides):
"""
Creates random nucleotides of length num_nucleotides.
"""
sequence = ''
letters = ['A', 'T', 'G', 'C']
for i in range(num_nucleotides):
sequence = sequence + choice(letters)
return sequence
def mutate_random_position(string):
"""
Mutates one position in the nucleotide sequence at random.
"""
positions = [i for i in range(len(string))]
pos_to_mut = choice(positions)
letters = ['A', 'T', 'G', 'C']
new_string = ''
for i, letter in enumerate(string):
if i == pos_to_mut:
new_string = new_string + choice(letters)
else:
new_string = new_string + letter
return new_string
# Create 100 Python srings by mutating a first sequence, then randomly choosing stuff to mutate a single position.
base_sequence = create_random_nucleotides_python(1000)
sequences = [base_sequence]
for i in range(99):
sequence = choice(sequences)
mutseq = mutate_random_position(sequence)
sequences.append(mutseq)
答案 0 :(得分:2)
关于复杂性:
您正在考虑每对琴弦。你不需要那个。您可以考虑每个字符串的所有1个距离字符串:
# I would start by populating the whole graph:
for s1 in strings:
if s1 not in G.nodes():
G.add_node(s1)
# Then compute the leven-1:
for s1 in strings:
for s2 in variations(s1):
if s2 in G.nodes():
G.add_edge(s1, s2)
现在您的所有需求都是variations(string)
短于O(n)
:
返回距离为1的所有变体(仅1次编辑|删除|插入)
def variations(string):
for i in range(len(string)):
# delete
yield string[:i] + string[i+1:]
# edit
for l in 'ATGC':
yield string[:i] + l + string[i+1:]
# insert
for l in 'ATGC':
yield string[:i] + l + string[i:]
# insert at the end
for l in 'ATGC':
yield string + l
现在,它的复杂性是O(m^2)
(因为字符串连接),其中m
是最长序列的大小。如果它已知,它是一个常数,现在只有O(1)
如果序列大小相同,则只能计算编辑。
否则,您可以将序列从最大到最小排序,并且只计算编辑和删除。
或者,您可以按大小对字符串进行排序,而不是将所有字符串与所有其他字符串进行比较,比较那些大小差异为< = 1的字符串。
答案 1 :(得分:0)
关于cython
代码如何更快的一些想法
for (s1, s2) in combinations(sequences, 2):
if lev.distance(s1, s2) == 1:
G.add_edge(s1, s2)
我认为itertools.combinations
是编译的,因此本身就是快速的。并且你调用它一次,所以for ...
部分可能跟它一样快。尽管如此,在sequences
中循环cython
并不难。
lev.distance
似乎使用已编译的代码。是否可以直接导入和调用该代码?查看lev
来源。
我认为lev.distance
最终会使用签名调用c函数:
distance_py(PyObject *self, PyObject *args)
或更基本
lev_edit_distance(size_t len1, const lev_byte *string1,
size_t len2, const lev_byte *string2,
int xcost)
https://github.com/ztane/python-Levenshtein/blob/master/Levenshtein/_levenshtein.c
G.add_edge
做什么?是否有更简单的数据结构,您可以收集这些“边缘”。也许作为元组列表?
networkx
是否提供了批量添加节点和边缘的方法?您是否总是必须使用add_node
和add_edge
?或者你可以给它一个列表节点,节点对(元组)列表?
看起来graph
是Python词典或词典词典。
networkx
允许您通过元组列表添加一堆边:
>>> G.add_edges_from([(1,2),(1,3)])
http://networkx.github.io/documentation/latest/tutorial/tutorial.html#edges
在python代码中,这篇文章可能是不必要的:
if s1 not in G.nodes():
G.add_node(s1)
if s2 not in G.nodes():
G.add_node(s2)
只是做:
G.add_nodes_from(strings)
http://networkx.github.io/documentation/latest/tutorial/tutorial.html#nodes