Практикум 8


Описание сигнала

Для этого задания я взяла сигнал ядерной локализации, он же NLS. Его узнают транспортные факторы — кариоферины (они же импортины), после чего осуществляется перенос в ядро. Сигнал имеет разные последовательности, лучше всего исследованы классические сигналы ядерной локализации. Белки с классическими NLS распознаются и связываются кариоферином α при этом сам сигнал может быть одночастным или двухчастным. Одночастные сигналы состоят из одного участка основных аминокислот, состоящих преимущественно из остатков лизина (K) и аргинина (R). Примером моночастичного NLS является NLS большого Т-антигена вируса SV40: 126 PKKKRKV 132. Аминокислотная замена второго лизина (выделен жирным) полностью прекращает ядерный импорт, что подчеркивает важность этого остатка. Двухчастные последовательности содержат два кластера основных аминокислот, разделенных линкерной областью неконсервативных аминокислот (10-13 остатков). Архетипическая двучастная NLS содержится в белке Xenopus laevis — нуклеоплазмине: 155 KRPAATKKAGQAKKK 169.

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

NLS Консенсусные последовательности сигнала ядерной локализации
Сигнал (расположенный на N-конце) после импорта в ядро не расщепляется (как происходит в случае митохондрий и эндоплазматического ретикулума) и белок может несколько раз попадать в ядро.
Сигнал ядерной локализации должен быть сильным для белков, функционирующих исключительно в кариоплазме.

PWM

Для построения PWM была выбрана последовательность Kozak человека. Для обучения и тестирования я взяла последовательности начала генов X хромосомы с прямой цепи. Для этого я сначала отфильтровала координаты начала генов из файла с координатами всех генов:

      import pandas as pd

      all_genes = pd.read_csv("/content/human-genes.tsv", sep="\t")
      start_list = all_genes[(all_genes["strand"] == "+") & (all_genes["#chrom"] == "chrX")]["thickStart"].to_list()[:200]
    
Затем я добыла сами фрагменты около стартового кодона, воспользовавшись базой данных Ensembl:
      import requests, sys

      server = 'https://rest.ensembl.org'
      seq = []
      for coord in start:
        ext = f"/sequence/region/human/1:{coord-6}..{coord+6}:1?expand_3prime=0;expand_5prime=0"
        r = requests.get(server+ext, headers={ "Content-Type" : "text/x-fasta"})

        if not r.ok:
          r.raise_for_status()
          sys.exit()

        seq.append(r.text.split('\n')[1])
      
Полученные фрагменты я затем разделила на обучающую и тестовую выборку:
        import numpy as np

        train = seq[0:100]
        test = seq[100:200]
        
        train_spl = np.array([list(i) for i in train])
        test_spl = np.array([list(i) for i in test])
      
Затем я посчитала числа нуклеотидов в каждой позиции:
    mask_A = train_spl == 'A'
    count_A = mask_A.sum(axis=0)
    mask_T = train_spl == 'T'
    count_T = mask_T.sum(axis=0)
    mask_G = train_spl == 'G'
    count_G = mask_G.sum(axis=0)
    mask_C = train_spl == 'C'
    count_C = mask_C.sum(axis=0)
    
Осталось посчитать частоты нуклеотидов в каждой позиции и веса нуклеотидов в каждой позиции:
      gc = 0.423 #GC состав из базы данных NCBI
      eps = 0.1
      
      prob_A = (count_A+eps)/(len(train_spl)+eps)
      prob_T = (count_T+eps)/(len(train_spl)+eps)
      prob_G = (count_G+eps)/(len(train_spl)+eps)
      prob_C = (count_C+eps)/(len(train_spl)+eps)

      A = np.log(2*prob_A/(1-gc))
      T = np.log(2*prob_T/(1-gc))
      G = np.log(2*prob_G/gc)
      C = np.log(2*prob_C/gc)

      pwm = pd.DataFrame([A, T, G, C], ['A', 'T', 'G', 'C'], [i for i in range(1, 14)])
      
Вот такая PWM у меня получилась:
pwm
Сформируем позитивный контроль (оставшиеся 100 последовательностей в выборке с X хромомсомы):
      positive = []
      for i in test_spl:
        positive.append(np.sum([pwm.loc[j].iloc[k] for k, j in enumerate(i)]))
      
Отрицательный контроль был сформирован из случайных встреч AUG в геноме коронавируса:
        import random
        from Bio import SeqIO

        neg_df = pd.read_csv('./sarsatg.tsv', sep = '\t')

        random.seed(422)
        
        clean_negative = neg_df[(neg_df.Strand == '+') & (neg_df.Pattern != 'start-tr')].reset_index(drop = True)
        negative_random = random.choices(clean_negative.index, k = 100)
        
        coords_atg = clean_negative.iloc[negative_random, [0, 1]].reset_index(drop = True)
        coords_atg.Start = coords_atg.Start - 8
        coords_atg.End = coords_atg.End + 3
        
        iterator = SeqIO.parse('./sars.fasta', 'fasta')
        genome = next(iterator)
        
        negative_seqs = []
        for i in coords_atg.index:
            negative_seqs.append(genome.seq[int(coords_atg.iloc[i, 0]):int(coords_atg.iloc[i, 1])])
            
        with open('./negative_seqs.fasta', 'w') as negative_seqs_fasta:
            for i in negative_seqs:
                print(i, file = negative_seqs_fasta)
        
        negative = []
        for i in negative_seqs:
            negative.append(sum([pwm.loc[j].iloc[k] for k, j in enumerate(i)]))
        
Из боксплотов видно, медиана для положительного контроля выше, чем для отрицательного, следовательно в средним вес для тестовых последовательностей выше, что свидетельствует о том, что последовательность Kozak не случайная и встретить ее в не в области начала гена маловероятно. Однако, выбросы из положительного контроля тоже есть, что странно, объяснения этому у меня нет.
boxplot
Для определения порога веса, я построила гистограмму:
hist
Исходя из графика я выбрала порог 3. Таблица 1. Результат проверки, порог=3
Обучение Положительный контроль Отрицательный контроль
Сигнал + 82 79 30
Сигнал + 18 21 70
Затем я посчитала информационное содержание:
          ic_dict = {'A': [], 'T': [], 'G': [], 'C': []}
          proba = {'A': (1-gc)/2, 'T': (1-gc)/2, 'G': gc/2, 'C': gc/2}

          transposed = np.transpose(list(map(lambda x: [i for i in x.__str__()], train)))

          df = pd.DataFrame(transposed)
          for i in df.index:
              values = df.iloc[i, :].value_counts()
              
              for k, v in proba.items():
                  
                  try:
                      ic_dict[k].append(round((values.loc[k]/len(train_spl))*np.log2(values.loc[k]/(len(train_spl)*v)), 3))
                  except KeyError:
                      ic_dict[k].append(0)
                      
          ic = np.transpose(pd.DataFrame(ic_dict, [i for i in range(1, 14)]))
          
Вот такая матрица IC получилась:
boxplot
Для получения лого был использован сайт weblogo. Такая визуализация сигнала получилась:
logo
Видно что самое большое IC непосредственно у ATG, что неудивительно, кроме того у ближайших в ATG нуклеотидов IC повышено, что говорит о наличие сигнала. Я ожидала, что информационное содержание будет выше, видимо сигнал слабый, по крайней мере для выбраной хромосомы.

Литература

1. McLane, L.M. and Corbett, A.H. (2009), Nuclear localization signals and human disease. IUBMB Life, 61: 697-706. https://doi.org/10.1002/iub.194

Благодарности

При выполнение работы мне сильно помогли скрипты Филиппа Качкина, спасибо ему за понятные примеры кода.