Ускорьте перебор символов в длинных строках

#python #pandas #string #lambda #nested-loops

Вопрос:

У меня есть код, который принимает строку ДНК, в которой найдено только 4 символа: A, C, T и G, например «ATACAAG», и для каждого символа, если найдено 3 других возможных символа. Код включает в себя цикл для строки и еще один цикл для списка возможных символов. Проблема в том, что строки очень длинные: до сотен тысяч символов, поэтому это не быстро, и компьютер нагревается (его вентиляторы начинают работать быстро).

Я ищу более быстрый способ сделать это. Я пытался понять список, но это все еще довольно медленно. Я также попытался вызвать код как функцию из лямбды панд, и это все равно занимает около минуты для каждой строки. Это лучшее, что я могу получить?

Для каждого символа код записывает 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 -  

Спасибо.

Комментарии:

1. пожалуйста, приведите пример вашего входного файла/фрейма данных

2. У меня есть. Вопрос включает в себя pandas.Series объект, который является примером для строки в фрейме данных.

3. Если вы хотите повторить каждый символ в строке, то нет, более быстрого метода не существует. Понимание списка только уменьшает количество кодов, но не ускоряет процесс.

Ответ №1:

Вот как бы я это сделал.

Начиная с фрейма данных:

 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'])  )  

Промежуточный выход:

 gt;gt;gt; 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)  

Комментарии:

1. Спасибо. Является ли это хорошим подходом, учитывая, что последовательности состоят из сотен тысяч символов? Я попытался использовать ваш код и получил фрейм данных примерно в 3,3 миллиона строк. В будущем он будет работать на сервере с большим количеством памяти и ядер, так что все может быть в порядке.

2. Ну, если у вас миллионы строк, это займет время, несмотря ни на что. Хорошая вещь здесь в том, что ваши строки, вероятно, независимы, поэтому это идеальный случай для разделения входных данных на более мелкие фреймы данных, применения к разным потокам/ядрам и объединения выходных данных 😉 Дай мне знать, как все пройдет!

3. Кроме того, вывод включает хромосому в первом столбце, поэтому первая строка кода будет df2 = (df.drop(columns=['symbol']) , пятая строка будет df2 = (df2.reindex(df2.index.repeat(4)) и последняя строка кода df2[['chromosome', 'start', 'end', 'var', 'strand']] ?

4. Кажется правильным, попробуйте на тестовом образце 😉

5. Я хочу сохранить каждое имя символа / гена в отдельном файле, поэтому я разделил фрейм данных на несколько фреймов данных и использовал предложенный вами код для каждого из них отдельно. Вы бы сделали это по-другому?