我有一个相对简单的问题:在基因组中给出一个位置,在那时返回基因的名称。
我现在解决这个问题的方法是在cython ::
中使用以下类class BedFile():
""" A Lookup Object """
def __init__(self, bedfile):
self.dict = {}
cdef int start, end
with open(bedfile) as infile:
for line in infile:
f = line.rstrip().split('\t')
if len(f) < 4:
continue
chr = f[0]
start = int(f[1])
end = int(f[2])
gene = f[3]
if chr not in self.dict:
self.dict[chr] = {}
self.dict[chr][gene] = (start, end)
def lookup(self, chromosome, location):
""" Lookup your gene. Returns the gene name """
cdef int l
l = int(location)
answer = ''
for k, v in self.dict[chromosome].items():
if v[0] < l < v[1]:
answer = k
break
if answer:
return answer
else:
return None
完整的项目在这里:https://github.com/MikeDacre/python_bed_lookup,尽管整个相关课程都在上面。
代码的问题在于,生成的类/字典占用了人类基因组的大量内存,拥有1.1亿个基因(这是一个1.1亿行长的文本文件)。在大约两分钟后,当它达到16GB的内存时,我在构建字典的过程中杀死了 init 函数。任何使用那么多记忆的东西都是无用的。
我相信我必须有一个更有效的方法,但我不是C程序员,我对cython很新。我的猜测是我可以建立一种纯粹的C结构来保存基因名称以及起始值和结束值。然后我可以将lookup()转换为调用另一个名为_lookup()的cdef函数的函数,并使用该cdef函数来执行该实际查询。
在一个理想的世界中,整个结构可以存在于内存中,占用的内存少于2GB,大约2,000,000个条目(每个条目有两个整数和一个字符串)。
编辑: 我想出了如何使用sqlite高效地执行此操作,使用sqlite查看完整代码,请参阅此处:https://github.com/MikeDacre/python_bed_lookup
但是,我仍然认为上面的类可以用cython进行优化,使字典内存更小,查找更快,任何建议都值得赞赏。
谢谢!
答案 0 :(得分:4)
在评论中稍微扩展我的建议,而不是将(start,end)
存储为元组,将其存储为一个简单的Cython定义的类:
cdef class StartEnd:
cdef public int start, end
def __init__(self, start, end):
self.start = start
self.end = end
(你也可以玩更改整数类型以节省更多的尺寸)。我不建议删除Python词典,因为它们易于使用,并且(我相信)已经过优化,对于字符串键的(通常在Python中)来说是合理有效的。
我们可以使用sys.getsizeof
来估算粗略的尺寸节省。 (请注意,这对内置类和Cython类很有效,但对Python类不太好,所以不要太信任它。还要注意结果是依赖于平台的,所以你的可能会有所不同。)< / p>
>>> sys.getsizeof((1,2)) # tuple
64
>>> sys.getsizeof(1) # Python int
28
(因此每个元组包含64 + 28 + 28 = 120字节)
>>> sys.getsizeof(StartEnd(1,2)) # my custom class
24
(24是有道理的:它是PyObject_Head
(16个字节:64位整数和指针)+ 2个32位整数)。
因此,小5倍,这是一个良好的开端,我认为。
答案 1 :(得分:2)
在我使用amend
和cython
的经验有限的情况下,将numpy
用于“内部”是最有利可图的。不需要使用Python / numpy代码的计算。它们是迭代,可以转换为紧凑和快速的C代码。
这里重写了你的代码,拆分两个类可以重新编写为Cython / C结构:
cython
这些将由高级Python函数(或类)导入和使用,不需要在Cython .pdx文件中:
# cython candidate, like DavidW's StartEnd
class Gene(object):
def __init__(self, values):
self.chr = values[0]
self.start = int(values[1])
self.end = int(values[2])
self.gene = values[3]
def find(self, i):
return self.start<=i<self.end
def __repr__(self):
return "%s(%s, %d:%d)"%(self.chr,self.gene,self.start,self.end)
# cython candidate
class Chrom(list):
def add(self, aGene):
self.append(aGene)
def find(self, loc):
# find - analogous to string find?
i = int(loc)
for gene in self:
if gene.find(i):
return gene # gene.gene
return None
def __repr__(self):
astr = []
for gene in self:
astr += [repr(gene)]
return '; '.join(astr)
与文件或文本模拟一起使用:
from collections import defaultdict
def load(anIterable):
data = defaultdict(Chrom)
for line in anIterable:
f = line.rstrip().split(',')
if len(f)<4:
continue
aGene = Gene(f)
data[aGene.chr].add(aGene)
return data
我可以定义一个# befile = 'filename'
# with open(bedfile) as infile:
# data = load(infile)
txt = """\
A, 1,4,a
A, 4,8,b
B, 3,5,a
B, 5,10,c
"""
data = load(txt.splitlines())
print data
# defaultdict(<class '__main__.Chrom'>, {
# 'A': A(a, 1:4); A(b, 4:8),
# 'B': B(a, 3:5); B(c, 5:10)})
print 3, data['A'].find(3) # a gene
print 9, data['B'].find(9) # c gene
print 11,data['B'].find(11) # none
函数,该函数遵循一个方法(如果可用),否则使用它自己的函数。这类似于委托给方法的numpy函数:
find
使用普通列表数据结构测试def find(chrdata, loc):
# find - analogous to string find?
fn = getattr(chrdata, 'find',None)
if fn is None:
#raise AttributeError(chrdata,'does not have find method')
def fn(loc):
i = int(loc)
for gene in chrdata:
if gene.find(i):
return gene # gene.gene
return None
return fn(loc)
print 3, find(data['A'],3)
:
find