基本原理
两条序列每个元素比对无非三种情况:1序列对空,2序列对空,两序列配对。该算法从序列最左端开始,依次按照这三种情况打分,每下一个元素的配对情况都是按照上一个元素的最优解往下的。直到将打分矩阵铺满,打分矩阵右下角的得分情况即说明两序列配对总得分的最优解。再通过路线的回溯得到比对的几种情况。
M[i][j] = max(M[i-1][j]+gap,M[i][j-1]+gap,M[i-1][j-1]+point)
gap:未配对罚分;point:配对得分(包括错配和正确配对)
示例:
将catgt和acgctg按照上面的规则打分
gap=-1 wrong point=-1 point=2
gap | c | a | t | g | t | |
gap | 0 | -1 | -2 | -3 | -4 | -5 |
a | -1 | -1 | 1 | 0 | -1 | -1 |
c | -2 | 1 | 0 | 0 | -1 | -2 |
g | -3 | 0 | 0 | -1 | 2 | 1 |
c | -4 | -1 | -1 | -1 | 1 | 1 |
t | -5 | -2 | -2 | 1 | 0 | 3 |
g | -6 | -3 | -3 | 0 | 3 | 2 |
将每个得分的来源用箭头表示,发现有三条线路是首尾连通的且分值最大
↘表示配对成功,↓的箭头的位置所在左边的碱基对空,→与前者相对
得分矩阵
BLOSUM62矩阵是经过大量实验验证出来正确性的得分矩阵,相比于PAM矩阵的预测得分来说更符合真实情况。
"BLOSUM62": {
"AA": "4", "AC": "0", "AD": "-2", "AE": "-1", "AF": "-2", "AG": "0", "AH": "-2", "AI": "-1", "AK": "-1", "AL": "-1", "AM": "-1", "AN": "-2", "AP": "-1", "AQ": "-1", "AR": "-1", "AS": "1", "AT": "0", "AV": "0", "AW": "-3", "AY": "-2", "CA": "0", "CC": "9", "CD": "-3", "CE": "-4", "CF": "-2", "CG": "-3", "CH": "-3", "CI": "-1", "CK": "-3", "CL": "-1", "CM": "-1", "CN": "-3", "CP": "-3", "CQ": "-3", "CR": "-3", "CS": "-1", "CT": "-1", "CV": "-1", "CW": "-2", "CY": "-2", "DA": "-2", "DC": "-3", "DD": "6", "DE": "2", "DF": "-3", "DG": "-1", "DH": "-1", "DI": "-3", "DK": "-1", "DL": "-4", "DM": "-3", "DN": "1", "DP": "-1", "DQ": "0", "DR": "-2", "DS": "0", "DT": "-1", "DV": "-3", "DW": "-4", "DY": "-3", "EA": "-1", "EC": "-4", "ED": "2", "EE": "5", "EF": "-3", "EG": "-2", "EH": "0", "EI": "-3", "EK": "1", "EL": "-3", "EM": "-2", "EN": "0", "EP": "-1", "EQ": "2", "ER": "0", "ES": "0", "ET": "-1", "EV": "-2", "EW": "-3", "EY": "-2", "FA": "-2", "FC": "-2", "FD": "-3", "FE": "-3", "FF": "6", "FG": "-3", "FH": "-1", "FI": "0", "FK": "-3", "FL": "0", "FM": "0", "FN": "-3", "FP": "-4", "FQ": "-3", "FR": "-3", "FS": "-2", "FT": "-2", "FV": "-1", "FW": "1", "FY": "3", "GA": "0", "GC": "-3", "GD": "-1", "GE": "-2", "GF": "-3", "GG": "6", "GH": "-2", "GI": "-4", "GK": "-2", "GL": "-4", "GM": "-3", "GN": "0", "GP": "-2", "GQ": "-2", "GR": "-2", "GS": "0", "GT": "-2", "GV": "-3", "GW": "-2", "GY": "-3", "HA": "-2", "HC": "-3", "HD": "-1", "HE": "0", "HF": "-1", "HG": "-2", "HH": "8", "HI": "-3", "HK": "-1", "HL": "-3", "HM": "-2", "HN": "1", "HP": "-2", "HQ": "0", "HR": "0", "HS": "-1", "HT": "-2", "HV": "-3", "HW": "-2", "HY": "2", "IA": "-1", "IC": "-1", "ID": "-3", "IE": "-3", "IF": "0", "IG": "-4", "IH": "-3", "II": "4", "IK": "-3", "IL": "2", "IM": "1", "IN": "-3", "IP": "-3", "IQ": "-3", "IR": "-3", "IS": "-2", "IT": "-1", "IV": "3", "IW": "-3", "IY": "-1", "KA": "-1", "KC": "-3", "KD": "-1", "KE": "1", "KF": "-3", "KG": "-2", "KH": "-1", "KI": "-3", "KK": "5", "KL": "-2", "KM": "-1", "KN": "0", "KP": "-1", "KQ": "1", "KR": "2", "KS": "0", "KT": "-1", "KV": "-2", "KW": "-3", "KY": "-2", "LA": "-1", "LC": "-1", "LD": "-4", "LE": "-3", "LF": "0", "LG": "-4", "LH": "-3", "LI": "2", "LK": "-2", "LL": "4", "LM": "2", "LN": "-3", "LP": "-3", "LQ": "-2", "LR": "-2", "LS": "-2", "LT": "-1", "LV": "1", "LW": "-2", "LY": "-1", "MA": "-1", "MC": "-1", "MD": "-3", "ME": "-2", "MF": "0", "MG": "-3", "MH": "-2", "MI": "1", "MK": "-1", "ML": "2", "MM": "5", "MN": "-2", "MP": "-2", "MQ": "0", "MR": "-1", "MS": "-1", "MT": "-1", "MV": "1", "MW": "-1", "MY": "-1", "NA": "-2", "NC": "-3", "ND": "1", "NE": "0", "NF": "-3", "NG": "0", "NH": "1", "NI": "-3", "NK": "0", "NL": "-3", "NM": "-2", "NN": "6", "NP": "-2", "NQ": "0", "NR": "0", "NS": "1", "NT": "0", "NV": "-3", "NW": "-4", "NY": "-2", "PA": "-1", "PC": "-3", "PD": "-1", "PE": "-1", "PF": "-4", "PG": "-2", "PH": "-2", "PI": "-3", "PK": "-1", "PL": "-3", "PM": "-2", "PN": "-2", "PP": "7", "PQ": "-1", "PR": "-2", "PS": "-1", "PT": "-1", "PV": "-2", "PW": "-4", "PY": "-3", "QA": "-1", "QC": "-3", "QD": "0", "QE": "2", "QF": "-3", "QG": "-2", "QH": "0", "QI": "-3", "QK": "1", "QL": "-2", "QM": "0", "QN": "0", "QP": "-1", "QQ": "5", "QR": "1", "QS": "0", "QT": "-1", "QV": "-2", "QW": "-2", "QY": "-1", "RA": "-1", "RC": "-3", "RD": "-2", "RE": "0", "RF": "-3", "RG": "-2", "RH": "0", "RI": "-3", "RK": "2", "RL": "-2", "RM": "-1", "RN": "0", "RP": "-2", "RQ": "1", "RR": "5", "RS": "-1", "RT": "-1", "RV": "-3", "RW": "-3", "RY": "-2", "SA": "1", "SC": "-1", "SD": "0", "SE": "0", "SF": "-2", "SG": "0", "SH": "-1", "SI": "-2", "SK": "0", "SL": "-2", "SM": "-1", "SN": "1", "SP": "-1", "SQ": "0", "SR": "-1", "SS": "4", "ST": "1", "SV": "-2", "SW": "-3", "SY": "-2", "TA": "0", "TC": "-1", "TD": "-1", "TE": "-1", "TF": "-2", "TG": "-2", "TH": "-2", "TI": "-1", "TK": "-1", "TL": "-1", "TM": "-1", "TN": "0", "TP": "-1", "TQ": "-1", "TR": "-1", "TS": "1", "TT": "5", "TV": "0", "TW": "-2", "TY": "-2", "VA": "0", "VC": "-1", "VD": "-3", "VE": "-2", "VF": "-1", "VG": "-3", "VH": "-3", "VI": "3", "VK": "-2", "VL": "1", "VM": "1", "VN": "-3", "VP": "-2", "VQ": "-2", "VR": "-3", "VS": "-2", "VT": "0", "VV": "4", "VW": "-3", "VY": "-1", "WA": "-3", "WC": "-2", "WD": "-4", "WE": "-3", "WF": "1", "WG": "-2", "WH": "-2", "WI": "-3", "WK": "-3", "WL": "-2", "WM": "-1", "WN": "-4", "WP": "-4", "WQ": "-2", "WR": "-3", "WS": "-3", "WT": "-2", "WV": "-3", "WW": "11", "WY": "2", "YA": "-2", "YC": "-2", "YD": "-3", "YE": "-2", "YF": "3", "YG": "-3", "YH": "2", "YI": "-1", "YK": "-2", "YL": "-1", "YM": "-1", "YN": "-2", "YP": "-3", "YQ": "-1", "YR": "-2", "YS": "-2", "YT": "-2", "YV": "-1", "YW": "2", "YY": "7"
},
"ATCG": {
"AA": 2, "AT": -1, "AC": -1, "AG": -1,
"TA": -1, "TT": 2, "TC": -1, "TG": -1,
"CA": -1, "CT": -1, "CC": 2, "CG": -1,
"GA": -1, "GT": -1, "GC": -1, "GG": 2
}
代码思路
路径生成
在得分矩阵每一个分值生成时,判断其分值的来源,考虑存在多个来源的情况,生成一个表示去向的矩阵,由xyz分别表示下,右,右下,无路可走就用空集表示,由于最左上角一定可以往右或往下,方便起见,在初始的方位矩阵左上角手动加上y和x。将方位储存在road里
gap = -2
seq1 = '-'+seq1
seq2 = '-'+seq2 #由于在打分时要对序列“前一个”不存在的碱基比对,因此加上“-”
martix = [[(i*gap)+(j*gap) for i in range(len(seq1))]for j in range(len(seq2))]#生成打分矩阵的第一行和第一列,其余部分由于后面会被修正,所以可以不用管
road = [[[] for i in seq1] for j in seq2]
len1 = len(seq1)
len2 = len(seq2)
for i in range(1,len2):
for j in range(1,len1):
score_from_left = martix[i][j - 1] + gap
score_from_diag = martix[i - 1][j - 1] + int(a[f'{seq1[j]}{seq2[i]}'])
score_from_up = martix[i - 1][j] + gap
maxim=max(martix[i][j-1]+gap,martix[i-1][j-1]+int(a[f'{seq1[j]}{seq2[i]}']),martix[i-1][j]+gap)
#判断分值来源
if maxim == score_from_left:
road[i][j-1].append('y')
if maxim == score_from_diag:
road[i-1][j-1].append('z')
if maxim == score_from_up:
road[i-1][j].append('x')
#生成打分矩阵
martix[i][j]=max(martix[i][j-1]+gap,martix[i-1][j-1]+int(a[f'{seq1[j]}{seq2[i]}']),martix[i-1][j]+gap)
#手动给左上角加上右和下方向
road[0][0].append('x')
road[0][0].append('y')
深度优先算法寻找所有比对可能性
关于深度优先算法,简而言之就是走迷宫从入口开始一直贴着右边墙壁或者左边墙壁走,在上面有箭头的图自己走走试试看
def find_path(martix):
paths = []
def depth_first(r, l, current_path):
#如果到达右下角,把路径记录下来,return即出栈
if r == len(martix) - 1 and l == len(martix[1]) - 1:
paths.append(current_path)
return
#识别到相关方位,即朝着此方位前进
for i in martix[r][l]:
if i == 'y' and l < len(martix[1]) - 1:
depth_first(r, l + 1, current_path + ['y'])#这一句包括了堆栈操作
elif i == 'x' and r < len(martix) - 1:
depth_first(r + 1, l, current_path + ['x'])
elif i == 'z' and l < len(martix[1]) - 1 and r < len(martix) - 1:
depth_first(r + 1, l + 1, current_path + ['z'])
depth_first(0, 0, [])
return paths
输出的结果是若干条线路,从左上角出发,按照xyz到右下角的线路
按照线路输出比对结果
这段代码比较简单,不多赘述
a = len(find_path(road))
r1 = {i:'' for i in range(a) }
r2 = {i:'' for i in range(a) }
for i in range(len(find_path(road))):
x = 0
y = 0
for j in find_path(road)[i]:
if j == 'x':
r1[i] = r1[i]+'-'
r2[i] = r2[i]+seq2[y+1]
y+=1
elif j == 'y':
r2[i] = r2[i]+'-'
r1[i] = r1[i]+seq1[x+1]
x+=1
elif j == 'z':
r1[i] = r1[i]+seq1[x+1]
r2[i] = r2[i]+seq2[y+1]
x += 1
y += 1
总代码如下
import json
def blastp(seq1,seq2):
def find_path(martix):
paths = []
def depth_first(r, l, current_path):
if r == len(martix) - 1 and l == len(martix[1]) - 1:
paths.append(current_path)
return
for i in martix[r][l]:
if i == 'y' and l < len(martix[1]) - 1:
depth_first(r, l + 1, current_path + ['y'])
elif i == 'x' and r < len(martix) - 1:
depth_first(r + 1, l, current_path + ['x'])
elif i == 'z' and l < len(martix[1]) - 1 and r < len(martix) - 1:
depth_first(r + 1, l + 1, current_path + ['z'])
depth_first(0, 0, [])
return paths
with open('/juzhen.json', 'r') as BLOSUM:
a = json.load(BLOSUM)['BLOSUM62'] #根据需要更改
gap = -2 #根据需要更改
seq1 = '-'+seq1
seq2 = '-'+seq2
martix = [[(i*gap)+(j*gap) for i in range(len(seq1))]for j in range(len(seq2))]
len1 = len(seq1)
len2 = len(seq2)
road = [[[] for i in seq1] for j in seq2]
road[0][0].append('x')
for i in range(1,len2):
for j in range(1,len1):
score_from_left = martix[i][j - 1] + gap
score_from_diag = martix[i - 1][j - 1] + int(a[f'{seq1[j]}{seq2[i]}'])
score_from_up = martix[i - 1][j] + gap
maxim=max(martix[i][j-1]+gap,martix[i-1][j-1]+int(a[f'{seq1[j]}{seq2[i]}']),martix[i-1][j]+gap)
if maxim == score_from_left:
road[i][j-1].append('y')
if maxim == score_from_diag:
road[i-1][j-1].append('z')
if maxim == score_from_up:
road[i-1][j].append('x')
martix[i][j]=max(martix[i][j-1]+gap,martix[i-1][j-1]+int(a[f'{seq1[j]}{seq2[i]}']),martix[i-1][j]+gap)
road[0][0].append('y')
a = len(find_path(road))
r1 = {i:'' for i in range(a) }
r2 = {i:'' for i in range(a) }
for i in range(len(find_path(road))):
x = 0
y = 0
for j in find_path(road)[i]:
if j == 'x':
r1[i] = r1[i]+'-'
r2[i] = r2[i]+seq2[y+1]
y+=1
elif j == 'y':
r2[i] = r2[i]+'-'
r1[i] = r1[i]+seq1[x+1]
x+=1
elif j == 'z':
r1[i] = r1[i]+seq1[x+1]
r2[i] = r2[i]+seq2[y+1]
x += 1
y += 1
print(r1)
print(r2)
seq1 = '' #在这里输入蛋白质序列
seq2 = ''
print(blastp(seq1,seq2))
此算法精度极高,但是一般不用于较长的序列,因为时间复杂度较高,且会将所有结果都显示出来。当空位罚分<-2时运行速度较快