做完基因组三代测序数据质控之后,我们把所有reads的Q值控制在7以上,每个read的长度在1000bp以上。我们不能明确自己的测序数据是否被其他物种污染,这个时候就要用balst比对的方法确定测序数据是否被污染,以及污染的来源。
1 下载balst+工具和数据库 在之前的一篇博客中,我详细介绍了如何本地安装NCBI的blast+工具,以及下载nr/nt库,建立本地的数据库。详情点击这里 。
在做数据污染评估的时候,我们还需要知道blast最佳结果对应的物种名,因此还需要下载分类数据库的以下两个子库:
1 2 3 4 5 6 7 8 # ascp工具下载大数据,wget命令下载小文件(md5校验文件) ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz ./ wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz.md5 ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/taxdump.tar.gz ./ wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz.md5
md5文件校验完成之后,两个数据库分别解压。注意.gz文件用gunzip,.tar.gz文件用tar -zxvf
看看这两个数据库长什么样:
第一张图片是nucl_gb.accession2taxid ,我们需要用到第二列版本信息和第三列的taxid。
第二张图片是names.dmp ,我们需要用到有taxid,学名和scientific name字符串的行。
这两个数据库怎么使用后面会详细说明。分析思路来自于CSDN的博主风风是超人 ,遗憾的是从17年开始,NCBI不再提供gi号与blastn结果的关联,博主的本地数据库可能版本比较早,采用的是gi号分析。
我将后续的代码做了修改,下载的也都是最新的数据库。总的逻辑是利用blast结果的version号,得到nucl_gb.accession2taxid数据库中的taxid号,最后通过names.dmp中的taxid号得到学名。代码方面做了少许优化,对集群服务器可能更友好一点?
2 fq文件处理和blast 质控后的数据fq文件是“@”开头的,我们要改成fa格式也就是“>”开头。取前10000条序列,每个序列有4行,只取第一行标题和第二行序列。
1 2 3 4 5 # NR表示当前行,判断除以4的余数,余数1为标题行,只输出第一个元素即reads id ;余数2则为序列行,输出所有元素也就是整条序列。最后替换@符号,文件名为test.fa zcat clean_filter.fq.gz | head -n 40000 | awk '{if(NR%4==1){print $1}else if(NR%4==2){print $0}}' | sed 's/@/>/g' >test.fa # 批量blast程序 blastn -query test.fa -out test_blastn_nt.xml -db nt -outfmt 5 -evalue 1e-5 -num_threads 20 -max_target_seqs 1
批量blast程序注意下我们输出的格式为xml格式,也就是- outfmt 5。为什么要用xml格式,因为xml格式能给出的信息最全,我们需要知道输出的版本号
evalue值根据需要设定,这里我设置1e-5
最大匹配数量注意下设置1,我们只需要知道和哪个物种相似度最高,一个输出结果就足够了(虽然设置1会有警告)。
看下blast生成的test_blastn_nt.xml这个结果文件:
虽然是第一次接触xml格式,但是感觉非常熟悉!之前做的一个微博爬虫小程序 就是扒了一个类似的html格式的文件……xml格式也挺容易解析的,可以看到比对信息以标签<Iteration>开始,以</Iteration>标签结束,<Hit>标签开始表示的是比对上的结果(因为我设置了最大比对序列数量是1,所以<Hit_num>只有1);<Hsp>标签表示某一块的比对结果(同一条序列,若干片段比对上),因此<Hsp_num>标签的数量可能不止一个。
当然,这些都可以不用关心,分析需要的信息我用红框标了出来。比较重要的是<Hit_def>标签,里面的字符串是空格隔开的,第一个元素是我们需要的物种版本号 。
3 XML文件解析 前面说了解析的思路,以下是代码的实现。因为用的python语言写的程序,我的建议是在vscode一类的编程软件中写这些代码,如果有错误可以及时调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import sysimport refrom collections import defaultdictsys.path.append("./" ) xmlfile = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/test_blastn_nt.xml" ,"r" ) outfile = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt" ,"w" ) dict1 = defaultdict(list ) for lines in xmlfile: line = lines.strip() read_id = re.match('<Iteration_query-def>.*</Iteration_query-def>' ,line) Hit_def = re.match('<Hit_def>.*</Hit_def>' ,line) if read_id != None : read_id = read_id.group() read_id = read_id.split("<" )[1 ].split(">" )[1 ] key=read_id elif Hit_def !=None : Hit_def = Hit_def.group() Hit_def = Hit_def.split("<" )[1 ].split(">" )[1 ] dict1[key].append(Hit_def) for key in dict1: outfile.write(key + "\t" + "\t" .join(dict1[key])+"\n" )
看下运行结束后解析得到的文件:
一共是两列,第一列是reads的queryID,第二列subjectID就是比对上的序列信息。可以看到第二列可以以空格为分隔符,提取第一个元素也就是物种版本号,后面会说。
为什么要把物种版本号提出来而不直接用这段内容里的物种名呢?因为不同的物种名字段数量和位置不一样 ,无法用统一的命令直接提取,精确的版本号可以对应唯一一个taxid ,从而被精准地注释上物种学名。
4 匹配物种学名 这里需要注意一个问题,blast用的nt库还有物种分类用到的两个数据库,他们的更新时间是不一致的 。也就是说,物种版本号不一定能完全匹配上taxid,而taxid也不一定能匹配上学名。
而python语言写的程序,用到字典类型数据的时候,如果没有对应的key值匹配是会报错的,不会继续执行下去。一会儿解释,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import sysimport gcsys.path.append("./" ) accession = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt" ,"r" ) accession2taxid = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/nucl_gb.accession2taxid" ,"r" ) taxid2name = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/names.dmp" ,"r" ) final_res = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt" ,"w" ) taxid_name_dict = {} for lines in taxid2name: if "scientific name" in lines: line = lines.strip().split("|" ) taxid = line[0 ].strip() name = line[1 ].strip() taxid_name_dict[taxid] = name accession_taxid_dict = {} for lines in accession2taxid: line = lines.strip().split("\t" ) TAXID = line[2 ] VERSION = line[1 ] accession_taxid_dict[VERSION] = TAXID for lines in accession: line = lines.strip().split("\t" ) version = line[1 ].split()[0 ] if version in accession_taxid_dict: taxid = accession_taxid_dict[version] if taxid in taxid_name_dict: final_res.write("\t" .join(line)+"\t" +taxid_name_dict[taxid]+"\n" ) else : final_res.write("\t" .join(line)+"\t" +"INVALID TAXID" +"\n" ) else : final_res.write("\t" .join(line)+"\t" +"INVALID ACCESSION VERSION" +"\n" ) gc.collect()
如果不加最后两个判断条件,程序会在报错的那行read序列终止。
通过比较两个输出结果文件行数是否一致来判断匹配是否完全。
两个文件输出结果一致说明匹配完成,为什么这里是9338而不是我们一开始blast的10000条序列呢?那是因为有662条序列balst结果的E值大于1e-5,没有在nt中比对上合适的序列
5 输出物种注释分布结果 到这一步就有很多种处理方法了,可以把结果文件直接用excel打开,统计reads在nt库的分布情况和比对上的物种分布。也可以直接写个python脚本做个数据统计。
统计前我们先检查一下是否存在上一步匹配失败的reads。
1 2 3 # 统计匹配失败的reads cat final_res.txt | grep "INVALID TAXID" cat final_res.txt | grep "INVALID ACCESSION VERSION"
提示有8条reads的物种版本号比对不上taxid,且都是Pyrus x bretschneideri这个物种,说明这个物种还未在nucl_gb.accession2taxid这个NCBI官方数据库中更新。在结果文件中将其替换掉。
1 2 # sed命令在原文件进行全局替换 sed -i 's/INVALID ACCESSION VERSION/Pyrus x bretschneideri/g' final_res.txt
修改完成,检查无误后,用以下python脚本统计物种注释分布:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from collections import Counter import syssys.path.append("./" ) name_file = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt" ,"r" ) res_stastics = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/stastics.txt" ,"w" ) name_list = [] for lines in name_file: line = lines.strip().split("\t" ) name = line[-1 ] name_list.append(name) count_result = Counter(name_list) count_list = list (count_result.items()) count_list.append(('Unmap' ,662 )) count_list.sort(key=lambda x:x[1 ],reverse=True ) res_stastics.write("Name\tHit_reads\tpercentage\n" ) for i in count_list: name = i[0 ] number = i[1 ] reads_num = 10000 percentage ="%.2f%%" %(100 *float (number)/float (reads_num)) res_stastics.write(name+"\t" +str (number)+"\t" +str (percentage)+"\n" )
需要注意手动添加blast失败的序列条数 ,方便最后一起统计。打开生成的stastics.txt文件:
这个数据是制表符分割的,可以用excel做一个分布统计表,或者用R做一个柱状图,底下展示结果
可以看到,10000条序列比对结果占比最高的前两条序列(橘黄色的)是细菌的核酸序列,总数达到3437条。992条序列比对上罗布麻,662条序列未比对上nt库。可以认为这个序测序数据被细菌污染,可以和测序公司battle要求重新测一遍了……
6 补充说明 2022/12/4 更新 秉着科学严谨的态度,再更新一些内容查漏补缺。
质控过滤后的reads有183万条,而我只取了前1万条。考虑到测序开头的低质量reads可能会对分析结果产生干扰(比如开头的电信号不稳定),我写了个python脚本对过滤后的数据随机 取10000条reads,这样就只有随机误差影响分析结果了。
在第2步fq文件处理部分,为了python调用方便,先解压clean_filter.fq.gz文件
1 gunzip clean_filter.fq.gz
读取解压后的文件需要49G内存,我只能在集群上处理,接着运行如下python脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import random import linecache import gcoutput_file = open ("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/random_test.fa" ,"w" ) reads_list = list (range (1 ,1834926 )) line = random.sample(reads_list,10000 ) for i in line: text1 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq" ,4 *i-3 ) text2 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq" ,4 *i-2 ) query_id = text1.split()[0 ] query_id_fa = query_id.replace("@" , ">" ) output_file.write(query_id_fa + "\n" + text2) gc.collect()
处理方式比之前多了几步,我运行了两次脚本,发现两次产生的文件大小都在135M左右,也就是随机取10000条reads产生的文件比取前10000条reads产生的文件大了40M。证明三代测序开头测得序列质量不太行 (短序列不一定质量不好,但是质量不好的序列一定是短序列),拿到随机产生的10000条reads做blast,后续步骤都一样。
2023/1/13更新 与测序公司技术人员沟通后,纠正一些误区:
三代测序数据是长读长片段,直接使用长读长序列进行blastn比对,能比对上数据库的概率会大很多(总有些区域能比对上一些同源序列,一条reads也可能比对上不同的物种)。
真核生物基因组含有大量的重复序列,对三代测序数据一般要进行随机打断成250-500bp大小,然后随机取一条进行blastn比对,如果这条序列刚好是重复序列片段,就会出现比对不上的结果(重复区域测序准确度也相对较低)。
因此,以上的方法更适合二代测序数据 的污染评估,三代数据污染评估需要在代码上考虑实现随机打断成短片段(250-500bp),再进行blastn比对nt数据库。
总的来说,这种随机取测序read进行blastn来确定数据污染的方法,只是简单粗暴的看一下样本污染情况,如果污染比例低(通常小于1%),说明数据可以使用,还需要结合GC-depth做具体分析(只有一个红色区域说明数据无污染)。blastn比对这部分内容一般不会在文章中做展示。