简介
WGDI(全基因组重复识别),一种基于 Python 的命令行工具,可让研究人员深入了解递归多倍化并进行跨物种基因组比对分析。
官方文档 下一 篇会选择一个物种的分析结果做示例。
安装
conda install -c bioconda wgdi
pip install wgdi
git clone https://github.com/SunPengChuan/wgdi.git
cd wgdi
python setup.py install
依赖软件
PAML | MAFFT | MUSCLE | PAL2NAL | IQTREE
使用pip下载完成 需要配置文件目录 wgdi -conf /? >conf.ini 里面是默认的文件路径,如果不对应打开修改即可 再次输入 wgdi -conf conf.ini 就将配置环境导入到程序中了
分析
数据预处理
数据处理是很有必要的,如果格式不正确,后面的分析很有可能会报错,大家可以自行处理数据得到gff文件以及基因组len文件
下面提供wgdi作者以及写的处理脚本,具体脚本内容将放到本文末尾
01.getgff.py 02.gff_lens.py 03.seq_newname.py generate_conf.py # 脚本由徐洲更制作。下载链接
序列比对
通过blastp或者diamond进行蛋白之间比对。
makeblastdb -in ath.pep.fa -dbtype prot
blastp -num_threads 50 -db ath.pep.fa -query ath.pep.fa -outfmt 6 -evalue 1e-5 -num_alignments 20 -out ath.blastp.txt &
点图
使用命令进入文件夹取出参数文件,主要是通过前面的[] 去识别程序所以可以将下列所有步骤放到一个文本文件中。
wgdi -d help >> total.conf
修改配置文件
[dotplot]
blast = blast file
blast_reverse = false
gff1 = gff1 file
gff2 = gff2 file
lens1 = lens1 file
lens2 = lens2 file
genome1_name = Genome1 name
genome2_name = Genome2 name
multiple = 1
score = 100
evalue = 1e-5
repeat_number = 10
position = order
blast_reverse = false
ancestor_left = none
ancestor_top = none
markersize = 0.5
figsize = 10,10
savefig = savefile(.svg,.png,.pdf)
运行命令 wgdi -d total.conf
任意修改lens1 和lens2的染色体的数量和顺序,可以得到任意染色体间的点图。 如果想修改图片中染色体的位置,则可以改动len文件进行操作。 也可以单独选择几条染色体做点图观察,都是通过改动len文件进行的
点图解读: 点图规则:点图是根据左边基因组的基因,最好同源的点为红色,次好的四个基因为蓝色,剩余部分(同源基因有限制个数)为灰色,是根据打分结果进行排序的。 (1)点图需要横向看和纵向看:这个点图是物种自身比对,所以只需横向看。片段深度应该为2,但最好同源个数为1,即红色点没有集中分布在某个片段上。常常可以认两个片段很相似,再加上自身。所以,认定为最近发生的加倍为三倍化。除此之外,蓝色或灰色的片段很少有,表明更古老的加倍很不明显。 (2)对角线上,本不应该出现片段。自身比自身显然是最好匹配,这些点已经去掉了。而剩余的这些由于串联重复造成的。即该基因临近的位置同源性很高,打在了对角线附近。可以计算这些串联重复的ks值,估算重复片段的爆发时间。 (3)这个点图是后续跑共线性的基石。所以,score, evalue, repeat_number判定的同源点的数量是共线性打分矩阵的来源。
可以看到点图的结果中,共线性片段,按照blast进行排序,红色,蓝色 ,灰色是最后剩下的,左边相对上面的义工只有十个同源基因对应 看到点图中共线性片段大多是由红色和蓝色构成的。
共线性
使用命令进入文件夹取出参数文件。wgdi -icl ? >> total.conf
[collinearity]
gff1 = gff1 file
gff2 = gff2 file
lens1 = lens1 file
lens2 = lens2 file
blast = blast file
blast_reverse = false
multiple = 1
process = 8
evalue = 1e-5
score = 100
grading = 50,40,25
mg = 40,40
pvalue = 0.2
repeat_number = 10
positon = order
savefile = collinearity file
运行 wgdi -icl total.conf
Ks值计算
wgdi -ks ? >> total.conf
[ks]
cds_file = cds file
pep_file = pep file
align_software = muscle
pairs_file = gene pairs file
ks_file = ks result
运行 wgdi -ks total.conf
输出的ks结果,基因对算不出来的ks值,默认为-1。
Blockinfo
因为共线性和ks值结果不容易展示,而且,常常需要对共线性结果筛选,因此,将这些信息汇总到一个表中,方便删选。 这一步主要是将前面跑的共线性和ks值统一到一个文件当中。
首先将参数保存到文件当中 wgdi -bi ? >> total.conf
[blockinfo]
blast = blast file
gff1 = gff1 file
gff2 = gff2 file
lens1 = lens1 file
lens2 = lens2 file
collinearity = collinearity file
score = 100
evalue = 1e-5
repeat_number = 20
position = order
ks = ks file
ks_col = ks_NG86
savefile = block_information.csv
运行 wgdi -bi total.conf
结果生成一个表block_information.csv
第1列: id 共线性的结果的唯一标识 第2.4.5列: chr1,start1,end1 参考基因组(点图的左边)的共线性范围 第3.6.7列: chr2,start2,end2 参考基因组(点图的上边)的共线性范围 第8列: pvalue 评估共线性的结果,常常认为小于0.01的更合理些 第9列: length 共线性片段长度 ks_median 共线性片段上所有基因对ks的中位数(主要用来评判ks分布的) ks_average 共线性片段上所有基因对ks的平均值 homo1 homo2 homo3 homo4 homo5 与multiple参数有关,即homo+multiple 主要规则是:基因对如果在点图中为红色,赋值为1,蓝色赋值为0,灰色赋值为-1。共线性片段上所有基因对赋值后求平均值,这样就赋予该共线性一个-1,1的值。如果共线性的点大部分为红色,那么该值接近于1。可以作为共线性片段的筛选。 block1,block2 分别为共线性片段上基因order的位置。 ks 共线性片段的上基因对的ks值 density1, density2 共线性片段的基因分布密集程度。值越小表示稀疏;如何计算的呢?就是length 除以对应的srart1 end1 差值
Correspondence
wgdi -c ? >> total.conf
[correspondence]
blockinfo = block_information.csv
lens1 = lens1 file
lens2 = lens2 file
tandem = (true/false)
tandem_length = 200
pvalue = 0.05
block_length = 5
multiple = 1
homo = 0,1
savefile = block_information.new.csv
wgd -c total.conf
筛选完的结果,是将对角线上的片段去除,剩余一些需要的片段。 得出筛选完之后的结果会发现新的csv文件会比之前的文件要小,主要就是将对角线上的片段给去除了。
blockks
wgdi -bk ? > blockks.conf
[blockks]
lens1 = lens1 file
lens2 = lens2 file
genome1_name = Genome1 name
genome2_name = Genome2 name
blockinfo = block_information.new.csv
pvalue = 0.05
tandem = true
tandem_length = 200
markersize = 1
area = 0,2
block_length = minimum length
figsize = 8,8
savefig = save image
wgdi -bk total.conf
最终得到的结果会发现对角线上的点被去除,但是还是会有一些点会没有去除。但是也可以继续进行后续操作。
KsPeaks
计算Ks峰值
wgdi -kp ? >> total.conf
[kspeaks]
blockinfo = block_information.new.csv
pvalue = 0.05
tandem = true
block_ length = int number
ks_area = 0,10
multiple = 1
homo = 0,1
fontsize = 9
area = 0,3
figsize = 10,6.18
savefig = saving image
savefile = ks_median.distri.csv
wgdi -kp total.conf
结果图片如果发现前面趋于0 的值比较多,查看前面得出的点图,原因是由于Correspondence这一步设置的tandom值长度偏小, 可以继续拟合或者直接在ks_median.distri.csv文件中进行筛选,将ks_median和ks_average进行从小到大排序,将数值过低的去除,它们还有一个特性就是 chr1以及chr2 都是自己对自己。 将修改结果重新输入[kspeaks] blockinfo = 修改后的(*.csv )
PeaksFit
峰拟合,需要结合KsPeaks和blockks的结果看峰为一个还是多个,mode = median 建议用每个block的中位数的ks值去做拟合。
wgdi -pf ? >> total.conf
[peaksfit]
blockinfo = block information
mode = median
bins_number = 200
ks_area = 0,10
fontsize = 9
area = 0,3
figsize = 10,6.18
savefig = saving image
wgdi -pf total.conf
跑完得到 拟合优度以及对应的曲线 R-square: 0.9618516780863838 The gaussian fitting curve parameters are : 1.305477901404146 | 2.1185082504069626 | 0.41222580475422466 其中: 中间的第二个值就是峰值的大小,数据保存到单独文件中,就可以绘制多个物种种内和种间的ks峰值了。
关于一个物种发生多次加倍事件如何获取Ks峰值的策略: 每次加倍保留的共线性片段是一定的,是某一次产生的。可以通过Ks值进行区分。 应该对其分开处理,对共线性片段筛选(去除对角线的Ks峰或者有多次加倍的颜色发生变化的需要去除之后在进行拟合)
脚本化运行
# 启动环境
conda activate wgdi
# 进入目录并解压
cd Arabidopsis_thaliana
gunzip *.gz
# 将generate_conf.py配置到环境变量
generate_conf.py -p ath Athaliana_447_TAIR10.fa Athaliana_447_Araport11.gene.gff3
head -n 3 ath.gff
Chr1 AT1G01010.Araport11.447 3631 5899 + 1 AT1G01010.1.Araport11.447
Chr1 AT1G01020.Araport11.447 6788 9130 - 2 AT1G01020.1.Araport11.447
Chr1 AT1G01030.Araport11.447 11649 13714 - 3 AT1G01030.1.Araport11.447
sed -i -e 's/.Araport11.447//' -e 's/.Araport11.447//' ath.gff
head -n 5 Athaliana_447_Araport11.cds_primaryTranscriptOnly.fa
>ATCG00500.1 pacid=37375748 polypeptide=ATCG00500.1 locus=ATCG00500 ID=ATCG00500.1.Araport11.447 annot-version=Araport11
ATGGAAAAATCGTGGTTCAATTTTATGTTTTCTAAGGGAGAATTGGAATACAGAGGTGAGCTAAGTAAAGCAATGGATAG
TTTTGCTCCTGGTGAAAAGACTACTATAAGTCAAGACCGTTTTATATATGATATGGATAAAAACTTTTATGGTTGGGATG
AGCGTTCTAGTTATTCTTCTAGTTATTCCAATAATGTTGATCTTTTAGTTAGCTCCAAGGACATTCGCAATTTCATATCG
GATGACACCTTTTTTGTTAGGGATAGTAATAAGAATAGTTATTCTATATTTTTTGATAAAAAAAAAAAAATTTTTGAGAT
seqkit grep -f <(cut -f 7 ath.gff ) Athaliana_447_Araport11.cds_primaryTranscriptOnly.fa | seqkit seq --id-regexp "^(.*?)\\.\\d" -i > ath.cds.fa
seqkit grep -f <(cut -f 7 ath.gff ) Athaliana_447_Araport11.protein_primaryTranscriptOnly.fa | seqkit seq --id-regexp "^(.*?)\\.\\d" -i > ath.pep.fa
makeblastdb -in ath.pep.fa -dbtype prot
blastp -num_threads 50 -db ath.pep.fa -query ath.pep.fa -outfmt 6 -evalue 1e-5 -num_alignments 20 -out ath.blastp.txt &
脚本
getgff.py
import sys
import pandas as pd
data = pd.read_csv(sys.argv[1], sep="\t", header=None,skiprows=0)
data = data[data[2] == 'mRNA']
data = data.loc[:, [0, 8, 3, 4, 6]]
data[8] = data[8].str.split(':|=|;|\|',expand=True)[1]
data[0] = data[0].str.replace('At','',regex=True)
data.to_csv(sys.argv[2], sep="\t", header=None, index=False)
gff_lens.py
import sys
import pandas as pd
data = pd.read_csv(sys.argv[1], sep="\t", header=None)
new = data[1].str.split('.').str
data['id'] = new[0].values
data['cha'] = data[3]-data[2]
for name, group in data.groupby(['id']):
if len(group) == 1:
continue
ind = group.sort_values(by='cha', ascending=False).index[1:].values
data.drop(index=ind, inplace=True)
data['order'] = ''
data['newname'] = ''
print(data.head())
for name, group in data.groupby([0]):
number = len(group)
group = group.sort_values(by=[2])
data.loc[group.index, 'order'] = list(range(1, len(group)+1))
data.loc[group.index, 'newname'] = list(
['ath2s'+str(name)+'g'+str(i).zfill(5) for i in range(1, len(group)+1)])
data['order'] = data['order'].astype('int')
data = data[[0, 'newname', 2, 3, 4, 'order', 1]]
print(data.head())
data = data.sort_values(by=[0, 'order'])
data.to_csv(sys.argv[2], sep="\t", index=False, header=None)
lens = data.groupby(0).max()[[3, 'order']]
lens.to_csv(sys.argv[3], sep="\t", header=None)
seq_newname.py
import sys
import pandas as pd
from Bio import SeqIO
data = pd.read_csv(sys.argv[1], sep="\t", header=None, index_col=6)
data.index = data.index.str[:-3]
id_dict = data[1].to_dict()
print(data.head())
seqs = []
n = 0
for seq_record in SeqIO.parse(sys.argv[2], "fasta"):
if seq_record.id in id_dict:
seq_record.id = id_dict[seq_record.id]
n += 1
else:
continue
seqs.append(seq_record)
SeqIO.write(seqs, sys.argv[3], "fasta")
print(n)
generate_conf.py
import re
import argparse
from collections import defaultdict
from collections import OrderedDict
def get_parser():
"""Get options"""
parser = argparse.ArgumentParser()
parser.add_argument('fasta',
help="fasta file name")
parser.add_argument('gff',
help="gff file name")
parser.add_argument('-p','--prefix', type=str, default="output",
help="prefix for ouput ")
return parser
def get_fasta_len(fasta):
fasta_dict = OrderedDict()
handle = open(fasta, "r")
active_sequence_name = ""
for line in handle:
line = line.strip()
if not line:
continue
if line.startswith(">"):
active_sequence_name = line[1:]
active_sequence_name = active_sequence_name.split(" ")[0]
if active_sequence_name not in fasta_dict:
fasta_dict[active_sequence_name] = 0
continue
sequence = line
fasta_dict[active_sequence_name] += len(sequence)
handle.close()
return fasta_dict
def parse_gff(gff):
gene_dict = OrderedDict()
tx_pos_dict = defaultdict(list)
CDS_dict = defaultdict(list)
handle = open(gff, "r")
for line in handle:
if line.startswith("#"):
continue
content = line.split("\t")
if len(content) <= 8:
continue
if content[2] == "transcript" or content[2] == "mRNA":
tx_id = re.search(r'ID=(.*?)[;\n]',content[8]).group(1)
tx_parent = re.search(r'Parent=(.*?)[;\n]',content[8]).group(1)
tx_parent = tx_parent.strip()
if tx_parent in gene_dict:
gene_dict[tx_parent].append(tx_id)
else:
gene_dict[tx_parent] = [tx_id]
tx_pos_dict[tx_id] = [content[0],content[3], content[4], content[6] ]
if content[2] == 'CDS':
width = int(content[4]) - int(content[3])
CDS_parent = re.search(r'Parent=(.*?)[;\n]',content[8]).group(1)
CDS_parent = CDS_parent.strip()
CDS_dict[CDS_parent].append(width)
handle.close()
return [gene_dict, tx_pos_dict, CDS_dict]
if __name__ == "__main__":
parser = get_parser()
args = parser.parse_args()
fa_dict = get_fasta_len( args.fasta)
gene_dict, tx_pos_dict, CDS_dict= parse_gff( args.gff )
gene_count = {}
len_file = open(args.prefix + ".len", "w")
gff_file = open(args.prefix + ".gff", "w")
for gene, txs in gene_dict.items():
tmp = 0
for tx in txs:
tx_len = sum(CDS_dict[tx])
if tx_len > tmp:
lst_tx = tx
tmp = tx_len
tx_chrom = tx_pos_dict[lst_tx][0]
if tx_chrom not in gene_count:
gene_count[tx_chrom] = 1
else:
gene_count[tx_chrom] += 1
tx_start = tx_pos_dict[lst_tx][1]
tx_end = tx_pos_dict[lst_tx][2]
tx_strand = tx_pos_dict[lst_tx][3]
print("{chrom}\t{gene}\t{start}\t{end}\t{strand}\t{order}\t{tx}".format(\
chrom=tx_chrom,gene=gene,start=tx_start,end=tx_end,strand=tx_strand,order=gene_count[tx_chrom],tx=lst_tx), file=gff_file )
for chrom,lens in fa_dict.items():
print("{chrom}\t{lens}\t{count}".format(\
chrom=chrom,lens=lens,count=gene_count[chrom]), file=len_file)
len_file.close()
gff_file.close()
参考文献
Sun P, Jiao B, Yang Y, et al. WGDI: A user-friendly toolkit for evolutionary analyses of whole-genome duplications and ancestral karyotypes[J]. bioRxiv, 2021.
|