【问题标题】:frequency of letters in column python - speed optimizationpython列中字母的频率 - 速度优化
【发布时间】:2025-12-19 21:15:12
【问题描述】:

我想以类似于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

(熊猫在那里,因为它是我的首选输出)。但是,由于我正在处理数十万甚至数百万个序列(长度为 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()) 的多处理,但似乎很容易实现。

【问题讨论】:

标签: python performance pandas


【解决方案1】:

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

【讨论】:

  • 哇!那是闪电般快速且非常优雅的语法。在一个真实的数据集中,它从需要几天(在 4 到 4 天)到几分钟。如果可以的话,我会给你 2 票。谢谢@DSM。
【解决方案2】:

所以我做了一些测试,这里有一种方法需要大约 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 这个,你总是可以把你的序列列表分成块,把它们分给不同的进程,然后在最后总结。这里也可能存在一些内存限制。

【讨论】:

    【解决方案3】:

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

    假设您有长度为numcolsnumseq 字符串,然后使用大小为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 上的比率为 1:4 @ 10**6 个序列。

    【讨论】: