python 列中字母的频率 - 速度优化

frequency of letters in column python - speed optimization

我想计算每个元素(一个字符)在每个位置出现的频率,其方式与 previous question 类似。这是我目前的解决方案:

import pandas as pd
sequences = ['AATC',
             'GCCT',
             'ATCA',
             'TGAG',
             'CGGA']
f = zip(*sequences)
counts = [{letter: column.count(letter) for letter in column} for column in f]
counts=pd.DataFrame(counts).transpose()
print counts
   0  1  2  3
A  2  1  1  2
C  1  1  2  1
G  1  2  1  1
T  1  1  1  1

(pandas 在那里是因为它是我的首选输出)。但是,由于我要处理数十万个甚至数百万个序列(长度为 10 个字符或更多),这有点慢:~100^3 个序列需要 20 分钟,而在我的真实数据集中需要几个小时。所以我想我可以通过求助于 pandas 来提高速度,因为我无论如何都要转换为数据帧:df = pd.DataFrame(f).transpose()

事实证明这个策略更慢:

解决方案 1

import time

start_time = time.time()
counts = [{letter: column.count(letter) for letter in column} for column in f]
counts=pd.DataFrame(counts).transpose()
print(counts)
print("--- %s seconds ---" % (time.time() - start_time))
--- 0.00820517539978 seconds ---

解决方案 2

start_time = time.time()
df = pd.DataFrame(f).transpose()
print df.apply(lambda col: col.value_counts())
print("--- %s seconds ---" % (time.time() - start_time))
--- 0.0104739665985 seconds ---

所以问题是:有没有办法优化这个?我研究了 df.apply(lambda col: col.value_counts()) 的多重处理,但似乎很容易实现。

所以我做了一些测试,这里有一个方法大约需要 40% 的时间:

def count_test():  # what you do
    f = zip(*sequences)
    counts = [{letter: column.count(letter) for letter in column} for column in f]
    counts=pd.DataFrame(counts).transpose()
    return counts


def new_way():
    df = pd.DataFrame(map(list, sequences))
    res = {}
    for c in df.columns:
        res[c] = df[c].value_counts()
    return pd.DataFrame(res)

如果你想 multiprocess 这个,你总是可以将你的序列列表分成块,将它们分配给不同的进程,然后在最后总结。这里也可能有一些内存限制。

column.count(letter) for letter in column 会很慢,因为它重复了很多很多次相同的计算; pandas 最适用于多行少列的情况。因此,如果您以这种格式保存数据,应该会很快。这是一个包含 10^6 行的示例:

>>> seqs = [''.join([random.choice("ACGT") for i in range(10)]) for j in range(10**6)]
>>> seqs[:5]
['CTTAAGCGAA', 'TATAGGATTT', 'AAACGGTGAG', 'AGTAGGCTAC', 'CTGTTCTGCG']
>>> df = pd.DataFrame([list(s) for s in seqs])
>>> df.head()
   0  1  2  3  4  5  6  7  8  9
0  C  T  T  A  A  G  C  G  A  A
1  T  A  T  A  G  G  A  T  T  T
2  A  A  A  C  G  G  T  G  A  G
3  A  G  T  A  G  G  C  T  A  C
4  C  T  G  T  T  C  T  G  C  G
>>> %time z = df.apply(pd.value_counts)
CPU times: user 286 ms, sys: 0 ns, total: 286 ms
Wall time: 285 ms
>>> z
        0       1       2       3       4       5       6       7       8       9
A  249910  250452  249971  250136  250048  250025  249763  249787  250498  251008
C  249437  249556  250270  249884  250245  249975  249888  250432  249867  249516
G  250740  250277  250414  249847  250080  249447  249901  249638  250010  249480
T  249913  249715  249345  250133  249627  250553  250448  250143  249625  249996

由于输入是逐行给出的,我认为不转置可能很自然并且可以节省时间。其次,我会将数据类型保留为字符串,然后才将结果转换为 Pandas 对象。

假设您有 numseq 个长度为 numcols 的字符串,然后可以使用大小为 numcols 的切片访问列中的元素。像这样(我在这里重复使用 DSM 中的序列创建代码):

numseq = 1*10**6      # number of sequences
numcols = 10          # length of single code sequence
letters = ['A','C','G','T']
# create input sequences
sequences = [''.join([random.choice("ACGT") for i in range(numcols)]) for j in range(numseq)]
counts = [[] * len(letters) for j in range(numcols)]

T2 = ''.join(sequences)
for i in range(numcols):
    counts[i] = [T2[i::numcols].count(letter) for letter in letters]

我将运行时与在转置字符串(不是 Pandas 对象)上连续计数的原始方法进行了比较,并注意到我的 PC @ 10**6 序列的比率为 1:4。