Короткая последовательность из 2-6 нуклеотидов (например, 5'-NGG-3' у Streptococcus pyogenes) [1], прилегающая к сайту разрезания ДНК комплексом из белка Cas9 и crRNA (или sgRNA в случае использования Cas9 в генетической инженерии). Таким образом, сигнал адресован белку Cas9, который сначала связывает ДНК (может связывать ДНК и без РНК), на которой есть PAM и только потом проверяет, комплементарна ли последовательность за ним последовательностм crRNA. В случае наличия PAM и комплементарности Cas9 разрезает ДНК. Сигнал высокоэффективен, что хорошо для бактерии, защищающейся от вирусов (вся ДНК с PAM будет проверена как потенциально опасная), но плохо для создания генетических сетей, где важно постоянное наличие мутантной версии Cas9 (отсутствует эндонуклеазная активность - dCas9) в клетке, т.к. тогда dCas9 неспецифично связывается с геномом во всех местах, где есть PAM (а таких мест много - 5.4e4 у E.Coli), при этом активно раскрывая двойную спираль ДНК, что делает Cas9 токсичным для клетки, поэтому количество молекул белка в клетке ниже, чем могло бы быть, и этого не всегда хватает для построения генетических схем. Утеря способности распознавать PAM сильно понижает токсичность Cas9 для клетки [2]. В то же время, согласно некоторым работам, неспецифичное связывание PAM важно для построения мультистабильных генетических схем (системы, имеющие два и более равновесных состояния) [3].
P.S. Сталкивался с этим сигналом и продолжаю с ним работать в курсовых (за прошлый и этот год), интересует меня в первую очередь из-за своей важности в дизайне генетических схем.
import numpy as np
import requests
import pandas as pd
from scipy.stats import mannwhitneyu
genes_table = pd.read_csv("human-genes.tsv", sep="\t")
sequences = []
i = 0
req_count = 0
while req_count < 100:
chromosome = genes_table.loc[i, "#chrom"]
start = genes_table.loc[i, "thickStart"]
strand = genes_table.loc[i, "strand"]
i += 1
if str(strand) == "+":
link = f"https://rest.ensembl.org/sequence/region/human/{chromosome}:{start-6}..{start+400}:1?expand_3prime=0;expand_5prime=0"
request = requests.get(link, headers={ "Content-Type" : "text/x-fasta"})
else:
continue
if "error" in request.text:
continue
else:
req_count += 1
sequence = "".join(request.text.split("\n")[1:])
sequences.append(sequence)
with open("sequences.txt", "w") as file:
print(*sequences, sep="\n", file=file)
С использованием кода выше было отобрано 100 генов человека на "+"-цепи; для простоты, были взяты участки от -7 нуклеотида от координаты старт-кодона до +400 нуклеотида после.
sequences_clear=[]
sequences_test_negative=[]
with open("sequences.txt", "r") as file:
sequences = file.readlines()
for sequence in sequences:
sequence_ = sequence[:13]
if sequence_[7:10] == "ATG":
sequences_clear.append(sequence_)
ATG_coord_fake = sequence[200:].find("ATG")
sequences_test_negative.append(sequence[200:][ATG_coord_fake-7:ATG_coord_fake+6])
sequences_train = sequences_clear[:40]
sequences_test = sequences_clear[40:]
sequences_test_negative = [seq for seq in sequences_test_negative if len(seq) == 13]
sequences_test_negative = sequences_test_negative[:36]
Далее были вырезаны участки размером 13 нуклеотидов в начале генов, из них были отобраны участки, содержащие старт-кодон. Полученные последовательности были разделены на 40 для построения PWM, и 36 для теста. Негативный котроль был сформирован из участков вокруг кодонов AUG, удаленных минимум на 200 нуклеотидов от начала гена, таким образом не являющихся старт-кодонами.
class PWM():
def __init__(self, e=1, freq=[0.295, 0.295, 0.205, 0.205]):
self.e = e
self.positions = {"A": 0, "T": 1, "G": 2, "C": 3}
self.frequencies = np.array(freq).reshape((4, 1))
def fit(self, seq_list):
self.seq_arr = np.array(list(map(list, seq_list)), dtype="str")
self.n_pos = len(seq_list[0])
self.n_seq = len(seq_list)
A_n = np.count_nonzero(self.seq_arr == "A", axis=0)
T_n = np.count_nonzero(self.seq_arr == "T", axis=0)
G_n = np.count_nonzero(self.seq_arr == "G", axis=0)
C_n = np.count_nonzero(self.seq_arr == "C", axis=0)
self.matrix = np.stack([A_n, T_n, G_n, C_n], axis=0).astype("float")
pseudocount = self.e / 4
self.matrix += pseudocount
self.matrix = self.matrix / (self.n_seq+self.e)
self.pwm_matrix = np.log(self.matrix / self.frequencies)
def test(self, seq):
weight = 0
for i, nuc in enumerate(list(seq)):
weight += self.pwm_matrix[self.positions[nuc], i]
return weight
def ic_calculate(self):
self.ic_matrix = self.matrix * np.log2(self.matrix / self.frequencies)
def return_pwm(self):
return pd.DataFrame(self.pwm_matrix, columns = np.arange(1, self.n_pos + 1), index = ["A", "T", "G", "C"])
def return_ic(self):
return pd.DataFrame(self.ic_matrix, columns = np.arange(1, self.n_pos + 1), index = ["A", "T", "G", "C"])
PWM была реализована в виде класса с методами для построения PWM, вычисления весов тестовых последовательностей, вычисления матрицы информационного содержания и вывода матриц. Для вычисления базовых частот нуклеотидов был взят GC-состав 41 [4]
pwm = PWM()
pwm.fit(sequences_train)
real_scores = []
fake_scores = []
for seq, fake_seq in zip(sequences_test, sequences_test_negative):
real_scores.append(pwm.test(seq))
fake_scores.append(pwm.test(fake_seq))
pwm.return_pwm()
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
A | -2.269649 | -0.268169 | -0.834564 | -0.660211 | -0.165514 | -0.382579 | -0.511791 | 1.202318 | -3.879087 | -3.879087 | -0.660211 | -0.268169 | -1.681862 |
T | -0.382579 | -0.511791 | -1.314137 | -1.314137 | -1.681862 | -0.834564 | -1.045873 | -3.879087 | 1.202318 | -3.879087 | -0.382579 | -0.268169 | -0.165514 |
G | 0.718985 | 0.198451 | 0.595753 | 0.595753 | 0.879328 | 0.095797 | -0.470599 | -3.515121 | -3.515121 | 1.566283 | 0.595753 | 0.198451 | 0.718985 |
C | 0.527930 | 0.527930 | 0.718985 | 0.659266 | -0.018614 | 0.775338 | 1.059590 | -3.515121 | -3.515121 | -3.515121 | 0.291541 | 0.376699 | 0.291541 |
Матрица была построена на основании полученных ранее последовательностей и протестирована на положительном и отрицательном контроле, оба по 36 последовательностей. Ниже представлен результат непараметрического теста Манна-Уитни наборов полученных весов. Видно, что при уровне значимости 0.05, вес положительного контроля выше веса отрицательного.
print(f"Средний вес положительного контроля: {np.mean(real_scores)}, средний вес отрицательного контроля: {np.mean(fake_scores)}.")
print(mannwhitneyu(real_scores, fake_scores))
Средний вес положительного контроля: 5.649725552268244, средний вес отрицательного контроля: 4.693530936295868. MannwhitneyuResult(statistic=483.0, pvalue=0.031966510996488576)
pwm.ic_calculate()
pwm.return_ic()
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
A | -0.099830 | -0.087285 | -0.154173 | -0.145196 | -0.059697 | -0.111062 | -0.130563 | 1.702848 | -0.034124 | -0.034124 | -0.145196 | -0.087285 | -0.133157 |
T | -0.111062 | -0.130563 | -0.150285 | -0.150285 | -0.133157 | -0.154173 | -0.156408 | -0.034124 | 1.702848 | -0.034124 | -0.111062 | -0.087285 | -0.059697 |
G | 0.436415 | 0.071576 | 0.319688 | 0.319688 | 0.626566 | 0.031181 | -0.086936 | -0.030922 | -0.030922 | 2.218334 | 0.319688 | 0.071576 | 0.436415 |
C | 0.264717 | 0.264717 | 0.436415 | 0.376968 | -0.005403 | 0.497903 | 0.904149 | -0.030922 | -0.030922 | -0.030922 | 0.115410 | 0.162376 | 0.115410 |
Выше представлена матрица информационного содержания для последовательностей, использованных для построения PWM. На рисунке ниже представлено LOGO для этих последовательностей (построено с учетом GC-состава в 41%)
Image(filename="logo.png")