Я создал функцию mutate_v1, которая генерирует случайные мутации в последовательности ДНК.

def mutate_v1(sequence, mutation_rate):
    dna_list = list(sequence)
    for i in range(len(sequence)):
        r = random.random()
        if r < mutation_rate:
            mutation_site = random.randint(0, len(dna_list) - 1)
            dna_list[mutation_site] = random.choice(list('ATCG'))
        return ''.join(dna_list)

Если я применяю свою функцию ко всем элементам G0, я получаю новое поколение (G1) мутантов ( список мутировавших последовательностей ).

G0 = ['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']

G1 = [mutate_v1(s,0.01) for s in G0]

#G1
['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']

Как я могу повторить свою функцию до G20 (20 поколений)?

Я могу сделать это вручную, как показано ниже

G1   = [mutate_v1(s,0.01) for s in G0]
G2   = [mutate_v1(s,0.01) for s in G1]
G3   = [mutate_v1(s,0.01) for s in G2]
G4   = [mutate_v1(s,0.01) for s in G3]
G5   = [mutate_v1(s,0.01) for s in G4]
G6   = [mutate_v1(s,0.01) for s in G5]
G7   = [mutate_v1(s,0.01) for s in G6]

Но я уверен, что цикл for будет лучше. Я протестировал несколько кодов, но безрезультатно.

Кто-нибудь может помочь, пожалуйста?

2
AEH 15 Окт 2021 в 20:04

2 ответа

Лучший ответ

Используйте диапазон для итерации до количества поколений и сохранения каждого поколение в списке, каждое поколение является результатом изменения предыдущего:

G0 = ['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']

generations = [G0]
for _ in range(20):
    previous_generation = generations[-1]
    generations.append([mutate_v1(s, 0.01) for s in previous_generation])

# then you can access by index to a generation
print(generations[1])  # access generation 1
print(generations[20]) # access generation 20

Вывод

['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']
['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAT']
3
Dani Mesejo 15 Окт 2021 в 17:22

Ответ Дэни - хорошее простое решение, но я хотел продемонстрировать другой подход, используя немного более продвинутую технику программирования на Python, функции генератора:

def mutation_generator(g0):
    g = g0.copy()
    while True:
        yield g
        g = [mutate_v1(seq, 0.01) for seq in g]

Прямо сейчас mutation_generator - это генератор бесконечной последовательности, что означает, что вы теоретически можете продолжать развивать свою последовательность бесконечно. Если вы хотите захватить 20 поколений:

g0 = ['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']
generation = mutation_generator(g0)
twenty_generations = [next(generation) for _ in range(20)]

Хорошая вещь в этом генераторе заключается в том, что мы можем запустить его с того места, где он остановился, в любой момент. Допустим, вы провели некоторый анализ первых двадцати поколений и теперь хотите посмотреть, что произойдет в следующих сотнях:

next_hundred = [next(generation) for _ in range(100)]

Теперь мы могли бы инициализировать новый генератор, используя последнее поколение из twenty_generations в качестве начального поколения нового генератора, но в этом нет необходимости, поскольку наш генератор generation просто остановился на 20 поколениях и готов к мутации всякий раз, когда вы вызываете next(generation).

Это открывает МНОГО возможностей, включая отправку новых параметров скорости мутации или даже, если хотите, совершенно новых функций мутации. На самом деле все, что угодно.

Другим преимуществом здесь является то, что вы можете запускать несколько генераторов в одной и той же начальной последовательности и наблюдать, как они расходятся. Обратите внимание, что это вполне возможно при более традиционном подходе к использованию цикла for в функции, но преимущество использования генераторов состоит в том, что вам не нужно генерировать всю последовательность сразу; он видоизменяется только тогда, когда вы ему указываете (через next()). Например:

g0 = ['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']
universe_1 = mutation_generator(g0)
universe_2 = mutation_generator(g0)
universe_3 = mutation_generator(g0)

# The first generation is always the same as g0, but this can be modified if you desire
next(universe_1)
next(universe_2)
next(universe_3)

# Compare the first mutation without having to calculate twenty generations in each 'universe' before getting back results
first_mutation_u1 = next(universe_1)
first_mutation_u2 = next(universe_2)
first_mutation_u3 = next(universe_3)

Опять же, вы также можете изменить функцию генератора mutation_generator, чтобы принимать другие параметры, такие как пользовательские функции мутации, или даже сделать возможным изменение скорости мутации в любое время и т. Д.

Наконец, в качестве примечания, использование генератора позволяет очень легко пропустить тысячи поколений без необходимости хранить более одной последовательности в памяти:

g0 = ['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']
generation = mutation_generator(g0)
for _ in range(10000):
    next(generation)

print(g0)  # first gen
print(next(generation))  # ten thousand generations later

Выход:

['CTGAA', 'CTGAA', 'CTGAA', 'CTGAA', 'CTGAA']
['TTGGA', 'CTTCG', 'TGTGA', 'TAACA', 'CATCG']

При подходе, основанном на цикле for, вам пришлось бы либо создать и сохранить все 10000 поколений (тратя много памяти), либо изменить код в ответе Дэни, чтобы он вел себя как генератор (но без преимущества!).

В Real Python есть хорошая статья о генераторах, если вы хотите узнать больше. И, конечно, также просмотрите документы.

2
ddejohn 15 Окт 2021 в 18:16