加快迭代长字符串中的字符

Speed up iterating over characters in long strings

我有一个采用 DNA 字符串的代码,其中仅找到 4 个字符:A、C、T 和 G,例如“ATACAAG”,并且对于每个字符,如果找到其他 3 个可能的字符。该代码包括一个字符串循环和另一个可能字符列表循环。问题是字符串很长:多达数十万个字符,所以速度不快并且计算机变热(风扇开始快速运转)。

我正在寻找一种更快的方法。我尝试了列表理解,但它仍然很慢。我还尝试将代码作为 pandas lambda 的函数调用,但每个字符串仍然需要大约一分钟的时间。这是我能得到的最好的吗?

对于每个字符,代码在文件的不同行中记录 3 个备选方案。

代码:

bases = set(list('ACGT'))
alts = {base: list(bases.difference(base)) for base in bases}

def get_variants(data, output_path):    # pb: position base, b: base
    [open(output_path + f'/{data.symbol}_variants.txt', 'a').writelines(
        [f'{data.chromosome}\t{data.end + index}\t{data.end + index}\t{pb}/{b}\t{data.strand}\n' for b in alts[pb]])
        for index, pb in enumerate(data.sequence)]

正在为“ATACAAG”调用函数:

get_variants(pandas.Series({'symbol': 'XYZ', 'sequence': 'ATACAAG', 'chromosome': 12, 'start': 9067664, 'end': 9067671, 'strand': '-'}),
             'write_an_existing_output_directory_path_here')

输出按以下列排列在文件中:

chromosome number, start position, end position, original character/alternative character, strand (can + or -)

它在文件 XYZ_variants.txt 中产生以下行:

12  9067664 9067664 A/T -
12  9067664 9067664 A/G -
12  9067664 9067664 A/C -
12  9067665 9067665 T/A -
12  9067665 9067665 T/G -
12  9067665 9067665 T/C -
12  9067666 9067666 A/T -
12  9067666 9067666 A/G -
12  9067666 9067666 A/C -
12  9067667 9067667 C/T -
12  9067667 9067667 C/A -
12  9067667 9067667 C/G -
12  9067668 9067668 A/T -
12  9067668 9067668 A/G -
12  9067668 9067668 A/C -
12  9067669 9067669 A/T -
12  9067669 9067669 A/G -
12  9067669 9067669 A/C -
12  9067670 9067670 G/T -
12  9067670 9067670 G/A -
12  9067670 9067670 G/C -

谢谢。

这是我的做法。

从数据帧开始:

  symbol sequence chromosome    start      end strand
0    XYZ  ATACAAG         12  9067664  9067671      -

我会 explode 序列 reindex 具有 A/C/G/T 的所有组合,并且只保留初始碱基不同的那个

import numpy as np

df2 = df.assign(base=df['sequence'].apply(list)).explode('base').reset_index()
df2 = (df2.reindex(df2.index.repeat(4))
          .assign(variant=np.tile(list('ACGT'), len(df2)))
          .loc[lambda d: d['base'].ne(d['variant'])]
          .assign(var=lambda d:d['base']+'/'+d['variant'])
       )

中间输出:

>>> df2.head()
   index symbol sequence chromosome    start      end strand base variant  var
0      0    XYZ  ATACAAG         12  9067664  9067671      -    A       C  A/C
0      0    XYZ  ATACAAG         12  9067664  9067671      -    A       G  A/G
0      0    XYZ  ATACAAG         12  9067664  9067671      -    A       T  A/T
1      0    XYZ  ATACAAG         12  9067664  9067671      -    T       A  T/A
1      0    XYZ  ATACAAG         12  9067664  9067671      -    T       C  T/C

然后导出:

df2[['start', 'end', 'var', 'strand']].to_csv('variants.txt', sep='\t', index=False, header=None)

示例输出(第一行):

9067664 9067671 A/C -
9067664 9067671 A/G -
9067664 9067671 A/T -
9067664 9067671 T/A -
9067664 9067671 T/C -
9067664 9067671 T/G -
9067664 9067671 A/C -
9067664 9067671 A/G -
9067664 9067671 A/T -
9067664 9067671 C/A -

优化

现在我们删除不需要的所有内容以保持最小大小:

df2 = (df.drop(columns=['symbol', 'chromosome'])
         .assign(sequence=df['sequence'].apply(list))
         .explode('sequence').reset_index(drop=True)
      )
df2 = (df2.reindex(df2.index.repeat(4))
          .assign(var=np.tile(list('ACGT'), len(df2)))
          .loc[lambda d: d['sequence'].ne(d['var'])]
          .assign(var=lambda d:d['sequence']+'/'+d['var'])
       )
df2[['start', 'end', 'var', 'strand']].to_csv('variants.txt', sep='\t', index=False, header=None)