Открытые рамки считывания и поиск генов

Чтобы найти ген в геноме, ищут открытые рамки считывания — участки от старт-кодона до стоп-кодона. Это первый шаг предсказания генов.

ORF (Open Reading Frame, открытая рамка считывания) — участок последовательности от старт-кодона ATG до ближайшего стоп-кодона в той же рамке, потенциально кодирующий белок.

Геном — это длинный текст без пробелов и заглавных букв. Где в нём гены? Простейшая гипотеза: ген начинается с ATG и заканчивается стопом, без стопов внутри. Такие участки и есть ORF. Поиск ORF — классическая задача, и она прекрасно решается на чистом Python.

Идея алгоритма

В каждой из трёх рамок плюс-нити ищем ATG, от него читаем тройками до первого стопа — это кандидат-ORF. Затем то же на минус-нити (обратный комплемент). Итого шесть рамок. Длинные ORF вероятнее кодируют настоящий белок, чем короткие случайные.

...A T G | C C C | G G G | T A A...
   старт   кодон   кодон   стоп
   |__________ORF__________|

Поиск ORF в трёх рамках

DNA_CODON = {
    'TTT':'F','TTC':'F','TTA':'L','TTG':'L','CTT':'L','CTC':'L','CTA':'L','CTG':'L',
    'ATT':'I','ATC':'I','ATA':'I','ATG':'M','GTT':'V','GTC':'V','GTA':'V','GTG':'V',
    'TCT':'S','TCC':'S','TCA':'S','TCG':'S','CCT':'P','CCC':'P','CCA':'P','CCG':'P',
    'ACT':'T','ACC':'T','ACA':'T','ACG':'T','GCT':'A','GCC':'A','GCA':'A','GCG':'A',
    'TAT':'Y','TAC':'Y','TAA':'*','TAG':'*','CAT':'H','CAC':'H','CAA':'Q','CAG':'Q',
    'AAT':'N','AAC':'N','AAA':'K','AAG':'K','GAT':'D','GAC':'D','GAA':'E','GAG':'E',
    'TGT':'C','TGC':'C','TGA':'*','TGG':'W','CGT':'R','CGC':'R','CGA':'R','CGG':'R',
    'AGT':'S','AGC':'S','AGA':'R','AGG':'R','GGT':'G','GGC':'G','GGA':'G','GGG':'G',
}
STOPS = {'TAA', 'TAG', 'TGA'}

def find_orfs(seq):
    orfs = []
    for frame in range(3):
        i = frame
        while i < len(seq) - 2:
            if seq[i:i+3] == 'ATG':
                prot = ''
                j = i
                while j < len(seq) - 2:
                    codon = seq[j:j+3]
                    if codon in STOPS:
                        orfs.append((frame, i, prot))
                        break
                    prot += DNA_CODON[codon]
                    j += 3
            i += 3
    return orfs

dna = 'AAATGCCCGGGTAAATGTTTTAG'
for frame, start, prot in find_orfs(dna):
    print(f'рамка {frame}, старт {start}: {prot}')

Вывод:

рамка 2, старт 2: MPG
рамка 2, старт 14: MF

Алгоритм нашёл два ORF, оба в рамке 2. Внешний цикл идёт по рамкам, внутренний от каждого ATG читает до стопа. Заметьте: ORF учитываются только если есть стоп — «открытая до конца последовательности» рамка обычно отбрасывается как неполная.

Обе нити: шесть рамок

Гены бывают и на минус-нити, поэтому полноценный поиск прогоняет тот же алгоритм по обратному комплементу. Соберём поиск по всем шести рамкам.

STOPS = {'TAA', 'TAG', 'TGA'}
rc = str.maketrans('ATGC', 'TACG')

def orf_lengths(seq):
    found = []
    for strand, s in [('+', seq), ('-', seq.translate(rc)[::-1])]:
        for frame in range(3):
            i = frame
            while i < len(s) - 2:
                if s[i:i+3] == 'ATG':
                    j = i
                    while j < len(s) - 2:
                        if s[j:j+3] in STOPS:
                            found.append((strand, (j + 3 - i) // 3))
                            break
                        j += 3
                i += 3
    return found

dna = 'ATGAAATAACCTTACCCCAT'
for strand, codons in orf_lengths(dna):
    print(f'нить {strand}: ORF, длина в кодонах = {codons}')

Вывод:

нить +: ORF, длина в кодонах = 3
нить -: ORF, длина в кодонах = 3

Как работает под капотом: от ORF к настоящему гену

ORF — лишь кандидат. У бактерий гены плотно упакованы и ORF почти равны генам, поэтому метод работает хорошо. У эукариот всё сложнее: гены разорваны интронами, и наивный поиск ORF найдёт лишь куски. Поэтому реальные предсказатели генов добавляют статистику (характерный состав кодонов кодирующих участков), сигналы промоторов и сплайс-сайтов, машинное обучение и сравнение с известными генами через выравнивание. Но в основе всё равно лежит идея ORF, которую вы только что реализовали.

Частые ошибки

  • Искать только на плюс-нити. Половина генов может быть на минус-нити — нужен обратный комплемент.
  • Брать любой ATG за старт. Случайных ATG много; фильтруют по длине ORF и контексту.
  • Считать ORF готовым геном у эукариот. Интроны рвут ген на части — нужны более тонкие методы.

Итог

  • ORF — участок от ATG до стоп-кодона в одной рамке, кандидат на ген.
  • Полный поиск прогоняет шесть рамок (3 на плюс-нити, 3 на минус через обратный комплемент).
  • Длинные ORF вероятнее кодируют белок, чем короткие случайные.
  • У бактерий ORF ≈ ген; у эукариот нужны более сложные предсказатели из-за интронов.
Проверьте себя
1. Что такое ORF (открытая рамка считывания)?
AЛюбой участок ДНК
BУчасток от старт-кодона ATG до ближайшего стоп-кодона в той же рамке
CТолько стоп-кодон
DВся хромосома
2. Сколько рамок считывания нужно проверить для полного поиска ORF?
A1
B3
C6
D2
3. Почему у эукариот наивный поиск ORF работает хуже, чем у бактерий?
AУ эукариот нет генов
BГены эукариот разорваны интронами, поэтому ORF находит лишь куски
CУ эукариот нет стоп-кодонов
DДНК эукариот не содержит ATG