1. 为什么还要聊VarScan这款“老牌”变异检测工具如果你最近几年才接触生信分析可能满耳朵都是GATK、DeepVariant、Strelka这些名字VarScan听起来像是上个时代的产物。但我在实际项目里反复验证过一件事工具没有绝对的新旧只有合不合适。VarScan最早是2009年发表在Genome Research上的一款基于Java的变异检测软件专门针对二代测序数据做SNP和Indel的识别尤其擅长处理肿瘤-正常配对样本的体细胞突变检测。它的核心优势在于逻辑清晰、参数透明、对低覆盖度数据的容忍度比较好而且运行速度在当时算是相当能打的。我之所以重新翻出VarScan的文献来细读是因为手头一个肿瘤panel的项目遇到了麻烦样本测序深度参差不齐有些区域只有30×左右用某些新工具跑出来的假阳性高得离谱。回头用VarScan的somatic模式配合合理的过滤策略结果反而干净很多。这让我意识到很多新手一上来就追新工具却忽略了老工具在特定场景下的稳定性。这篇文章我会把VarScan的核心原理、文献里的关键设计、实操流程、参数调优和踩坑经验全部拆开讲清楚适合刚入门变异检测的生信新手也适合正在做肿瘤突变负荷分析的老手参考。VarScan的全称是Variant Scanner最初设计目标是处理Illumina和SOLiD平台的比对结果输入是SAM/BAM格式输出是VCF或自定义的变异列表。它不像GATK那样依赖复杂的本地重组和BQSR流程而是直接基于比对结果做碱基级别的统计检验。这种“轻量级”的设计思路让它在处理异质性高的肿瘤样本时反而有独特的优势。文献里提到的一个核心观点是肿瘤样本的突变等位基因频率往往远低于50%传统的二倍体假设会漏掉大量低频突变而VarScan的统计模型专门为此做了优化。2. VarScan文献核心思路拆解与设计哲学2.1 从“计数”到“统计检验”的底层逻辑VarScan的检测逻辑说穿了并不复杂它在每个位点上统计四种碱基的测序读段支持数然后通过Fisher精确检验或卡方检验来判断该位点是否存在非参考碱基的显著富集。对于肿瘤-正常配对样本它会分别计算肿瘤和正常样本的等位基因频率再用配对检验来判断突变是体细胞来源还是胚系来源。文献里详细描述了三种检测模式mpileup2snp用于单样本SNP检测mpileup2indel用于Indel检测somatic用于配对样本的体细胞突变检测。这里有个关键设计值得注意VarScan并没有采用贝叶斯模型去估计基因型概率而是直接基于测序深度和等位基因计数做频率阈值判断。这种做法的好处是计算速度极快一个30×的全外显子组数据在普通服务器上几十分钟就能跑完坏处是对测序错误的建模不够精细所以必须配合严格的过滤条件。文献里给出的推荐阈值是肿瘤样本中变异等位基因频率≥5%正常样本中≤5%且肿瘤样本中支持变异的读段数≥3。这些数字看起来简单但背后是大量模拟和真实数据验证的结果。2.2 为什么VarScan对低频突变更友好肿瘤样本的突变检测有个绕不开的难题肿瘤纯度。如果肿瘤细胞只占样本的30%那么一个杂合突变在测序数据里的等位基因频率可能只有15%左右甚至更低。很多基于二倍体基因型假设的工具会直接把这类位点判为测序错误或噪声。VarScan的设计从一开始就考虑到了这一点它的somatic模式不要求肿瘤样本的等位基因频率达到某个固定值而是通过正常样本的频率作为背景对照只要肿瘤样本的频率显著高于正常样本就判定为候选体细胞突变。文献里有一组模拟数据很能说明问题在肿瘤纯度20%、测序深度50×的条件下VarScan对等位基因频率5%的突变检出率仍然能达到70%以上而某些基于贝叶斯模型的新工具在同样条件下检出率不到50%。当然代价是假阳性率会有所上升这就需要后续的过滤步骤来平衡。我在实际项目里的经验是VarScan的原始输出必须经过至少三轮过滤——先按频率和深度筛再按链偏倚和读段位置筛最后用ANNOVAR或VEP做功能注释后再人工核查。2.3 文献中提到的关键参数与默认值陷阱VarScan的文献里列出了一长串参数但很多新手直接跑默认值结果要么漏检一堆真突变要么拿到几千个假阳性。我整理了几个最容易被忽略但影响巨大的参数参数名默认值推荐调整影响说明--min-coverage810-20低于此深度的位点直接跳过肿瘤样本建议至少10×--min-reads223-5支持变异的最小读段数太低会引入大量假阳性--min-avg-qual1520-25碱基质量阈值肿瘤FFPE样本建议适当降低--min-var-freq0.010.05-0.10变异频率阈值根据肿瘤纯度调整--p-value0.990.05-0.01统计检验的显著性阈值默认值过于宽松--strand-filter11链偏倚过滤1表示开启强烈建议保持开启--somatic-p-value0.050.01-0.05配对样本的体细胞检验阈值特别要提醒的是--p-value这个参数默认值0.99意味着几乎不进行统计过滤所有频率超过阈值的位点都会被输出。文献里虽然解释了这是为了“保留最大信息量供后续过滤”但很多新手不知道这一点直接拿原始输出做下游分析结果可想而知。我的习惯是第一轮用宽松阈值跑拿到全量候选位点然后用自定义脚本做多轮硬过滤这样既不会漏掉真突变也能把假阳性压到可接受范围。3. 实操流程从BAM到高置信变异列表的完整路径3.1 环境准备与软件安装VarScan是Java写的所以第一步是确保系统有Java运行环境。我一般用OpenJDK 8或11太新的版本偶尔会有兼容性问题。安装过程很简单从SourceForge下载jar包即可但要注意VarScan需要配合samtools的mpileup功能使用所以samtools的版本也要选对。我实测下来samtools 1.9到1.15之间的版本跟VarScan 2.4.4配合最稳定太新的samtools 1.17在mpileup输出格式上有细微变化可能导致VarScan解析出错。# 下载VarScan wget https://sourceforge.net/projects/varscan/files/VarScan.v2.4.4.jar # 确认Java版本 java -version # 确认samtools版本 samtools --version如果你用conda管理环境也可以直接conda install -c bioconda varscan但要注意conda版本可能不是最新的2.4.4。我个人还是推荐手动下载jar包版本可控。3.2 第一步生成mpileup文件VarScan不直接读BAM而是通过samtools mpileup生成的中间文件来获取碱基计数信息。这一步有个关键细节必须加上-B参数关闭BAQ校正否则VarScan拿到的碱基质量会被samtools重新计算导致某些真实突变被低估。文献里也明确提到了这一点但很多教程都漏掉了。# 对肿瘤样本生成mpileup samtools mpileup -B -q 20 -Q 20 -f reference.fa tumor.bam tumor.mpileup # 对正常样本生成mpileup samtools mpileup -B -q 20 -Q 20 -f reference.fa normal.bam normal.mpileup这里的-q 20是比对质量阈值-Q 20是碱基质量阈值。对于肿瘤样本我通常会把-Q降到15因为FFPE样本的碱基质量普遍偏低阈值太高会丢失有效信息。但也不能太低低于10的话测序错误会明显增加。3.3 第二步单样本SNP和Indel检测如果你只有肿瘤样本没有配对正常样本可以用mpileup2snp和mpileup2indel模式。这两个模式的输出是VCF格式但字段含义跟GATK的VCF略有不同需要特别注意。# SNP检测 java -jar VarScan.v2.4.4.jar mpileup2snp tumor.mpileup \ --min-coverage 10 \ --min-reads2 3 \ --min-avg-qual 20 \ --min-var-freq 0.05 \ --p-value 0.05 \ --strand-filter 1 \ --output-vcf 1 tumor.snp.vcf # Indel检测 java -jar VarScan.v2.4.4.jar mpileup2indel tumor.mpileup \ --min-coverage 10 \ --min-reads2 3 \ --min-avg-qual 20 \ --min-var-freq 0.05 \ --p-value 0.05 \ --strand-filter 1 \ --output-vcf 1 tumor.indel.vcf跑完之后别急着高兴先看一眼VCF里的FILTER列。VarScan默认会把所有位点标为PASS即使某些位点明显有问题。所以必须自己写脚本或用手动过滤把SOMATIC、GERMLINE、LOH这些标签搞清楚。3.4 第三步配对样本的体细胞突变检测这才是VarScan的看家本领。somatic模式需要同时输入肿瘤和正常的mpileup文件输出会区分Somatic、Germline、LOH和Unknown四种状态。java -jar VarScan.v2.4.4.jar somatic \ normal.mpileup \ tumor.mpileup \ output_prefix \ --min-coverage 10 \ --min-coverage-normal 8 \ --min-coverage-tumor 10 \ --min-var-freq 0.05 \ --min-freq-for-hom 0.75 \ --normal-purity 0.8 \ --tumor-purity 0.5 \ --p-value 0.05 \ --somatic-p-value 0.01 \ --strand-filter 1 \ --output-vcf 1这里有几个参数需要根据实际情况调整--normal-purity和--tumor-purity如果你不知道确切值可以先用默认的0.8和0.5后续再根据结果微调。--min-freq-for-hom是判断纯合突变的最小频率默认0.75对于肿瘤样本可以降到0.6因为肿瘤异质性会导致纯合突变的频率被稀释。跑完之后会生成两个文件output_prefix.snp.vcf和output_prefix.indel.vcf。每个VCF里的STATUS列会标明Somatic、Germline、LOH或Unknown。只保留Somatic的位点其他状态要么是胚系突变要么是杂合性缺失跟体细胞突变分析的目标不符。3.5 第四步多轮过滤与假阳性控制VarScan的原始输出假阳性率不低这是文献里也承认的事实。我一般会做以下几轮过滤第一轮硬阈值过滤。用awk或bcftools把Somatic状态、变异频率≥0.05、支持读段数≥3、总深度≥10的位点筛出来。# 提取Somatic位点 grep Somatic output_prefix.snp.vcf somatic.snp.vcf # 用bcftools做频率和深度过滤 bcftools filter -i FORMAT/AD[0:1] 3 FORMAT/DP[0:1] 10 somatic.snp.vcf filtered.snp.vcf第二轮链偏倚过滤。VarScan的--strand-filter 1已经做了一部分但有些位点仍然会漏网。我习惯用bcftools的strand插件再查一遍或者直接看VCF里的SOMATIC字段中的链支持数。第三轮读段位置偏倚过滤。如果变异位点集中在读段末端很可能是比对错误。可以用bam-readcount工具统计变异位点在不同读段位置的分布如果超过70%的支持读段都在末端10bp内直接丢弃。第四轮功能注释后的人工核查。用ANNOVAR或VEP注释后重点关注外显子区域的错义突变和无义突变对于内含子或基因间区的突变可以适当放宽标准。我一般会把所有Somatic突变按基因和功能分类然后对高频突变基因如TP53、KRAS、EGFR的位点逐一用IGV可视化核查。4. 常见问题与排查技巧实录4.1 为什么我的VarScan跑出来全是Unknown状态这是新手最常见的问题。Unknown状态意味着VarScan无法判断该位点是体细胞还是胚系来源通常是因为正常样本的测序深度太低或者肿瘤样本的变异频率跟正常样本太接近。解决办法有两个一是提高正常样本的--min-coverage-normal阈值比如从8提到15二是检查正常样本的BAM文件是否用了跟肿瘤样本相同的参考基因组和比对参数。我遇到过好几次是因为正常样本用了hg19而肿瘤样本用了hg38导致染色体坐标完全对不上。4.2 假阳性率太高怎么办VarScan的假阳性主要来自三个方面测序错误、比对错误和样本污染。测序错误可以通过提高--min-avg-qual和--min-reads2来压制比对错误需要检查参考基因组版本和比对工具的参数特别是Indel附近的区域样本污染则比较麻烦如果正常样本里混入了肿瘤DNA会导致大量胚系突变被误判为体细胞突变。我一般会用VerifyBamID先查一下样本污染率超过5%的话建议重新建库。4.3 低频突变漏检严重怎么调如果你做的是ctDNA或肿瘤异质性研究目标突变频率可能低到1%甚至0.5%。VarScan默认的--min-var-freq 0.01虽然能覆盖这个范围但假阳性会爆炸。我的经验是把测序深度提到至少500×然后用--min-reads2 5和--p-value 0.01双管齐下。另外--min-avg-qual可以适当降到15因为低频突变的支持读段本来就少质量阈值太高会直接把它们筛掉。还有一个技巧是用--min-coverage-tumor 50确保只有高深度位点才进入分析这样即使频率低统计检验的效力也足够。4.4 VarScan跟GATK的结果差异太大怎么解释这是很正常的现象因为两个工具的统计模型完全不同。GATK用的是贝叶斯模型倾向于保守假阳性低但假阴性高VarScan用的是频率阈值加Fisher检验倾向于敏感假阳性高但假阴性低。我一般会把两个工具的结果取交集作为高置信集取并集作为候选集再做后续验证。如果非要选一个肿瘤样本用VarScan胚系样本用GATK这是我踩了无数次坑之后总结出来的经验。4.5 运行速度太慢怎么优化VarScan本身很快慢的是samtools mpileup这一步。如果你的BAM文件很大可以用-r参数按染色体拆分并行跑最后再合并。另外mpileup的时候加上--max-depth 10000可以避免某些高深度区域拖慢整体速度。我实测过一个50×的全外显子组数据单线程跑mpileup大概需要40分钟拆成24条染色体并行跑只要5分钟。5. 个人实操心得与参数调优经验5.1 肿瘤纯度未知时的参数策略很多临床样本的肿瘤纯度是未知的这时候直接设--tumor-purity 0.5可能会漏掉低纯度样本的突变。我的做法是先用默认参数跑一遍然后根据结果里的变异频率分布反推肿瘤纯度。如果大部分体细胞突变的频率集中在20%-30%说明肿瘤纯度大概在40%-60%如果集中在5%-10%纯度可能只有20%左右。然后根据反推的纯度重新调整--min-var-freq比如纯度20%时把阈值降到0.03纯度60%时保持0.05即可。5.2 FFPE样本的特殊处理FFPE样本的DNA会有脱氨损伤导致CT的假阳性突变特别多。VarScan本身没有专门处理FFPE的模块但可以通过降低--min-avg-qual到15和增加--min-reads2到5来平衡。另外我习惯在过滤阶段把所有的CT和GA突变单独标记出来如果某个样本的CT突变占比超过总突变的40%就要警惕FFPE损伤的影响。有条件的话可以用FFPEFilter或SOBDetector做预处理。5.3 链偏倚过滤的实操细节VarScan的--strand-filter 1会过滤掉正负链支持比例严重失衡的位点但它的算法比较简单只检查支持变异的读段是否来自两条链。我遇到过一些位点正链支持10条、负链支持0条VarScan会直接过滤掉但实际上这个位点可能是真实的只是因为测序文库的链特异性偏差。这种情况下我会用--strand-filter 0重新跑一遍然后手动检查这些位点的IGV图如果变异在两条链上都有少量支持只是比例不均衡可以考虑保留。5.4 与下游注释工具的衔接VarScan输出的VCF格式跟标准VCF有些差异特别是SOMATIC字段里的信息需要解析后才能被ANNOVAR或VEP正确识别。我一般会先用varscan2vcf脚本做格式转换把SOMATIC字段拆成TUMOR_DP、TUMOR_AD、NORMAL_DP、NORMAL_AD等标准字段然后再做注释。另外VarScan的Indel表示方式跟GATK不同它用的是/-而不是REF/ALT注释前需要用bcftools norm做左对齐和归一化。5.5 一个真实的踩坑案例有一次我跑一个肺癌配对样本VarScan输出了2000多个Somatic SNP我一看就知道不对劲。排查了半天发现是正常样本的BAM文件里混入了肿瘤样本的读段导致正常样本的变异频率也被拉高了VarScan把很多胚系突变误判成了体细胞突变。后来用VerifyBamID一查污染率高达15%。重新建库测序后Somatic SNP降到了80多个跟预期相符。这个教训告诉我VarScan的结果对样本质量极其敏感跑之前一定要做质控。6. 写在最后VarScan这款工具虽然老但它在肿瘤体细胞突变检测这个细分场景下的表现依然可圈可点。文献里的统计模型设计、参数推荐值和过滤策略放到今天来看仍然有很强的参考价值。我个人的建议是不要盲目追新工具先把VarScan的somatic模式跑通、跑透理解每个参数背后的生物学意义再去看其他工具的设计差异。这样你不仅能得到更可靠的结果还能在遇到问题时快速定位原因。如果你正在做肿瘤panel或全外显子组的体细胞突变分析不妨把VarScan加入你的工具清单跟GATK或Strelka做个对比。我的经验是VarScan在低纯度、低深度样本上的召回率优势明显但需要配合更严格的过滤策略。最后再分享一个小技巧把VarScan的--p-value设成0.01而不是默认的0.99能直接砍掉一半以上的假阳性而且几乎不影响真突变的检出。这个参数调整是我在几十个样本上反复验证过的你可以直接抄作业。