Замена вложенных циклов 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]