昨天晚上和今天抽空實現了Burrows Wheleer Tansform,並且嘗試利用BWT,將短序列比對到長序列中。BWT的核心我覺得是要理解兩個原則:
1. F序列的每個元素是下標對應的L元素的後一位。
2. 排序後,F中第一個A和L中第一個A是同一個A。(排序不改變相對位置),公共前綴不改變排序位置。
mapping 過程實現的非常基礎,只能全序列不對,不能有gap。
#!/usr/bin/env python3
'''
burrows wheleer transform
核心是 排序後的F,L數組, L數組的某個元素是F數組對應的元素的前一位。
其次是,F數組的某個元素A在所有A中的相對位置不變。也就是F中的第一個A對應着L中的第一個A.
'''
import os
import sys
from collections import Counter
def BWT(s, end):
'''
輸出1:L string
輸出2:L string 每個字符的原來的位置
'''
# 記錄原序列中字符出現位置
C = {i: [] for i in set(s)} # 初始化L列的元素相對位置
for i in range(len(s)): # 分別給每個元素標記出現的位置
C[s[i]].append(i)
s = s + end # 添加最後的符號
M = [] # 初始化輸出
count = len(s) # 計算字符串長度
# 設置偏移矩陣
while True:
M.append(s)
s = s[len(s)-1]+s[0:len(s)-1]
count -= 1
if count <= 0:
break
N = [[v, k] for k, v in enumerate(M[::-1])] # 標記位置信息
N_sort = sorted(N, key=lambda x: x[0]) #字典排序
C = {i: [] for i in set(s)} # 初始化L列的元素相對位置
L = []
for k, v in N_sort: # 以A爲例,Lstring中 從頭開始每一個A在原來字符串的位置
L.append(k[-1])
C[k[-1]].append(v)
return(["".join(L), C])
def reverseBWT(s, end):
'''
'''
B = Counter(s) # 壓縮的F列
C = {i: [] for i in set(B)} # 初始化L列的元素相對位置
for i in range(len(s)): # 分別給每個元素標記出現的位置
C[s[i]].append(i)
out = "" # 初始化輸出
arrow = 1
while True:
Lbase = s[arrow-1] # 取出對應的字符
out = out + Lbase # 添加到輸出
if Lbase == end: # 如果遇到終止符,則退出循環,並輸出
break
# 從數組C中查找對應字符出現的位置,並且枚舉,這樣可以得到字符所在位置對應的相同字符的偏移量。
for i, j in enumerate(C[Lbase]):
if j == arrow-1:
pianyi = i+1 # 這裏應該是對應的L中的序號
break
arrow = CheckF(B, Lbase, pianyi)
return(out[::-1])
def simple_mapping(ref, seed):
s, rank = BWT(ref, "#")
B = Counter(s) # 壓縮的F列
C = {i: [] for i in set(s)} # 初始化L列的元素相對位置
for i in range(len(s)): # 分別給每個元素標記出現的位置
C[s[i]].append(i)
seed = seed[::-1]
print(ref)
for mark in range(len(C[seed[0]])):
arrow = C[seed[0]][mark] # mark:0,1,2 編號,0開始
# arrow: L 中的 12,34,65... 實際位置
out = ""
count = 0
while True:
Lbase = s[arrow]
if Lbase != seed[count] or Lbase == '#':
break
out = out + Lbase
for i, v in enumerate(C[Lbase]):
if v == arrow:
pianyi = i + 1
break
arrow = CheckF_0base(B, Lbase, pianyi)
count += 1
if count > len(seed)-1:
left = rank[seed[0]][mark]
out = "*"*(left-len(out)+1) + out[::-1] + "*"*(len(s)-left-2)
print(out)
break
def CheckF(mtx, base, order):
out_order = 0
for k in sorted(mtx.keys()): # 從F數組中根據字符,以及偏移量,計算出下一個座標 P = 字符前所有的字符的數目+偏移量
if base != k:
out_order += mtx[k]
else:
out_order += order
break
return(out_order)
def CheckF_0base(mtx, base, order):
out_order = 0
for k in sorted(mtx.keys()): # 從F數組中根據字符,以及偏移量,計算出下一個座標 P = 字符前所有的字符的數目+偏移量
if base != k:
out_order += mtx[k]
else:
out_order += order
break
return(out_order-1)
if __name__ == '__main__':
x = "actgagctttagcgtagctttaggagagcttcctagctacgtatcgagcgggcatctatc"
seed = "ga"
print("origin string is :" + x)
print("L string is :" + BWT(x, '#')[0])
print("seed string is :" + seed)
print("mapped seq is :")
simple_mapping(x, seed)
結果: