SAM/BAM相关的进阶知识

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SAM/BAM相关的进阶知识相关的知识,希望对你有一定的参考价值。

参考技术A

目录

samtools 和 picard 都有对SAM/BAM文件进行排序的功能,一般都是基于坐标排序(还提供了 -n 选项来设定用reads名进行排序),先是对chromosome/contig进行排序,再在chromosome/contig内部基于start site从小到大排序,对start site排序很好理解,可是对chromosome/contig排序的时候是基于什么标准呢?

基于你提供的 ref.fa 文件中的chromosome/contig的顺序 。当你使用比对工具将fastq文件中的reads比对上参考基因组后会生成SAM文件,SAM文件包含头信息,其中有以 @SQ 开头的头信息记录,reference中有多少条chromosome/contig就会有多少条这样的记录,而且它们的顺序与 ref.fa 是一致的。

当使用samtools或picard对SAM/BAM文件进行排序时,这些工具就会读取头信息,按照头信息指定的顺序来排chromosome/contig。所以进行排序时需要提供包含头信息的SAM/BAM文件。

那么 普通情况下我们的chromosome/contig排序情况是什么样的?

一般情况下在进行SAM文件的排序时,染色体的排序到底是按照哪种规则进行排序的,不是一个很重要的问题,也不会对后续的分析产生影响,但是在执行GATK流程时,GATK对染色体的排序是有要求的,必须按照从chr1开始到chr22,最后是chrX和chrY这样的顺序,否则会报错

面对这样变态的要求,我们怎么解决?

在构造ref.fa文件时,让它按照从chr1开始到chr22,最后是chrX和chrY这样的顺序进行组织就可以了:

FLAG列在SAM文件的第二列,这是一个很重要的列,包含了很多mapping过程中的有用信息,但很多初学者在学习SAM文件格式的介绍时,遇到FLAG列的说明,常常会一头雾水

what?还二进制,这也太反人类的设计了吧!

不过如果你站在开发者的角度去思考这个问题,就会豁然开朗

在mapping过程中,我们想记录一条read的mapping的信息包括:

这些信息总结起来总共包括以下12项:

而每一项又只有两种情况,是或否,那么我可以用一个12位的二进制数来记录所有的信息,每一位表示某一项的情况,这就是原始FLAG信息的由来,但是二进制数适合给计算机看,不适合人看,需要转换成对应的十进制数,也就有了我们在SAM文件中看到的FLAG值

但是FLAG值所包含信息的解读还是要转换为12位的二进制数

SAM格式文件的第3和第7列,可以用来判断某条reads是否比对成功到了基因组的染色体,左右两条reads是否比对到同一条染色体

有两个方法可以提取未比对成功的测序数据:

对于PE数据,在未比对成功的测序数据可以分成3类:

看完这一部分,是不是有一个感觉: FLAG玩得溜,SAM文件可以处理得出神入化

首先,思考一个问题: 对于PE数据,一条测序片段(fragment)有read1和read2两条测序片段,它们俩的名字相同,那么对于这一条测序片段,对它进行mapping之后得到的SAM文件中会出现几条记录呢?

对于我的这个假设可以用以下的方法进行验证:

上面的测试结果与我们的假设吻合

但是在一次处理三代测序数据(三代测序数据是Single-End)中发现了不同:

在输出中出现了一些不太和谐的结果:有极少部分的QNAME对应2条以上的记录,这意味着存在一条read会有多条比对记录的情况,why?

对这个与预期不完全相符的结果,尝试去寻找里面的原因,其间进行了各种各样的推理、假设、验证,最终在 李恒的github 中找到了答案

这种情况容易在三代测序数据中出现

如果你用的是Single-End的数据,那么差异应该比较小,不会太明显,而在Pair-End上差异可能会比较大,之所以会产生这些差异,原因有两点:

从上面列出的两点差异可以看出,mpileup默认输出的是高质量的覆盖深度,这是有历史原因的:当场mpileup功能被开发出来就是为了与bcftools组合,将samtools mpileup的输出作为bcftools的输入用于下游的snp-calling,当然需要保证数据的质量

当然可以通过设置对应的参数使得它的属于结果与depth的一致,但是不推荐这么做

下面是对同一个样本的paired-end Fastaq文件比对结果(比对使用hisat2),hisat2和samtools分别给出的mapping rate的统计

hisat2:

samtools flagstat:

从上面可以看出,hisat2给出的mapping rate为85.40%,而samtools给出的为86.32%,两个的统计结果不一样,而且samtools的统计会大一些,what?

介四什么鬼?

我们来简单地分析一下:

hisat2中,

samtools中,

计算没问题,那问题出在哪呢?

有没有注意到上面的两个式子中的分子和分母,计算它们的差值:

分子:

分母:

发现了没有,它们的差值正好都等于samtools flagstat的输出结果的第二行:

所以,hisat2和samtools计算mapping rate的公式实际上分别为:

一般来说,我们想得到的是hisat2计算公式所得到的统计结果,hisat2统计结果在比对结束后会以标准错误形式给出,我们可以将标准错误重定向到一个log文件中,但是如果我们忘了保持这个统计结果,怎么办?

最简单的办法就是重新跑一遍hisat2,但是这样太耗费时间和计算资源了,这时我们可以利用samtools flagstat对SAM文件的统计结果,以及它的部分统计值与hisat2计算公式的关系,快速地算出准确的mapping rate:

参考资料:

(1) 【】从零开始完整学习全基因组测序数据分析:第5节 理解并操作BAM文件

(2) 【生信技能树】【直播】我的基因组(十五):提取未比对的测序数据

(3) BWA\'s README in github

(4) 【】黄树嘉《样本量重要,还是测序深度重要? 生物信息工程师可以分为多少种类型? |《解螺旋技术交流圈》精华第3期》

(5) 生信媛《HISAT2的比对率计算结果和SAMTools flagstat不同,你想过原因吗? 》

Edit Distance编辑距离(NM tag)- sam/bam格式解读进阶

sam格式很精炼,几乎包含了比对的所有信息,我们平常用到的信息很少,但特殊情况下,我们会用到一些较为生僻的信息,关于这些信息sam官方文档的介绍比较精简,直接看估计很难看懂。

今天要介绍的是如何通过bam文件统计比对的indel和mismatch信息

首先要介绍一个非常重要的概念--编辑距离

定义:从字符串a变到字符串b,所需要的最少的操作步骤(插入,删除,更改)为两个字符串之间的编辑距离

(2016年11月17日:增加,有点误导,如果一个插入有两个字符,那编辑距离变了几呢?1还是2?我又验证了一遍:确实是2

这也是sam文档中对NM这个tag的定义。

 

编辑距离是对两个字符串相似度的度量(参见文章:Edit Distance

举个栗子:两个字符串“eeba”和“abca”的编辑距离是多少?

根据定义,通过三个步骤:1.将e变为a 2.删除e 3.添加c,我们可以将“eeba”变为“abca”

image

所以,“eeba”和“abca”之间的编辑距离为3

 

回到序列比对的问题上

下面是常见的二代比对到ref的结果(bwa):

D00360:96:H2YLYBCXX:1:2110:18364:84053    353    seq1_len154_cov5    1    1    92S59M8I17M1D6M1D67M    seq30532_len2134_cov76    1    0    AAAAAAAAAAAAAAAAAAAAAAAACCCTGTCTCTAATAAAATACAAACAATTAGCCGGGCATGGTGGCACGCGCCTTTAGTCCCAGCTACTAGGGAGGCTGAGGCAGGGGAATTGTTTGAACCCGGGAGGTGGAGGTTGCAGTGAGCGGAGTTTTTTTCACTGCACTCCAGCCTGGTGACAAATCAAAAATCCATTTCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACAACAAA
DDDDDHIIIIIIII<EHHII?EHH0001111111<11<DEH1D1<FH1D<<<C<@GEHD</<11<101<1D<<C<E0D11<<1<D?1F1CC1DE110C0D1011100////0DD<1=@1=FGHCDHH<FG<D0<<<EF?CE<00<<0<D//0;<:D/////;///////;8F.;/.8.8......88.9........-8BBGADHIIHD?>D?HH<,>=HHDD,5CHDCDHD><,,,--8----8-8--    NM:i:25    MD:Z:16A21C16C0A3T15^A6^G1G0T0G3C2T0G1C41A4A2A3    AS:i:49    XS:i:42    SA:Z:seq13646_len513_cov63,125,-,103S125M21S,1,11;    RG:Z:chr22

这是ref序列

>seq1_len154_cov5
GGGAGGCTGAGGCAGGAGAATTGTTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCAAGATTGCACTCCAGCCTGGATGACAAGAGTGAAACTCTGTCTCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

cigar字段包含了序列比对的简化信息,M(匹配比对,包含match和mismatch),=(纯match),X(纯mismatch),I(插入),D(删除),还有N、P和S、H。(注:目前只在blasr比对结果中见过=和X)

根据cigar字段可以统计indel信息,但是无法统计mismatch。

这个时候就可以用到NM tag了,mismatch = NM – I - D = 25 – 8 – 1 – 1 = 15

有兴趣的可以对着cigar数一遍,下面是我无聊数着的结果,也是15个

image

image

 

使用python的pysam模块可以很容易的提取出每个比对结果的NM信息

参见之前的帖子:pysam - 多种数据格式读写与处理模块(python)

 

再次验证一遍:证明NM=len(mismatch) + len(insertion) + len(deletion), 而不是 count(mismatch) + count(insertion) + count(deletion) (count的意思是三个碱基的insertion算一个)

>>> bam_file = pysam.AlignmentFile(infile, "rb")
>>> for line in bam_file:
...     if 300 < line.query_alignment_length < 500:
...             break
... 
>>> line.query_alignment_length
321
>>> line.seq
\'CTCTTCATCACGTCACTTGTCCATGTCAATGGCTACAGGTTGGAAGTTTGGCCGCGGAGGGTGGGCAGGCAAGAGAAAGAAATCAGATAGGGCAGATGGTAGGGTAAAAGGAGGGGGTTAAGTGCAAATTGTCTACTGTTTGCAAATGGGAAGCATGTGATTGTTAAAATTTATACGATAAACCTTCTCATCATGTTGAGTCTCATGCTTGCGCCAAGAAGATCGGGTTCGGCGGGTCAAGCTGATAAGCAACTTGGGCAGCAAAGTCGTTCAGTGATACAAAATCATGTGCAAAAATCACAAGCATTCTTATAAACACCA\'
>>> line.cigarstring
\'5148H3M1I2M1I4M2I3M3I3M1D2M1I4M3I1M1I4M1D10M1D5M4I4M3D1M3I18M1I1M1I1M1I4M4D2M1I2M2I4M2I4M1I4M5I4M1I9M1I14M3I1M1I2M2I7M2I7M3I2M1I5M2I1M1I2M1I1M1I4M5I3M1I2M3I1M1I4M4I3M4I2M2I1M3I19M3I10M2I4M1I11M1D27M2I6M25212H\'
>>> line.reference_name
\'Backbone_1\'
>>> line.reference_start
7164
>>> line.reference_end
7413
>>> line.get_tag(\'NM\')
100

取出了一条长度适中的比对结果,cigar字段比较全面。

取出了比对上的ref:

>>> ref = \'CTCTCTCACCACTCCTATTCAACACAGTGTTGGAAGTTCTGGACAGGGCAATCAGGCAAGAGAAAGAAATAAAGGGTATTCAATTAGGAAAAGAGGAAGTCAAATTGTCCCCGTTTGCAGATGACATGATTGTATATTTAGAAAACCCCACTGTTTCAGCCCCAAATCTCCTTAAGCTGATAAGCAACTTCAGCAAAGTCTCAGGATACAAAATCAATGTGCAAAAATCACAAGCATTCTTATACACCA\'

先通过我自己写的cigar校正函数校正:

def formatSeqByCigar(seq, cigar):
    \'\'\'
    Input: query_alignment_sequence and cigar from sam file
    Output: formatSeq
    Purpose: make sure the pos is one to one correspondence(seq to ref)
    \'\'\'
    formatSeq = \'\'
    pointer = 0; qstart = 0; qend = -1; origin_seq_len = 0
    if cigar[0][0] == 4 or cigar[0][0] == 5:
        qstart = cigar[0][1]
    if cigar[-1][0] == 4 or cigar[-1][0] == 5:
        qend = - cigar[-1][1] - 1  # fushu count
    for pair in cigar:
        operation = pair[0]
        cigar_len = pair[1]
        if operation == 0: # 0==M
            formatSeq += seq[pointer:(pointer+cigar_len)]
            pointer += cigar_len
            origin_seq_len += cigar_len
        elif operation == 1: # 1==I
            pointer += cigar_len
            origin_seq_len += cigar_len
        elif operation == 2: # 2==D
            formatSeq += \'D\'*cigar_len
        elif operation == 4 or operation == 5: # 5==H
            origin_seq_len += cigar_len
            continue
        else:
            print (cigar)
            raise TypeError("There are cigar besides S/M/D/I/H\\n")
    return formatSeq, qstart, qend, origin_seq_len

然后分别计算deletion和mismatch:

>>> i = 0
>>> count = 0
>>> while i < len(ref):
    if formatSeq[i] != ref[i]:
        count += 1
    i += 1

>>> count
17
>>> formatSeq.count(\'D\')
11

可以看出deletion有11个,退出mismatch有6个。

随手一推insertion有83个。

>>> cigarstring = \'5148H3M1I2M1I4M2I3M3I3M1D2M1I4M3I1M1I4M1D10M1D5M4I4M3D1M3I18M1I1M1I1M1I4M4D2M1I2M2I4M2I4M1I4M5I4M1I9M1I14M3I1M1I2M2I7M2I7M3I2M1I5M2I1M1I2M1I1M1I4M5I3M1I2M3I1M1I4M4I3M4I2M2I1M3I19M3I10M2I4M1I11M1D27M2I6M25212H\'
>>> cigarstring.count(\'I\')
41
>>> cigarstring.count(\'D\')
6

用count计算显然不对。

验证成功

 

真切的明白了计算机有多么伟大,如果要是你肉眼去比对去数的话,我估计你会立马崩溃。

以上是关于SAM/BAM相关的进阶知识的主要内容,如果未能解决你的问题,请参考以下文章

Edit Distance编辑距离(NM tag)- sam/bam格式解读进阶

认识Panda3D引擎bam相关命令

SAM/BAM文件解读

提取bam/sam文件指定区域reads

SAM/BAM 格式文件内容解析

基因组比对文件(SAM/BAM)中 Soft Clip 与 Hard Clip的含义描述