Замена вложенных циклов for и присвоение значений для понимания списка
Я написал функцию для подсчета вхождений определенного символа (A
, C
, G
и T
) в нескольких строках в одной позиции и сохранить количество вхождений в словаре.
например, с этими двумя строками "ACGG" и "CAGT" он должен вернуться:
{'A': [1, 1, 0, 0], 'C': [1, 1, 0, 0], 'G': [0, 0, 2, 1], 'T': [0, 0, 0, 1]}
Я хочу преобразовать код ниже, чтобы перечислить понимание, чтобы оптимизировать его для скорости. Он использует два вложенных цикла for, а входные мотивы-это список строк содержащий цэ а.
def CountWithPseudocounts(Motifs):
count = {}
k = len(Motifs[0])
t = len(Motifs)
for s in 'ACGT':
count[s] = [0] * k
for i in range(t):
for j in range(k):
symbol = Motifs[i][j]
count[symbol][j] += 1
return count
Я попытался заменить вложенные циклы for В нижней части функции для понимания этого списка:
count = [ [ count[Motifs[i][j]][j] += 1 ] for i in range(0, t) ] for j in range(0, k)]
это не работает, вероятно, потому, что мне не разрешено выполнять присвоение значения += 1 в пределах понимания списка. Как я могу обойти это?
4 ответов
вы действительно не можете выполнять задания в понимании списка (ну, вы можете - вызывая функции - выполнять побочные эффекты). Понимание списка требует выражения. Кроме того странно, что вы хотите назначить count
и в то же время обновить старую count
.
способ сделать это с помощью понимание словарь и понимание это не очень эффективно:
chars = 'ACGT'
a = 'ACGG'
b = 'CAGT'
sequences = list(zip(a,b))
counts = {char:[seq.count(char) for seq in sequences] for char in chars}
(кредиты @Chris_Rands на seq.count(char)
предложение)
это производит:
{'G': [0, 0, 2, 1], 'A': [1, 1, 0, 0], 'C': [1, 1, 0, 0], 'T': [0, 0, 0, 1]}
вы можете легко обобщить решение, чтобы подсчитать больше строк, вызвав zip(..)
С большим количеством строк.
вы также можете решить оптимизировать сам алгоритм. Это, вероятно, будет более эффективным, так как вам нужно только один раз перебирать строки, и вы можете использовать поиск словаря, например:
def CountWithPseudocounts(sequences):
k = len(sequences[0])
count = {char:[0]*k for char in 'ACGT'}
for sequence in sequences:
j = 0
for symbol in sequence:
count[symbol][j] += 1
j += 1
return count
редактировать:
если вы хотите добавить один ко всем элементам в счетчиках, вы можете использовать:
counts = {char:[seq.count(char)+1 for seq in sequences] for char in chars}
можно использовать zip()
:
In [10]: a = 'ACGG'
In [11]: b = 'CAGT'
In [12]: chars = ['A', 'C', 'G', 'T']
In [13]: [[(ch==i) + (ch==j) for i, j in zip(a, b)] for ch in chars]
Out[13]: [[1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 2, 1], [0, 0, 0, 1]]
если вы хотите словарь вы можете использовать понимание дикт:
In [25]: {ch:[(ch==i) + (ch==j) for i, j in zip(a, b)] for ch in chars}
Out[25]: {'T': [0, 0, 0, 1], 'G': [0, 0, 2, 1], 'C': [1, 1, 0, 0], 'A': [1, 1, 0, 0]}
или если вы хотите результат в том же порядке, что и ваш список символов, вы можете использовать collections.OrderedDict
:
In [26]: from collections import OrderedDict
In [27]: OrderedDict((ch, [(ch==i) + (ch==j) for i, j in zip(a, b)]) for ch in chars)
Out[28]: OrderedDict([('A', [1, 1, 0, 0]), ('C', [1, 1, 0, 0]), ('G', [0, 0, 2, 1]), ('T', [0, 0, 0, 1])])
Если вам все еще нужно больше производительности и/или вы имеете дело с длинными строками и большими наборами данных вы можете использовать numpy для обойти эту проблему, хотя метод векторизации.
In [61]: pairs = np.array((list(a), list(b))).T
In [62]: chars
Out[62]:
array(['A', 'C', 'G', 'T'],
dtype='<U1')
In [63]: (chars[:,None,None] == pairs).sum(2)
Out[63]:
array([[1, 1, 0, 0],
[1, 1, 0, 0],
[0, 0, 2, 1],
[0, 0, 0, 1]])
я предлагаю подход включает также Kasramvs если скорость действительно имеет значение.
кроме того, подсчет символов не дружелюбен даже к современным компьютерам, и, возможно, вы можете сыграть с некоторыми трюками об индексировании/хэшировании входных строк перед подсчетом. Например, поскольку каждая строка имеет только 4 символа, и каждый символ содержит только 4 возможных буквы: "A", "C", "G", "T", поэтому он может легко представлять каждую из всех комбинаций "ACGT" от "AAAA" до "TTTT" с номером, хэшем или таинственным кодом. Объем комбинаций должен быть равен или меньше 4x4x4x4=256 различных чисел здесь.
а затем посчитайте код вместо этого. Например, каждый раз, когда вы видите "AAAA", а затем считаете его как 0x0 в списке python или массиве numpy, см. "AAAC" и считайте как 0x1, и наоборот. После этого вы получите массив binning с индексами в диапазоне от 0x0 ~ 0xFF(255) и с соответствующими вхождениями, верно? Сейчас помните, что один 0x0 означает:{1, 1, 1, 1} в вашем случае или семь 0x1 для A:{7, 7, 7, 0} вместе с C:{0, 0, 0, 7}... Суммируйте их все тщательно, и это результат.
такие трюки, возможно, помогут повысить производительность скорости довольно много в этом случае. Преимущества скорости в двух факторах: первый заключается в том, что теперь компьютеры имеют дело с числами вместо символов, в то время как числа намного проще сортировать, считать, группировать, разбивать или индексировать, чем символы; второй приходит от гораздо более высокой скорости попадания кэша по природе, так как трассировка памяти сильно уменьшается в этих трюках.
надеюсь, это поможет. :)
Ну, добавьте некоторые коды следующим образом, чтобы быть четкой ссылкой.
прежде всего, импорт:
import itertools
import numpy as np
и ниже должно быть достаточно быстро при условии dict t02
не слишком большой, скажем, обычно меньше 1M пар ключ-значение:
def encode_sequence(tsq):
t00 = itertools.product('ACGT', repeat=4)
t01 = np.array(list(t00)).view('|U4').ravel()
t02 = dict(zip(t01, np.arange(len(t01))))
t03 = [t02[i] for i in tsq]
return t03
и используйте следующий фрагмент чтобы создать тензор,map_decode
, чтобы представить материалы о "подсчете кода"... Кроме того, есть математический трюк под названием augmented matrix transform под этой частью, говоря, что он преобразует ll0
и 'ACGT'
таким же образом, чтобы охватить map_decode
для последующего использования.
ll0 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
map_decode = np.array(list(itertools.product(ll0, repeat=4)))
подача тестовой последовательности и перевод,
test_seq = ('ACGG', 'CAGT', 'CCGA', 'AAAT', 'TTGC', 'GCAT', 'ACGG', 'AAAT')
u01 = encode_sequence(test_seq)
подсчет числа; помните, что блок ниже должен быть основным источником увеличения скорости, потому что компьютер хорошо справляется числа в u01
,
p01, q01 = np.unique(u01, return_counts=True)
в конце концов, генерировать выходной... Это немного сложно здесь, например p01
- это отсортированный хэш-код of test_seq
и q01
действительно соответствующие подсчеты в то время как map_decode
служит тем, что я сказал, тензор для отображения хэш-кода в p01
к другому вектору, который мы хотим, например, отображение 0x0 (или 'AAAA') в A:[1, 1, 1, 1], C:[0, 0, 0, 0], G:[0, 0, 0, 0] и Т:[0, 0, 0, 0]. The mapped map_decode[p01]
таким образом взвешивается по подсчетам q01
и готовы подвести итоги для доклада:
np.sum(map_decode[p01]*q01[:, None, None], axis=0).T
и он говорит:
array([[4, 3, 3, 1],
[2, 4, 0, 1],
[1, 0, 5, 2],
[1, 1, 0, 4]])
что эквивалентно:{4, 3, 3, 1}, C:{2, 4, 0, 1}, G:{1, 0, 5, 2} и Т:{1, 1, 0, 4}. Проверьте, соответствует ли он ответу.
это реализация numpy; она не содержит явного цикла в основном теле. И более того, encode_sequence()
может быть заменен некоторыми автономными приготовлениями входов для повышения производительности заранее. Хотя у меня не было измерения скорости из приведенных выше фрагментов я думаю, что они должны быть ускорены в определенной степени. :)
Хорошо, давайте обсудим, что произойдет, если будут длинные строки.
мы используем эту последовательность, например,
test_seq0 = ((
'A'*40, 'A'*40, 'A'*40, 'C'*40, 'C'*40,
'C'*40, 'C'*40, 'G'*40, 'G'*40, 'T'*40
))*4
на test_seq0
содержит 40 строк и каждая строка имеет 40 символов. Это выглядит так,
In: len(test_seq0), test_seq0
Out: (40,
('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
... skip 30 lines ...
'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG',
'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG',
'TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT'))
довольно забавный вид, верно?
тогда мы должны переоборудовать encode_sequence()
для длинной строки версии
def encode_sequence_longstring(tsq_np):
t00 = itertools.product('ACGT', repeat=4)
t01 = np.array(list(t00)).view('|U4').ravel()
t02 = dict(zip(t01, np.arange(len(t01))))
t03 = np.empty_like(tsq_np, dtype=np.uint)
t03.ravel()[:] = [t02[i] for i in tsq_np.ravel()]
return t03
будьте осторожны, что tsq_np
здесь больше нет простого списка строк. Постфикс _np
означает, что теперь это массив numpy.
и разделить исходный test_seq0
в сторону библиотеки numpy,
In: v01 = np.asarray(test_seq0).view('|U4').reshape(-1, int(40/4))
In: v01
Out:
array([['AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA'],
['AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA'],
['AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA', 'AAAA'],
['CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC'],
['CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC'],
... skip 30 lines ...
['CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC'],
['CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC', 'CCCC'],
['GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG'],
['GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG', 'GGGG'],
['TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT', 'TTTT']],
dtype='<U4')
еще один забавный вид в v01
. :)
и использовать v01
для вычисления хэш-кодов u02
такой. Он включает в себя некоторые соглашения numpy вокруг этих переменных и функций. Просто привыкай к этим трюкам; они стоит того,
In: u02 = encode_sequence_longstring(v01)
In: u02
Out:
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],
[ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],
... skip 30 lines ...
[ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],
[ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],
[170, 170, 170, 170, 170, 170, 170, 170, 170, 170],
[170, 170, 170, 170, 170, 170, 170, 170, 170, 170],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255]],
dtype=uint64)
по наблюдению, вы можете сказать u02
на самом деле отображение 1-к-1 v01
. Он просто сопоставляет каждый "AAAA" с 0x0, как ожидалось.
отныне карта u02
содержит всю необходимую информацию относительно test_seq0
. Извлеките его из u02
С помощью numpy,
s01 = np.empty((4, 0))
for u03 in u02.T:
p02, q02 = np.unique(u03, return_counts=True)
s02 = np.sum(map_decode[p02]*q02[:, None, None], axis=0).T
s01 = np.hstack((s01, s02))
есть правило большого пальца для собственного цикла python: вы можете использовать его, но использовать его вне любой чувствительной к производительности области. Но, это нужен опыт, чтобы оценить ситуацию.
теперь s01
ожидаемый отчет следующим образом:
In: s01
Out:
array([[ 12., 12., 12., 12., 12., 12., 12., 12., 12., 12., 12.,
12., 12., 12., 12., 12., 12., 12., 12., 12., 12., 12.,
12., 12., 12., 12., 12., 12., 12., 12., 12., 12., 12.,
12., 12., 12., 12., 12., 12., 12.],
[ 16., 16., 16., 16., 16., 16., 16., 16., 16., 16., 16.,
16., 16., 16., 16., 16., 16., 16., 16., 16., 16., 16.,
16., 16., 16., 16., 16., 16., 16., 16., 16., 16., 16.,
16., 16., 16., 16., 16., 16., 16.],
[ 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8.,
8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8.,
8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8.,
8., 8., 8., 8., 8., 8., 8.],
[ 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,
4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,
4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,
4., 4., 4., 4., 4., 4., 4.]])
прочитайте 4 строки сверху вниз, и они будут "A", "C", "G", "T", соответственно.
в то же время, я пробовал 40x10x4000 test_seq0
как это
test_seq0 = ((
'A'*40, 'A'*40, 'A'*40, 'C'*40, 'C'*40,
'C'*40, 'C'*40, 'G'*40, 'G'*40, 'T'*40
))*4000
в докладе говорится,
array([[ 12000., 12000., 12000., 12000., 12000., 12000., 12000.,
12000., 12000., 12000., 12000., 12000., 12000., 12000.,
12000., 12000., 12000., 12000., 12000., 12000., 12000.,
12000., 12000., 12000., 12000., 12000., 12000., 12000.,
12000., 12000., 12000., 12000., 12000., 12000., 12000.,
12000., 12000., 12000., 12000., 12000.],
[ 16000., 16000., 16000., 16000., 16000., 16000., 16000.,
16000., 16000., 16000., 16000., 16000., 16000., 16000.,
16000., 16000., 16000., 16000., 16000., 16000., 16000.,
16000., 16000., 16000., 16000., 16000., 16000., 16000.,
16000., 16000., 16000., 16000., 16000., 16000., 16000.,
16000., 16000., 16000., 16000., 16000.],
[ 8000., 8000., 8000., 8000., 8000., 8000., 8000.,
8000., 8000., 8000., 8000., 8000., 8000., 8000.,
8000., 8000., 8000., 8000., 8000., 8000., 8000.,
8000., 8000., 8000., 8000., 8000., 8000., 8000.,
8000., 8000., 8000., 8000., 8000., 8000., 8000.,
8000., 8000., 8000., 8000., 8000.],
[ 4000., 4000., 4000., 4000., 4000., 4000., 4000.,
4000., 4000., 4000., 4000., 4000., 4000., 4000.,
4000., 4000., 4000., 4000., 4000., 4000., 4000.,
4000., 4000., 4000., 4000., 4000., 4000., 4000.,
4000., 4000., 4000., 4000., 4000., 4000., 4000.,
4000., 4000., 4000., 4000., 4000.]])
и он завершен менее чем за 1 секунду (нажмите ENTER, и все готово) на моем MacBook Pro, который не является чудовище. :)
сборники.Счетчик ваш друг:)
s1 = 'ACGG'
s2 = 'CAGT'
from collections import Counter
counter = Counter(enumerate(s1))
counter += Counter(enumerate(s2))
формат вывода: ((позиция, символ), количество вхождений)
sorted(counter.items())
[((0, 'A'), 1),
((0, 'C'), 1),
((1, 'A'), 1),
((1, 'C'), 1),
((2, 'G'), 2),
((3, 'G'), 1),
((3, 'T'), 1)]
[counter[i,'A'] if (i,'A') in counter else 0 for i in range(4)]
[1, 1, 0, 0]
[counter[i,'C'] if (i,'C') in counter else 0 for i in range(4)]
[1, 1, 0, 0]
[counter[i,'G'] if (i,'G') in counter else 0 for i in range(4)]
[0, 0, 2, 1]
[counter[i,'T'] if (i,'T') in counter else 0 for i in range(4)]
[0, 0, 0, 1]