sam突变统计

思考过程:

好的,我现在需要解决的问题是根据给定的SAM文件中的某行数据,统计所有测序比对行相对于参考序列的突变频率,特别是每个碱基突变成特定碱基的频数和频率占比。首先,我需要理解题目中给出的数据和相关信息,然后一步步找出解决方法。

首先,题目中的例子里,第六列CIGAR是150M,这意味着该比对结果中,测序的read与参考序列完全匹配,长度为150个碱基。补充信息列中的NM:i:3表示有3个错配,即在这个比对中存在三个突变。而MD:Z:49A0A0C98则提供了具体的错配位置和类型。我需要通过解析MD字段来获取每个突变的位置以及参考碱基和替代碱基的信息,进而统计这些突变的频率。

现在,我需要明确如何解析MD字段。MD字段的格式是用于描述参考序列与比对read之间的错配情况。例如,MD:Z:49A0A0C98,这应该被拆解为不同的部分,每个部分指示匹配的长度、参考碱基以及可能的错配情况。例如,49A表示在参考序列中连续匹配49个碱基后,出现了一个参考碱基A,而read中的碱基与之不匹配。接下来的0A可能表示紧接着的位置,参考碱基是A,同样不匹配。然后0C可能表示参考碱基是C,也不匹配。最后的98表示剩下的98个碱基是匹配的。这样的话,总共有三个错配,对应NM:i:3的值。

但这里可能存在一个问题,例如,在MD字符串中的“49A0A0C98”可能需要正确解析。根据MD字段的规范,MD字符串由多个部分组成,可以是数字(表示匹配的长度)、字母(表示参考碱基的错配,如A、T、C、G),或者是带有符号的脱氧核苷酸(表示删除)。例如,MD字符串可能像“10A5AC6”这样的结构,其中10是匹配长度,A是参考碱基错配,接着5匹配,然后删除两个碱基AC,再接着6匹配。

现在回到例子中的MD:Z:49A0A0C98。可能需要拆解为:

  • 49:匹配49个碱基。

  • A:参考碱基是A,但read中的碱基与之不同,导致一个错配。

  • 0:接下来的0匹配?

这可能有问题,因为通常数字部分代表匹配的长度,而0的话可能意味着没有匹配,直接出现错配。例如,在MD字符串中,数字后面跟着的是参考碱基,表示在该位置参考碱基是这个,而read中的碱基不同。例如,49A表示在位置49(0-based或1-based?可能需要确认)之后,参考碱基是A,而read中的碱基不是A,导致错配。但后面的0A可能意味着在紧接着的位置(即位置49+1=50的位置)参考碱基是A,而read中的碱基不同。同理,0C可能表示下一个位置参考碱基是C,而read的碱基不同。然后剩下的98个碱基是匹配的。

那总共有三个错配:位置49(或可能的位置50?需要确定起始位置的问题)、位置50和位置51。每个错配的参考碱基分别是A、A、C。而对应的read中的碱基是什么呢?这需要从比对中的序列列(即第10列)中获取。例如,在SAM文件中,该行的第10列是测序read的序列,假设为某个字符串,其中在对应的错配位置上的碱基即为替代后的碱基。

所以,要统计每个位置上的突变,需要结合MD字段中的参考碱基和位置,以及read中的对应碱基。例如,在MD字符串中的每个错配块(如A、A、C)对应的位置,需要找到read中该位置的碱基,从而得到参考碱基→替代碱基的突变类型。

现在,我需要确定如何解析MD字段,并找到每个错配的位置和对应的替代碱基。然后,将所有比对行中的这些突变统计起来,计算每个突变类型(如A→T,C→G等)的频数和频率占比。

首先,处理单个行的步骤:

  1. 解析MD字符串,得到每个错配的位置(相对于参考序列的坐标)。

  2. 对于每个错配,确定参考碱基和替代碱基(来自read的序列)。

  3. 统计这些突变。

然后,对所有行重复这个过程,最后汇总统计结果。

现在,如何解析MD字符串?

假设参考序列的位置是从0开始还是从1开始?根据SAM格式的规范,MD字符串中的数字部分表示的是匹配的长度,而错配的位置需要根据这些数字来推断。例如,MD字符串“49A0A0C98”可能被拆解为:

  • 49个匹配的碱基。

  • 然后有一个错配,参考碱基是A。

  • 接着是0个匹配,即下一个位置是错配,参考碱基是A。

  • 再接着是0个匹配,下一个位置错配,参考碱基是C。

  • 最后是98个匹配。

总共有三个错配,对应NM:i:3。

那么,每个错配的位置如何计算呢?

假设参考序列的起始位置由SAM的POS字段给出(第4列),例如该行的POS是某个值,假设为p。则参考序列的起始位置是p(1-based),而read比对到参考序列的位置从p开始。CIGAR为150M,说明整个read是匹配的,没有插入或删除,所以长度是150。那么,参考序列中被覆盖的区域是p到p+149(假设POS是1-based)。

现在,在MD字符串中,每个错配的位置需要相对于参考序列的起始位置来计算。例如,第一个错配是在第49个匹配后的位置,也就是参考序列的位置p +49的位置(因为前49个碱基是匹配的,然后下一个位置是错配)。例如,参考序列中的位置p+49的碱基是A,而read中的对应位置的碱基不是A。

第二个错配,MD字符串中是0A,可能意味着在当前位置的下一个位置,参考碱基是A,而该位置没有匹配(因为数字是0)。那么,该错配的位置是前一个错配的位置+1。例如,第一个错配在p+49,第二个在p+49 +1 = p+50,参考碱基是A。第三个错配在p+50 +1 = p+51,参考碱基是C。

第三个错配后,剩下的98个碱基匹配,总长度是49(前面的匹配) +1(错配) +0(第二个错配的数字0可能有问题)?这里可能需要更仔细的分析。

可能,正确的解析方式是将MD字符串分解成各个组件:

MD字符串的解析规则是:每个组件可以是数字、单个字母(前面可能有符号表示删除)。例如,MD字符串中的每个部分由数字、字母或+字母组成。当解析时,数字表示匹配的长度,而字母(不带^)表示单个错配的参考碱基。例如,MD:Z:49A0A0C98应该被拆分为:49, A, 0, A, 0, C, 98。这可能有问题,因为0后面跟着A,可能表示在当前位置之后的0匹配之后有一个参考碱基为A的错配。或者,可能0A的组合表示在该位置的参考碱基是A,并且该位置没有匹配,所以是一个错配。这可能意味着,在连续的错配中,每个错配的位置都是连续的。

例如,分解后的MD组件是:

  • 49(匹配长度)

  • A(错配,参考碱基是A)

  • 0(匹配长度0)

  • A(错配,参考碱基是A)

  • 0(匹配长度0)

  • C(错配,参考碱基是C)

  • 98(匹配长度98)

这可能不太对,因为两个0匹配中间的两个错配可能意味着连续的错配位置。例如,当匹配长度是0时,可能意味着当前位置没有匹配,所以必须有一个错配或删除。但在这里,可能0A的组合表示该位置的参考碱基是A,并且该位置是一个错配。所以,在解析时,每个错配的位置是前一个位置的结束加上匹配长度后的位置。

例如,初始位置是p(参考序列的起始位置,1-based)。第一个组件是49,匹配长度,所以这覆盖了参考序列的位置p到p+48(因为49个碱基,从p开始,到p+48结束)。接下来的错配发生在p+49的位置,参考碱基是A。然后,下一个组件是0,所以匹配长度为0,紧接着的错配参考碱基是A,在位置p+50。然后是0,匹配长度为0,错配参考碱基是C在位置p+51。然后剩下的98匹配覆盖到p+51 +98 = p+149的位置,总长度是49+1+1+1+98=150?但原CIGAR是150M,总长度是150,所以可能各个组件相加的总和是150。

计算各组件之和:49(匹配) +1(A错配) +0(匹配)?或者每个错配占1的位置?可能需要明确每个错配在参考序列中所占的位置数目。例如,错配A占一个位置,因为替换,而如果是插入或删除则可能不同。所以,每个错配对应参考序列中的一个位置,替换的话参考碱基和read中的碱基都是1个长度,所以每个错配组件对应一个位置。因此,各个组件的总长度应为49 +1(A) +0(这如何处理?) +1(A) +0(如何处理?) +1(C) +98 = 49+1+1+1+98=150。这可能成立,因为每个错配占1的位置,而匹配的部分则是数字所表示的长度。

因此,当分解MD字符串为49A0A0C98时,各组件对应的参考序列位置是:

  • 49匹配:p到p+48(共49个碱基)。

  • A错配:位置p+49。

  • 0匹配:可能没有匹配,所以下一个错配的位置是p+49 +0 +1 = p+50?

或者,0A中的0代表匹配长度是0,所以在当前位置(即p+49后的位置p+50)开始,匹配长度为0,导致错配的位置是p+50?这可能需要更详细的解析规则。

或许更好的办法是将MD字符串中的每个元素按顺序处理,累计当前的位置。例如:

初始化当前位置为0(相对于参考序列起始位置的偏移,即起始位置是p,所以参考位置是p + current_pos)。

处理MD字符串的每个部分:

  • 首先,分割MD字符串为组件。例如,49A0A0C98可以被分解为:['49', 'A', '0', 'A', '0', 'C', '98']。这可能不是正确的分割方式,因为可能存在多位数的数字。例如,比如像“123A45”需要被分割为“123”,“A”,“45”。

正确的分割方法应该按照正则表达式来分割,例如,将数字和字母分开。例如,使用正则表达式 (\d+)|([A-Za-z]+|^[A-Za-z]+) 来分割。例如,对于MD字符串“49A0A0C98”,分割的结果应该是 ['49', 'A', '0', 'A', '0', 'C', '98']。

然后,逐个处理每个组件:

初始化current_pos = 0(参考序列的起始位置是p,所以绝对位置是p + current_pos)。

当处理每个组件时:

  • 如果是数字,则增加current_pos += 该数字的值。例如,处理“49”时,current_pos +=49。

  • 如果是字母(表示错配的参考碱基),则记录该位置为current_pos,参考碱基是该字母,然后current_pos +=1(因为每个错配占据一个位置)。

  • 如果是类似^AC这样的删除标记,则处理参考碱基被删除的部分,此时current_pos增加删除的长度(因为删除是相对于参考序列的,所以参考序列中有这些碱基,但read中没有)。

现在,回到例子中的组件列表:['49', 'A', '0', 'A', '0', 'C', '98']。

处理第一个组件“49”:current_pos +=49 → current_pos=49。

第二个组件“A”:记录位置p +49(因为参考起始位置是p),参考碱基是A。此时,该错配的位置是p+49。然后current_pos +=1 → current_pos=50。

第三个组件“0”:这是一个数字,所以current_pos +=0 → current_pos=50。

第四个组件“A”:记录位置p+50,参考碱基是A。current_pos +=1 → 51.

第五个组件“0”:current_pos +=0 →51.

第六个组件“C”:记录位置p+51,参考碱基是C。current_pos +=1 →52.

第七个组件“98”:current_pos +=98 →52+98=150,此时总长度应为150,与CIGAR的150M一致。

所以,三个错配的位置分别是p+49(参考碱基A),p+50(参考碱基A),p+51(参考碱基C)。

现在,这些位置对应的read中的碱基是什么?需要查看该行的第10列,即序列列。假设该行的序列是某个字符串,比如第10列的值是某个长度为150的字符串。那么,这三个错配的位置对应于该字符串中的哪些位置?

由于CIGAR是150M,说明read与参考序列完全匹配,没有插入或删除,所以每个位置是一一对应的。因此,在参考序列中的位置p+49对应的read中的位置是49(假设read的起始是0-based?或者可能要考虑方向,如果比对是反向互补的话。这时候需要考虑FLAG字段中的位是否指示了反向互补。例如,如果FLAG包含0x10,则read序列是反向互补的,此时需要将序列反转并取互补。

假设该行的FLAG字段为0,表示没有反向互补,那么read的序列是正向的,所以参考序列的位置p+i对应read中的第i个位置(从0开始?或者从1开始?这需要确定SAM格式中的具体规则。例如,SAM中的POS是1-based的,所以参考序列的位置p是1-based,而read的第1个碱基对应参考位置p,第二个对应p+1,依此类推。但在read的序列列(第10列)中,序列是按5'到3'方向存储的,所以如果比对是正向的,那么第i个字符对应参考位置p + (i-1)(假设i从1开始)。或者,可能i从0开始?这需要确认。

例如,假设该行的POS是100(1-based),则参考序列的起始位置是100。CIGAR是150M,所以覆盖参考位置100到249(包括两端)。read的序列长度是150,每个位置对应参考的位置100,101,...,249。对于错配的位置p+49,即100+49=149(参考位置是1-based)。对应的read中的位置是49+1=50?或者可能是49?这取决于索引方式。

如果参考序列的位置是100(1-based),那么第一个碱基对应read的第1个碱基(如果是正向比对的话),那么参考位置100对应read的位置0(如果是0-based的话),或者位置1(1-based)。但SAM中的序列列存储的是5'到3'的序列,因此如果是正向比对,则第i个字符对应参考位置p + (i-1)。例如,i=0对应p,i=1对应p+1,等等。或者可能i从1开始?例如,在SAM格式中,第10列的字符串的第一个字符对应参考位置p,第二个字符对应p+1,依此类推,如果比对是正向的话。假设是的。

那么,参考位置p+49对应的read中的位置是49(假设0-based索引)或50(1-based)。例如,假设read的第0个字符对应参考位置p(即100),第1个字符对应p+1,依此类推。那么参考位置100+49=149对应的read中的位置是49(0-based)。因此,这三个错配的位置对应的read中的碱基分别是第49、50、51位(0-based)的字符。

现在,假设该行的第10列序列为某个字符串,例如,假设序列在第49位是T,第50位是G,第51位是T。那么这三个位置的突变分别是:

  • 参考A → read T (位置49)

  • 参考A → read G (位置50)

  • 参考C → read T (位置51)

这三个突变将被统计到各自的类型中,即A→T,A→G,C→T。

现在,对于每个比对行,都需要解析MD字段,找出所有错配的位置和参考碱基,然后从序列列中获取对应的read碱基,统计每个参考碱基到替代碱基的突变次数。

接下来,我需要考虑如何处理所有的比对行:

  1. 对于每一行,首先检查是否成功比对。例如,FLAG字段中的某些位可能表示比对是否成功。如果该行不是成功比对的,则跳过。

  2. 解析CIGAR字符串,确保整个比对是M(匹配),或者处理可能存在的插入或删除,但在此问题中,例子中的CIGAR是150M,可能其他行可能更复杂。比如,如果CIGAR含有I或D的话,会影响参考序列和read的位置对应关系。这种情况下需要更复杂的处理。但题目中的例子可能只需要处理M的情况,但实际可能需要处理所有CIGAR情况。不过,对于统计突变频率,可能需要考虑CIGAR中的I和D如何处理,比如插入在read中的碱基不算作突变,删除在参考中的碱基可能属于删除型突变,但MD字段可能用符号表示。例如,如果有删除,则MD字段会包含类似AC的部分,这表示参考序列中有AC两个碱基被删除,即read中没有对应的部分。这种情况下,可能需要将删除视为参考碱基到“-”的突变?但题目中是否要考虑这些情况呢?题目中的例子只有替换型突变,所以可能需要明确是否需要处理插入和删除,或者只处理替换。根据问题描述,“突变频率统计”可能指的是替换突变,而插入和删除可能属于indel,可能需要另外处理或排除。这需要根据题目的具体要求,但问题中的例子只涉及替换,所以可能暂时仅处理替换的情况。

假设现在只处理替换的情况,即MD字段中的字母(没有^前缀的),那么:

对于每个比对行:

a. 检查FLAG是否表明是成功比对(如未设置4标志)。

b. 解析CIGAR字符串,以确定参考序列和read之间的对应关系。这可能比较复杂,例如,如果有插入或删除,则会影响位置的映射。例如,如果CIGAR中存在I(插入到read中)或D(删除,即在参考中存在但read中不存在),这时候需要调整参考和read的位置对应。

例如,假设CIGAR是10M5I20M,则read中前10个碱基匹配,接着5个插入(在参考中没有对应的位置),然后20个匹配。这时候,参考序列覆盖的长度是10 +0(插入不计入参考长度) +20 =30,而read长度是10+5+20=35。这时候,在处理MD字段时,需要考虑参考的位置如何对应到read的位置。

这确实比较复杂,但考虑到问题中的例子只有M,所以可能暂时假设CIGAR仅包含M操作,或者需要处理更一般的情况。此时可能需要一个更通用的解析方法,能够处理CIGAR中的各种操作,并正确映射参考位置到read位置。

因此,处理一般情况的步骤可能如下:

对于每个比对行:

  1. 解析CIGAR字符串,生成参考位置到read位置的映射关系。例如,根据CIGAR的操作,建立一个参考位置和read位置之间的对应表,考虑可能的插入(在read中占据位置,但不影响参考位置)和删除(在参考中占据位置,但read中没有)。

例如,初始参考位置是POS(第4列,1-based),初始read位置是0(0-based)。然后,遍历CIGAR的每个操作:

  • M:匹配,参考和read各前进长度。

  • I:插入到read,仅read前进长度。

  • D:删除在参考,仅参考前进长度。

  • N:跳过参考区域(如剪接),参考前进长度,不影响read。

  • S:软裁剪,read中的碱基被裁剪,可能在比对中不考虑。

  • H:硬裁剪,不处理。

因此,根据CIGAR操作,可以建立参考位置与read位置之间的对应关系。例如,当处理到M操作时,参考和read的位置都逐步增加,每个M中的碱基对应一个参考位置和一个read位置。当处理到I操作时,只有read的位置增加,而参考位置不变,这样这些read的碱基在参考中没有对应位置,可能属于插入突变。当处理到D或N操作时,参考位置增加,而read位置不变,这样参考中的这些位置在read中没有对应的碱基,属于删除。

因此,对于每个错配的位置(由MD字段给出参考位置),需要确定对应的read中的位置。这可能需要根据CIGAR操作来计算。

例如,假设参考位置是ref_pos,那么通过遍历CIGAR操作,可以找到对应的read_pos。这可能需要构建一个映射表,或者动态计算。

但这样处理可能比较复杂。有没有更简便的方式?

另外,MD字段中的错配位置是相对于参考序列的,而对应的read中的碱基需要根据CIGAR操作确定。例如,在存在插入或删除的情况下,参考位置和read位置可能不是一一对应的,这需要仔细处理。

不过,回到问题中的例子,CIGAR是150M,所以参考和read的位置是直接对应的。这时候,处理起来比较简单,每个参考位置对应的read位置可以直接计算。但对于更复杂的CIGAR情况,可能需要更通用的处理方式。

现在,回到问题,假设需要处理所有可能的CIGAR情况,那么如何处理?

或者,是否可以通过MD字段和read序列直接得到替代碱基,而无需处理CIGAR?例如,MD字段中的每个错配位置对应的参考碱基,而对应的read中的碱基就是该位置的替代碱基。但如何确定该参考位置对应的read中的位置呢?

例如,在存在插入或删除的情况下,MD字段中的错配位置可能指的是参考序列中的位置,而该位置是否在read中存在对应的碱基需要根据CIGAR的情况确定。例如,如果在参考序列的某个位置有一个删除(D),则read中没有对应的碱基,此时MD字段中的错配可能表示为删除(例如,^AC),此时参考碱基是AC,而read中没有,这可能属于删除类型的突变,但在问题中可能需要排除这类情况,只统计替换。

因此,可能需要分步骤处理:

步骤一:解析MD字段,提取所有替换型突变(即参考碱基到替代碱基的替换,而非插入或删除)。

步骤二:对于每个替换型突变,找到其在参考中的位置,然后根据CIGAR和POS确定对应的read中的位置,从而获取替代碱基。

步骤三:统计所有替换型突变的参考碱基和替代碱基的组合频次。

现在,如何实现步骤一,即解析MD字段中的替换型突变?

MD字段中的替换型突变由单独的字母表示,如“A”、“T”等,而删除由“^”后跟字母表示。例如,MD字符串中的组件可以是:

  • 数字:表示匹配的长度。

  • 字母(无前缀):表示替换型突变,参考碱基是该字母。

  • ^后跟字母:表示删除,参考中的碱基是这些字母,而read中没有对应碱基。

因此,在解析MD字段时,需要遍历各个组件,对于每个替换型突变(单独的字母),记录参考位置和参考碱基。对于删除(^后的字母),记录参考位置和参考碱基,但此时替代碱基为“-”或忽略。对于插入(由CIGAR中的I操作表示),MD字段中可能不体现,因为插入是read中的额外碱基,参考中没有对应位置,所以MD字段不处理插入,这部分可能需要通过比较CIGAR中的I操作来识别插入突变,此时参考碱基为“-”,替代碱基为插入的碱基。

然而,问题中的例子只涉及替换型突变,因此可能暂时只处理替换型突变,即MD字段中的单独字母的情况。而插入和删除可能需要另外处理,但可能不在当前问题范围内。

回到步骤一,解析MD字段中的替换型突变:

例如,MD:Z:49A0A0C98中的每个替换型突变是A、A、C。在解析时,这些字母对应的参考位置分别是p+49、p+50、p+51。然后,根据CIGAR和比对方向,找到对应的read中的位置,并获取替代碱基。

现在,假设比对是正向的,FLAG未设置反向互补标志。则参考位置p+i对应read中的第i位(假设i从0开始?或者根据POS的位置,可能需要更精确的计算)。

例如,参考的起始位置是POS(第4列,1-based),所以参考位置p = POS。CIGAR为150M,所以覆盖的参考位置是p到p+149(1-based)。read的长度是150,所以每个参考位置p+i-1(i从1到150)对应read中的第i-1位(0-based)。

因此,参考位置p+49(1-based)对应read中的位置49(0-based)?例如,当p=100时,参考位置100+49=149(1-based),对应的read中的位置是49(0-based)。

因此,在例子中的三个错配位置对应的read中的碱基分别是第49、50、51位(0-based)。这些位置的碱基可以从第10列的序列中获取。

所以,针对替换型突变,处理过程如下:

对于每个比对行:

  1. 检查FLAG是否表示未比对成功(如FLAG的第四位是否设置),如果未成功,跳过。

  2. 解析CIGAR,确定参考和read的位置对应关系。假设CIGAR为M,则参考和read的位置一一对应。

  3. 解析MD字段,提取所有替换型突变的参考位置(相对于参考序列的起始位置)和参考碱基。

  4. 根据比对方向(由FLAG判断是否反向互补),确定read中的碱基是否需要取反向互补。

  5. 对于每个替换型突变,计算其在read中的位置,并获取对应的碱基。

  6. 统计参考碱基→替代碱基的次数。

现在,处理比对方向的问题。如果比对是反向互补的(FLAG包含0x10),则read的序列需要被反转并取互补。例如,在例子中,如果FLAG是反向互补的,那么read中的第49位(0-based)对应的碱基是原序列的反向互补后的对应位置的碱基。例如,原序列是第10列字符串,假设为s,那么反向互补后的序列为reverse_complement(s),此时参考位置p+49对应的碱基是reverse_complement(s)[49]?或者需要重新计算位置?

这可能比较复杂。例如,在反向比对的情况下,CIGAR中的M操作的方向是相对于参考序列的。此时,read的序列在存储时是5'→3'方向,而比对到参考序列的反向链时,需要将read序列反转互补以匹配参考的正向链。因此,当比对是反向互补时,参考位置p+i对应的read中的位置是length - (i+1)。例如,如果read长度是150,则参考位置p对应read中的位置149(0-based,反转后的)。

例如,假设比对是反向的,则参考的起始位置是p,但read序列是反转互补的。此时,参考位置p+i(相对于参考序列的正向)对应read中的位置(length -1 -i)。例如,i=0(参考位置p)对应read的位置149,i=1对应148,依此类推。因此,在反向的情况下,参考位置p+49对应的read中的位置是150 -1 -49 =100。这可能吗?

这需要更仔细的考虑。例如,假设read的长度是L,那么反向比对时,原read的序列是s,反转互补后变成rc(s),此时参考位置p对应rc(s)[0],p+1对应rc(s)[1],依此类推,直到p+L-1对应rc(s)[L-1]。或者是否参考位置p+i对应rc(s)[i]?这可能需要更详细的分析。

假设比对是反向的,那么read的序列存储的是原始测序的5'→3'方向,但当比对到参考的反向链时,实际匹配的是其互补链。因此,在比对到反向链时,参考序列的正向链的碱基与read的反向互补序列匹配。因此,参考中的位置p+i的正向碱基与read中的位置L-1 -i的碱基互补。

例如,参考位置p+i的正向链的碱基是A,那么read中的对应位置L-1 -i的碱基应该是T(互补),否则就是一个错配。此时,在MD字段中记录的参考碱基是参考的正向链的碱基,而read中的碱基是互补后的。

例如,假设在反向比对的情况下,参考正向链的碱基是A,而read的对应位置碱基是T(互补),则这将被视为匹配,不会被记录为错配。如果read的碱基是C,则MD字段会记录参考碱基A,而替代碱基是C的反向互补碱基G?

或者,可能MD字段中的参考碱基是参考的正向链的碱基,而替代碱基是read的原始序列中的碱基(未取互补)?或者是否MD字段中的参考碱基是相对于比对链的?

这可能需要明确MD字段的定义。根据SAM格式的文档,MD字段中的参考碱基是在参考序列的正向链上的碱基,无论比对的方向如何。而替代碱基是read中的碱基,如果比对是反向互补的,那么read中的碱基已经被反转和取互补,因此在MD字段中的错配参考碱基对应参考的正向链,而替代碱基是反转互补后的read中的碱基。

或者,可能read中的序列在存储时已经根据比对方向进行了调整。例如,如果比对是反向的,则read的序列存储的是反转互补后的序列。例如,SAM格式中的第10列序列是5'→3'方向的,而如果比对到参考的反向链,则存储的序列是原测序的互补反向,以便与参考的正向链匹配。因此,在MD字段中的参考碱基是参考的正向链的碱基,而替代碱基是反转互补后的read序列中的碱基,即第10列存储的是反转互补后的序列。因此,在这种情况下,当比对是反向时,替代碱基可以直接从第10列中获取,无需额外处理。

例如,假设在反向比对的情况下,原read的序列是ATCG,存储时会被转换为CGAT(反转互补),然后比对到参考的正向链。此时,MD字段中的参考碱基是参考的正向链的碱基,而替代碱基是CGAT中的对应位置。

因此,在解析MD字段时,无论比对方向如何,替代碱基都可以直接从第10列的序列中获取,因为该列已经根据比对方向进行了调整。

这样处理的话,无论比对方向如何,替代碱基的正确性都得到保证。例如,如果比对是反向的,则第10列是原read的反转互补,所以替代碱基已经对应于参考的正向链的互补。

因此,在处理替换型突变时,无需考虑比对方向对替代碱基的影响,直接使用第10列的序列中的对应位置的碱基即可。

综上,处理步骤可以简化为:

对于每个替换型突变:

  1. 解析MD字段得到参考位置(相对于参考序列起始的偏移量)。

  2. 根据参考起始位置(POS)和偏移量,计算绝对参考位置。

  3. 根据CIGAR操作,找到该参考位置对应的read中的位置。这可能比较复杂,需要考虑CIGAR中的M、D、I等操作对位置映射的影响。

  4. 获取read中的对应位置的碱基,作为替代碱基。

  5. 统计参考碱基→替代碱基的次数。

现在,问题是如何根据CIGAR操作,将参考位置转换为read中的位置。这需要遍历CIGAR操作,并跟踪参考和read的位置。

例如,假设参考起始位置是pos(1-based),读的起始位置是0(0-based)。然后,处理每个CIGAR操作:

例如,CIGAR是150M,那么参考和read的位置是线性对应的。参考位置从pos到pos+149,read位置从0到149。因此,参考位置pos + i对应read位置i(0-based)。

如果CIGAR是更复杂的,例如,20M3I10M2D5M,那么参考和read的对应关系将不同:

  • 20M:参考位置pos到pos+19,read位置0到19。

  • 3I:read位置增加3,参考位置不变。因此,这三个碱基属于插入,参考中没有对应的位置。

  • 10M:参考位置pos+20到pos+29,read位置20到29.

  • 2D:参考位置增加2,read位置不变。这两个参考位置没有对应的read碱基。

  • 5M:参考位置pos+30到pos+34,read位置30到34.

因此,参考位置pos+30对应read位置30,参考位置pos+31对应read位置31,等等。

假设现在有一个参考位置,例如,pos+25,属于哪个CIGAR部分?

在20M之后是3I(跳过,参考位置不变),接着是10M:参考位置从pos+20到pos+29,对应read位置20到29. 所以,参考位置pos+25对应read位置20 + (25-20) =25?是的,在10M的部分,参考位置从pos+20开始,对应的read位置从20开始。因此,参考位置pos+25在该部分中的偏移是5,对应read位置20+5=25.

因此,通过遍历CIGAR操作,可以建立一个映射,将参考位置转换为read位置。

这可能需要编写一个函数,给定参考位置,返回对应的read位置,或确定该参考位置是否在read中存在对应的碱基(比如,如果该参考位置处于D或N的区域,则没有对应的read碱基)。

这似乎比较复杂,但对于问题中的统计,可能需要处理所有可能的CIGAR情况,以正确获取替代碱基。

因此,现在需要设计一个方法来将参考位置转换为read位置,考虑CIGAR中的所有操作。

算法思路:

输入:参考起始位置(pos,1-based),CIGAR字符串,目标参考位置(target_ref_pos,1-based)。

输出:对应的read中的位置(0-based),或者None(如果目标参考位置没有被read覆盖,比如位于D或N区域)。

步骤:

  1. 初始化当前_ref_pos = pos (1-based)

  2. 初始化 current_read_pos =0 (0-based)

  3. 遍历CIGAR的每个操作:

a. 操作类型为M或X或=:处理匹配/替换。

  • 对于每个长度中的每个碱基:

  • current_ref_pos和current_read_pos同时递增。

  • 如果current_ref_pos == target_ref_pos,返回current_read_pos.

  • 否则,继续。

b. 操作类型为I:插入到read中,参考位置不变。

  • current_read_pos +=长度。

c. 操作类型为D或N:删除或跳过参考中的碱基。

  • 检查是否target_ref_pos在current_ref_pos到current_ref_pos +长度 -1之间。

  • 如果是,则目标参考位置位于删除区域,没有对应的read位置,返回None。

  • current_ref_pos +=长度。

d. 操作类型为S或H:软或硬裁剪,不影响参考位置。

  • current_read_pos +=长度(仅S操作影响,H不处理)。
  1. 如果遍历完所有CIGAR操作仍未找到目标参考位置,则返回None(该参考位置不在比对覆盖的区域内)。

这可能需要将每个CIGAR操作分解,并逐个处理每个碱基的位置变化。但这样处理可能效率较低,尤其对于大长度的操作。因此,可以优化为按块处理,而不是逐个碱基:

例如,对于每个操作:

  • M:参考和read各前进长度。覆盖的参考区间是[current_ref_pos, current_ref_pos + op_len -1]。如果target_ref_pos在该区间内,则对应的read位置是 current_read_pos + (target_ref_pos - current_ref_pos). 然后更新current_ref_pos += op_len,current_read_pos += op_len.

  • I:read前进op_len,参考不变。如果当前参考位置是target_ref_pos,则无对应(因为参考没有前进)。更新current_read_pos += op_len.

  • D/N:参考前进op_len,read不变。检查target_ref_pos是否在current_ref_pos到current_ref_pos + op_len -1之间。如果是,返回None。否则,current_ref_pos += op_len.

例如,优化后的算法:

current_ref_pos = pos

current_read_pos =0

for (op, op_len) in cigar_ops:

if op in 'M=X': # 这些操作对应参考和read同时前进

start_ref = current_ref_pos

end_ref = current_ref_pos + op_len -1

if start_ref <= target_ref_pos <= end_ref:

target_ref_pos在此操作范围内

offset = target_ref_pos - start_ref

return current_read_pos + offset

current_ref_pos += op_len

current_read_pos += op_len

elif op == 'I':

插入到read,参考不变

current_read_pos += op_len

elif op in 'DN':

start_ref = current_ref_pos

end_ref = current_ref_pos + op_len -1

if start_ref <= target_ref_pos <= end_ref:

目标参考位置被删除或跳过,无对应的read位置

return None

current_ref_pos += op_len

elif op == 'S':

current_read_pos += op_len

elif op == 'H':

pass # 硬裁剪,不影响read位置

else:

其他操作,如P等,暂不处理

pass

return None

这样,就可以快速定位到目标参考位置是否存在于CIGAR覆盖的参考区域中,并找到对应的read位置。

综上,处理每个替换型突变的步骤如下:

对于每个比对行:

  1. 解析必要的字段:FLAG, POS(参考起始位置), CIGAR, MD, 序列。

  2. 判断该行是否是主比对(如FLAG中的secondary或supplementary比对是否设置),若是则跳过。

  3. 解析MD字段,提取所有替换型突变的参考位置(相对于参考起始位置的偏移量)。

  4. 对于每个替换型突变的参考位置:

a. 绝对参考位置 = POS + 偏移量。

b. 使用CIGAR解析算法,找到对应的read位置。

c. 如果无法找到对应的read位置(例如,位于D区域),则跳过。

d. 否则,获取read中的对应碱基。

e. 统计参考碱基到替代碱基的次数。

  1. 处理所有错配后,继续下一行。

现在,如何解析MD字段中的替换型突变的参考位置?

例如,MD字段是49A0A0C98,那么如何得到每个替换型突变的参考位置偏移量?

解析MD字段的算法:

初始化:

  • current_offset =0 (相对于参考起始位置的偏移量,参考起始位置是POS,1-based)

MD字符串分解为各个组件,例如,49、A、0、A、0、C、98.

逐个处理组件:

  • 如果是数字,则current_offset +=该数字。

  • 如果是字母,则表示有一个替换型突变,参考碱基是该字母,参考位置的偏移量是current_offset。之后current_offset +=1。

  • 如果是^后跟字母,表示删除,参考碱基是这些字母,此时current_offset +=删除的长度。这些属于删除型突变,可能需要另外处理。

例如,处理MD:Z:49A0A0C98的组件:

组件列表:49, A, 0, A, 0, C, 98.

处理:

  1. 组件49:current_offset +=49 → 49.

  2. 组件A:替换型突变,参考位置偏移量是49。记录该突变,之后current_offset +=1 →50.

  3. 组件0:数字0,current_offset +=0 →50.

  4. 组件A:替换型突变,参考位置偏移量50 →记录,current_offset +=1 →51.

  5. 组件0:current_offset +=0 →51.

  6. 组件C:替换型突变,参考位置偏移量51 →记录,current_offset +=1 →52.

  7. 组件98:current_offset +=98 →52+98=150.

这样,三个替换型突变的参考位置偏移量是49、50、51,对应的参考碱基是A、A、C.

因此,每个突变的绝对参考位置是POS +49, POS+50, POS+51.

然后,用CIGAR解析算法,将这些绝对参考位置转换为read中的位置。

在例子中,CIGAR是150M,所以这些绝对参考位置对应的read位置是49、50、51(0-based),因此,替代碱基是序列列的第49、50、51位的字符。

统计这三个突变:参考碱基A→替代碱基(比如T、G、T等),然后累加到频次表中。

现在,将这些步骤综合起来,编写一个处理所有比对行的流程:

流程:

  1. 初始化一个字典,例如mutation_counts,键是(ref_base, alt_base),值是出现次数。

  2. 遍历SAM文件的每一行:

a. 跳过以@开头的头部行。

b. 解析该行的字段:FLAG, POS, CIGAR, ..., MD, SEQ等。

c. 检查比对是否成功(例如,FLAG未设置UNMAP标志)。

d. 解析MD字段,获取所有替换型突变的参考位置偏移量和参考碱基。

e. 对于每个替换型突变:

i. 绝对参考位置 = POS + 偏移量。

ii. 使用CIGAR和POS,计算对应的read位置。

iii. 如果该位置有效(即存在对应的read碱基):

  • 获取参考碱基(来自MD字段)。

  • 获取替代碱基(来自SEQ字段的对应位置)。

  • 如果比对是反向的(FLAG的0x10位设置),则需要取SEQ的对应位置的字符,或者是否需要反转取互补?根据之前讨论,SEQ字段已经存储了比对到参考链的序列,所以无需反转。

  • 将(ref_base, alt_base)加入mutation_counts字典,计数+1。

  1. 处理完所有行后,统计每个突变类型的频率,即每个(ref, alt)的次数除以总突变数。

现在,关于比对方向的问题:SEQ字段存储的是否已经是反转互补后的序列?

根据SAM格式的规范,如果比对到反向链(FLAG的0x10位设置),则SEQ字段存储的序列是原read的反向互补。例如,原read是ATCG,比对到反向链时,SEQ字段存储的是CGAT。因此,此时无需再处理SEQ字段的碱基,直接取对应位置的碱基即可,因为已经反转互补过了。

因此,在反向比对的情况下,替换型突变的替代碱基可以直接从SEQ字段中获取,而无需额外处理。

综上,替代碱基的正确性已经由SEQ字段的存储方式保证,无论比对方向如何。

现在,编写伪代码:

for line in sam_file:

if line starts with '@':

continue

fields = line.split('\t')

flag = int(fields[1])

if (flag & 0x4) != 0:

continue # 未比对成功

pos = int(fields[3]) # 参考起始位置,1-based

cigar = fields[5]

seq = fields[9]

md = next((field for field in fields[11:] if field.startswith('MD:Z:')), None)

if md is None:

continue # 没有MD字段

md_str = md.split(':')[-1] # 例如,MD:Z:49A0A0C98 → '49A0A0C98'

解析MD字段,获取替换型突变的参考偏移量和参考碱基

mutations = parse_md(md_str)

for (offset, ref_base) in mutations:

ref_pos = pos + offset # 参考的绝对位置,1-based

使用CIGAR和pos,计算ref_pos对应的read位置(0-based)

read_pos = get_read_position(pos, cigar, ref_pos)

if read_pos is None:

continue # 该参考位置无对应的read位置(如位于删除区域)

if read_pos >= len(seq):

continue # 防止越界,虽然理论上CIGAR和SEQ长度一致

alt_base = seq[read_pos]

key = (ref_base, alt_base)

mutation_counts[key] +=1

统计完成后,计算每个突变类型的频率:

total = sum(mutation_counts.values())

for (ref, alt), count in mutation_counts.items():

freq = count / total

print(f"{ref}->{alt}: {count} ({freq:.2%})")

现在,问题是如何实现parse_md函数,以及get_read_position函数。

parse_md函数的实现:

需要将MD字符串解析成替换型突变的列表,每个元素包含偏移量和参考碱基。

例如,对于MD字符串'49A0A0C98',解析得到:

[

(49, 'A'),

(50, 'A'),

(51, 'C')

]

实现parse_md函数:

import re

def parse_md(md_str):

分割MD字符串为数字、字母、^字母等组件

components = re.findall(r'(\d+)|([A-Za-z])|(^[A-Za-z]+)', md_str)

该正则表达式匹配数字、单个字母、或^后跟字母的组合

current_offset =0

mutations = []

for component in components:

num_part, letter_part, delete_part = component

if num_part:

current_offset += int(num_part)

elif letter_part:

替换型突变

ref_base = letter_part.upper()

mutations.append( (current_offset, ref_base) )

current_offset +=1

elif delete_part:

删除,参考碱基是delete_part[1:], 可能多个碱基

例如,^AC表示删除两个碱基A和C

这里不需要处理,因为只关心替换型突变

current_offset += len(delete_part) -1 # -1因为^占一个字符

return mutations

测试该函数:

md_str = '49A0A0C98'

components = re.findall(r'(\d+)|([A-Za-z])|(^[A-Za-z]+)', md_str)

components会是 [('49', '', ''), ('', 'A', ''), ('0', '', ''), ('', 'A', ''), ('0', '', ''), ('', 'C', ''), ('98', '', '')]

然后遍历这些组件:

初始current_offset=0.

处理第一个组件:num_part='49' → current_offset +=49 →49.

第二个组件:letter_part='A' → 添加(49, 'A'),current_offset=50.

第三个组件:num_part='0' → current_offset +=0 →50.

第四个组件:letter_part='A' → 添加(50, 'A'),current_offset=51.

第五个组件:num_part='0' → current_offset +=0 →51.

第六个组件:letter_part='C' → 添加(51, 'C'),current_offset=52.

第七个组件:num_part='98' → current_offset +=98 →150.

所以,返回的mutations列表是[(49, 'A'), (50, 'A'), (51, 'C')],正确。

这样,parse_md函数可以正确解析替换型突变。

接下来,实现get_read_position函数,根据CIGAR、参考起始位置pos(1-based)和目标参考位置ref_pos(1-based),返回对应的read位置(0-based)。

实现该函数需要解析CIGAR字符串,并按照前面讨论的算法进行处理。

例如,CIGAR解析的步骤:

首先,将CIGAR字符串拆分成操作列表:

例如,CIGAR '150M' → [ (150, 'M') ]

拆分函数:

def parse_cigar(cigar_str):

cigar_ops = []

buffer = ''

for c in cigar_str:

if c.isdigit():

buffer += c

else:

op = c

length = int(buffer)

cigar_ops.append( (length, op) )

buffer = ''

return cigar_ops

测试:parse_cigar('150M') → [(150, 'M')]

然后,实现get_read_position函数:

def get_read_position(ref_start_pos, cigar_str, target_ref_pos):

ref_start_pos是参考起始位置(1-based)

target_ref_pos是要查找的参考绝对位置(1-based)

返回对应的read位置(0-based)或None

current_ref_pos = ref_start_pos # 1-based

current_read_pos =0 # 0-based

cigar_ops = parse_cigar(cigar_str)

for (op_len, op) in cigar_ops:

if op in ('M', '=', 'X'):

这些操作同时推进ref和read

start_ref = current_ref_pos

end_ref = current_ref_pos + op_len -1 # 1-based

if start_ref <= target_ref_pos <= end_ref:

找到目标位置

offset_in_op = target_ref_pos - start_ref

return current_read_pos + offset_in_op

否则,更新位置

current_ref_pos += op_len

current_read_pos += op_len

elif op == 'I':

插入,只推进read

current_read_pos += op_len

elif op in ('D', 'N'):

删除或跳过参考区域

start_ref = current_ref_pos

end_ref = current_ref_pos + op_len -1

if start_ref <= target_ref_pos <= end_ref:

目标位置在删除区域,无对应read位置

return None

current_ref_pos += op_len

elif op == 'S':

软裁剪,推进read位置

current_read_pos += op_len

elif op == 'H':

硬裁剪,不处理

pass

else:

其他操作,如P等,暂不处理

pass

遍历完所有操作未找到,返回None

return None

测试该函数:

例如,CIGAR为150M,ref_start_pos=100,target_ref_pos=149(1-based) → 100+49=149,偏移量是49,对应的read位置是49(0-based)→ 返回49.

另一个例子,CIGAR为20M3I10M,ref_start_pos=100,target_ref_pos=125 → 20M后,current_ref_pos=120,current_read_pos=20。接着3I,current_read_pos=23。然后10M覆盖ref的120到129。target_ref_pos=125在120到129之间,offset_in_op=125-120=5 → read_pos=23+5=28 → 返回28.

这样,该函数应该能正确解析CIGAR并找到对应的read位置。

综上,将所有步骤综合起来,就能统计所有比对行中的替换型突变,并计算每个突变类型的频数和频率占比。

回到题目中的例子,某行的MD字段为49A0A0C98,假设该行的第10列序列为:

假设SEQ列为...(假设在位置49、50、51的碱基分别为T、G、T,则这三个突变分别是A→T、A→G、C→T,那么统计中这三个突变将被计数各一次。总突变数是3,所以每个突变的频率占比分别为1/3 ≈33.33%。

在实际处理时,需要遍历所有比对行,解析每个符合条件的行中的MD字段,并统计所有突变类型。最后,汇总所有突变类型的计数,计算其占总突变数的比例。

现在,可能存在的边界情况或需要注意的问题:

  1. MD字段的解析是否正确,尤其是连续的数字和字母,例如,像0A这样的情况,可能表示在参考的某个位置没有匹配,导致替换。例如,在解析时,0之后的A是否被正确视为替换型突变。

根据前面的parse_md函数,当处理到0A时,组件会被拆分为num_part='0',然后letter_part='A'。处理num_part时,current_offset +=0,然后处理letter_part,记录current_offset的位置,并增加1。这样,0A将被正确解析为在current_offset的位置发生替换。

例如,假设current_offset是50,处理num_part=0后,current_offset仍然是50。然后处理A,记录(50, 'A'),current_offset变为51.

  1. CIGAR中存在I或D操作时,参考位置和read位置的映射是否正确。例如,插入导致read的位置跳跃,而删除导致参考位置跳跃。

根据get_read_position函数的实现,能正确处理这些情况。

  1. 是否考虑软裁剪(S操作)和硬裁剪(H操作)。软裁剪影响read的位置,但硬裁剪不影响。

根据函数中的处理,软裁剪会增加read的位置,而硬裁剪不会。

  1. 参考位置是否超出SEQ的长度。例如,由于CIGAR和SEQ长度不一致,或者解析错误,可能导致read_pos超出序列范围。因此,在获取alt_base时,需要检查read_pos是否在SEQ的有效索引范围内。

具体步骤

综上,以上的步骤应该能够正确统计所有测序比对行中的替换型突变,并计算每个参考碱基到特定替代碱基的频次和频率占比。

要统计SAM文件中所有测序比对行相对于参考序列的突变频率,需解析CIGAR和MD字段,并结合序列信息。以下是具体步骤:

步骤详解

  1. 解析MD字段
    MD字段(如49A0A0C98)表示错配位置和参考碱基。解析后得到每个替换型突变的参考偏移量及参考碱基。

  2. 确定参考位置和对应Read位置
    结合参考起始位置(POS)和CIGAR字符串,计算每个突变在参考序列中的绝对位置,并转换为Read中的位置。

  3. 获取替代碱基
    根据Read位置从序列列(SEQ)中提取对应的替代碱基。

  4. 统计突变类型
    统计每个参考碱基到替代碱基的频数,最后计算频率占比。

代码实现

import re
from collections import defaultdict

def parse_cigar(cigar_str):
    """解析CIGAR字符串,返回操作列表"""
    cigar_ops = []
    buffer = ''
    for c in cigar_str:
        if c.isdigit():
            buffer += c
        else:
            op = c
            length = int(buffer)
            cigar_ops.append((length, op))
            buffer = ''
    return cigar_ops

def get_read_position(ref_start_pos, cigar_str, target_ref_pos):
    """根据CIGAR确定参考位置对应的Read位置"""
    current_ref_pos = ref_start_pos  # 1-based
    current_read_pos = 0  # 0-based
    cigar_ops = parse_cigar(cigar_str)
    for (op_len, op) in cigar_ops:
        if op in ('M', '=', 'X'):
            start_ref = current_ref_pos
            end_ref = current_ref_pos + op_len - 1
            if start_ref <= target_ref_pos <= end_ref:
                offset = target_ref_pos - start_ref
                return current_read_pos + offset
            current_ref_pos += op_len
            current_read_pos += op_len
        elif op == 'I':
            current_read_pos += op_len
        elif op in ('D', 'N'):
            start_ref = current_ref_pos
            end_ref = current_ref_pos + op_len - 1
            if start_ref <= target_ref_pos <= end_ref:
                return None
            current_ref_pos += op_len
        elif op == 'S':
            current_read_pos += op_len
        elif op == 'H':
            pass
    return None

def parse_md(md_str):
    """解析MD字符串,返回替换型突变的(偏移量,参考碱基)列表"""
    components = re.findall(r'(\d+)|([A-Za-z])|(\^[A-Za-z]+)', md_str)
    current_offset = 0
    mutations = []
    for component in components:
        num_part, letter_part, delete_part = component
        if num_part:
            current_offset += int(num_part)
        elif letter_part:
            ref_base = letter_part.upper()
            mutations.append((current_offset, ref_base))
            current_offset += 1
        elif delete_part:
            current_offset += len(delete_part) - 1  # 删除符号^后跟多个碱基
    return mutations

def main():
    mutation_counts = defaultdict(int)
    
    # 示例:替换为实际读取SAM文件的代码
    with open('your_file.sam', 'r') as f:
        for line in f:
            if line.startswith('@'):
                continue
            fields = line.strip().split('\t')
            if len(fields) < 12:
                continue
            flag = int(fields[1])
            if flag & 0x4:  # 未比对成功
                continue
            pos = int(fields[3])
            cigar = fields[5]
            seq = fields[9]
            md_field = next((f for f in fields[11:] if f.startswith('MD:Z:')), None)
            if not md_field:
                continue
            md_str = md_field.split(':')[-1]
            mutations = parse_md(md_str)
            for offset, ref_base in mutations:
                ref_pos = pos + offset  # 1-based
                read_pos = get_read_position(pos, cigar, ref_pos)
                if read_pos is None or read_pos >= len(seq):
                    continue
                alt_base = seq[read_pos].upper()
                mutation_counts[(ref_base, alt_base)] += 1
    
    total = sum(mutation_counts.values())
    if total == 0:
        print("No mutations found.")
        return
    
    print("Mutation Frequencies:")
    for (ref, alt), count in sorted(mutation_counts.items()):
        freq = count / total
        print(f"{ref} -> {alt}: {count} ({freq:.2%})")

if __name__ == '__main__':
    main()

结果解释

运行上述代码后,输出将显示每个突变类型(如A -> T)的频数和频率占比。例如:

Mutation Frequencies:
A -> T: 1 (33.33%)
A -> G: 1 (33.33%)
C -> T: 1 (33.33%)

关键点

  1. MD解析:使用正则表达式正确分解MD字段,处理连续数字和字母。
  2. CIGAR处理:通过遍历CIGAR操作,准确映射参考位置到Read位置。
  3. 方向处理:SAM文件中的序列已根据比对方向调整,无需额外反转互补。
  4. 统计与输出:汇总所有突变类型,计算频率并以易读格式输出。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容