拓冰建站拓冰建站
首页 / 资讯中心 / 正文

R/qtl QTL定位实战:从数据格式到全基因组扫描全流程

很多人一看“QTL定位分析”这六个字第一反应是“我是不是得先把遗传学、统计模型全部学完才能动手”。但以我自己跑项目的经验来看真正卡住新手的往往不是原理而是数据进不了程序、参数不知道该填啥、结果出来了不知道怎么判断。R/qtl 是 R 语言生态里最老牌也最常用的 QTL 定位工具包它能把你从数据读入、质量检查、基因型概率计算、单QTL扫描、置换检验到出图整条链路串起来。这篇文章我打算用 R/qtl 自带的小鼠高血压数据集hyper把完整流程跑一遍再讲清楚怎么把代码换成你自己的数据最后把那些文档里不会写的坑和常见报错一并整理给你。只要跟着代码走第一张像样的 QTL 图谱两个小时内基本能出来。1. QTL定位到底在做什么新手需要先建立的框架1.1 一个容易理解的比喻QTL英文全称是 quantitative trait locus翻译过来叫数量性状基因座。这里有个很关键的概念像身高、体重、血压、产量这类性状往往不是单个基因决定的而是很多微效基因加上环境共同作用的结果。QTL 定位做的事情就是在一个遗传分离群体里通过分子标记和表型数据的关联扫描找到染色体上可能影响这个性状的区间。你可以把它想象成在一个很大的地图上找“信号薄弱点”——地图上的地标是分子标记表型就是你关心的那个“信号值”QTL 定位就是在不同地标之间做地毯式排查。做 QTL 定位需要三类数据一是基因型数据每个个体在每个标记位点上是哪种带型二是表型数据每个个体的性状测定值三是遗传图谱每个标记位于哪条染色体、什么遗传位置。这三样缺一个都不行。很多新手拿着一堆基因分型数据就冲进来结果发现标记没有染色体位置或者表型缺失太多那后面所有分析都无从谈起。1.2 R/qtl 凭什么值得新手学你可能会问做 QTL 定位的软件那么多MapQTL、JoinMap、QTL IciMapping 都用得不少为什么非要学 R/qtl我个人建议新手先入 R/qtl核心原因有几点第一开源免费装一个 R 环境就能跑不存在授权和版本破解问题第二它支持回交BC、F2、重组自交系RIL、四亲本等多种群体类型覆盖面广第三函数设计很完整从读入数据到画图都有对应 API不用在好几个软件之间来回导数据第四R 语言本身的数据处理和可视化能力强做完 QTL 分析后续做效应估计、模型比较、出论文图都方便。当然它不是没缺点比如面对超大规模标记数据时会有点慢但那是进阶以后要考虑的事。对新手的第一个项目来说R/qtl 绝对是最稳妥的选择。1.3 你需要提前准备什么除了安装 R 和 RStudio你要在 R 环境里安装qtl包。装包命令很简单install.packages(qtl) library(qtl)版本问题我后面会提。数据方面你要确认手头的数据能整理成“一个样本一行”的表格并且有清晰的标记名称、染色体编号、遗传位置和表型列。如果数据是从供应商那边拿的先别急着做格式转换先看它缺不缺失、编码统一不统一。这一环节我大概花过整整一个下午所以特别想让你少走这个弯路。2. 数据格式是第一道坎先搞懂R/qtl的cross对象2.1 cross对象是什么R/qtl 里几乎所有分析函数都围绕一个叫 cross 的对象展开。这个对象本质上是一个 R 列表里面至少包含两个大块geno存放每个标记的基因型数据和遗传图谱信息pheno存放表型数据。你自己手动拼一个列表当然可以但更常规的方式是用read.cross()把文件读进来自动生成 cross 对象。很多新手最头疼的是 R/qtl 的数据格式。它不像普通 CSV 那样第一行就是表头、下面就是数据而是前面还有几行“元信息”用来告诉程序个体数、表型数、标记数、染色体号、标记位置这些关键信息。正因为这样直接拿 Excel 里的常规表格去read.cross()大概率会报错。2.2 如果完全不想记格式导出模板再改我这里有个特别实用的土办法不要凭记忆去排格式而是用 R/qtl 自带的数据集导出一个标准模板再往模板里填你自己的数据。代码非常简单library(qtl) data(hyper) write.cross(cross hyper, format csv, dir ., filestem hyper_template)运行之后当前工作目录下会生成一个hyper_template.csv文件。你用 Excel 打开它就会发现前几行是 R/qtl 需要的元信息包括标记染色体、遗传位置、表型名称等下面才是每个个体的数据。你只需要保留这些列结构把数据替换成自己的就行。这比看文档硬背格式快得多也不容易把文件读歪。有些时候你的标记基因型编码可能是“A/B/H”或者“0/1”读入的时候可以用read.cross()的genotypes参数告诉程序怎么映射。等你有模板之后读数据就变成这么一行mycross - read.cross(format csv, dir ., file hyper_template.csv, genotypes c(AA, AB, BB), alleles c(A, B))注意genotypes要和你模板里的编码完全一致否则基因型会被读成缺失。2.3 读入数据和基础质检读入之后先别急着分析先做三件事看摘要、查缺失、看图谱。我自己每次拿到一个新数据集这三步是雷打不动的。summary(mycross) nmissing(mycross) plotMissing(mycross) plotMap(mycross)summary()会告诉你这个群体的类型、个体数、标记数和表型数nmissing()和plotMissing()能帮你定位缺失严重的个体和标记plotMap()可以看标记在染色体上的分布是否合理。如果某条染色体上的标记集中挤在一个位置或者整条染色体没有任何标记那后面就算扫出 LOD 峰可信度也很低。我见过有人一开始没检查分析完才发现某个标记的染色体编号写错了结果整个图谱都是乱的返工成本非常高。3. 实战一条龙用R/qtl自带数据完成QTL扫描全流程3.1 加载数据并快速跑通最小流程下面我直接用 R/qtl 自带的hyper数据集演示。hyper是小鼠高血压相关研究中的回交群体数据包含 249 个个体的基因型和多个血压相关表型。这是 R/qtl 官方文档里最经典的示例数据跑起来不踩坑。library(qtl) data(hyper) hyper - calc.genoprob(hyper, step 1, error.prob 0.001) out - scanone(hyper, pheno.col 1, method hk) plot(out)这段代码运行完你会得到一张 LOD 曲线图横轴是连锁图谱位置纵轴是 LOD 值。别小看这个最小流程它已经完成了 QTL 定位主链路里的最关键三步计算基因型概率、扫描每个可能位置、输出 LOD 曲线。我第一次跑通时心里大概只有三个字就这但后面才明白参数细节才是决定结果可靠性的重点。3.2 calc.genoprob 为什么必须做在真正的 QTL 定位里QTL 不一定恰好落在标记位置上更多时候是在两个标记之间。我们不可能在每个真实位置都测定基因型所以需要用标记信息去“推断”某个区间内候选位置的基因型概率。calc.genoprob()就是干这个的。这里两个参数很关键step表示每隔多少厘摩cM计算一次概率。默认是 0也就是只在标记位置计算我一般建议至少设为 1这样扫描的粒度更细。error.prob是基因分型误差概率默认是 0.0001实际数据里完全无误差很少见设成 0.001 可以稍微容忍一点错误避免个别错误的标记把结果带偏。但也不要设太大误差概率设得过高会把真实的交换事件也抹掉导致图谱膨胀。3.3 scanone 核心参数和结果解读scanone()是真正做全基因组扫描的函数。常用参数就这几个pheno.col用第几个表型默认是 1model表型模型连续型数据用normal0/1 性状用binary分布很不正常时用np非参数模型method计算方法em是最大期望算法hk是 Haley-Knott 回归。我自己的习惯是连续性状、数据量不是特别大的情况下优先用method hk。它比em快很多而且在大多数情况下结果接近新手用它不会因为计算太久而丧失耐心。scanone()返回的数据框每一行代表一个扫描位置包含染色体、遗传位置和 LOD 值。你可以在 RStudio 里直接执行View(out)查看。3.4 用permutation确定显著性阈值很多新手拿到 LOD 曲线后看到某个峰超过 3 就兴奋得不行。但 LOD 等于 3 只是经验阈值不是万能标准。不同样本量、不同标记密度、不同性状分布下显著性阈值差别很大。最靠谱的做法是用置换检验permutation test算一个属于当前数据的阈值。set.seed(20240117) operm - scanone(hyper, pheno.col 1, method hk, n.perm 1000) summary(operm, alpha 0.05)这段代码会把表型随机打乱然后重新扫描很多遍得到一个“没有 QTL 时也可能出现的 LOD 分布”取它的 95% 分位数就是你当前数据的显著性阈值。把summary(operm, alpha 0.05)输出的阈值拿出来再用这个阈值去看扫描结果这样判断就不拍脑袋了summary(out, threshold 3.1)注意我这里写 3.1 只是示意实际阈值以你机器跑出来的为准。置换检验次数越多越稳定一般先跑 100 次看个大概最终报告里可以用 1000 次。3.5 结果可视化与QTL区间找到显著峰以后你还需要确定这个峰的可信区间。R/qtl 里有两个函数常用lodint()是基于 LOD 下降某个值来确定区间bayesint()是贝叶斯可信区间。两者都可以用lodint(out, chr 1, drop 1.5) bayesint(out, chr 1)drop 1.5的意思是从 LOD 峰值向下降低 1.5这个范围内的区域就是候选 QTL 区间。这个区间比单独一个峰值点更有生物学意义写论文时也更常被引用。之后如果你想看这个 QTL 的基因型效应可以用find.marker()找到峰值附近的标记再用effectplot()画出来peak_row - out[which.max(out$lod), ] best_marker - find.marker(hyper, chr peak_row$chr, pos peak_row$pos) effectplot(cross hyper, mname1 best_marker, pheno.col 1)这个图会展示携带不同基因型的个体在表型上的均值差异一眼就能看出这个 QTL 的效应方向和大小。4. 参数深度解读别只会上面的代码得知道为什么这么填4.1 LOD值和QTL区间怎么读LOD 是 log10 likelihood ratio 的缩写意思是“存在 QTL 的似然比不存在 QTL 的似然取对数”。LOD 为 3粗略理解就是“有 QTL 的解释力是没有 QTL 的 1000 倍”。不过这个解释听起来很诱人实际使用时还是要看置换检验阈值因为标记密度、样本量都会影响零分布。区间方面drop 1.5是比较经典的选择但如果你峰值很尖得到的区间会很窄如果你峰值是一个很平的平台区间会延伸到覆盖整条染色体这时候要考虑是不是标记太少或者 QTL 效应太小。4.2 Haley-Knott回归 vs EM速度与精度怎么选R/qtl 的scanone()里method参数最常见的三选一是em、hk、ehk。em是完整最大似然估计理论上最精确但每个位置的迭代优化非常耗时数据量大或者置换检验次数多的时候会等到怀疑人生。hk是 Haley-Knott 回归用基因型概率的期望值做回归计算快很多在大多数常规数据上准确率足够。ehk是扩展版本处理缺失基因型多的数据时更稳但计算量比hk大。我的建议是分析自己的数据时先用hk跑通如果结果出来后你心里没底再用em核对显著峰的位置两者差别一般不会太大。4.3 性状模型怎么选normal / binary / npmodel normal适合连续型数量性状比如产量、株高、血压如果你的表型是患病/未患病这种二分类数据就用model binary它内部会走 logistic 回归那套逻辑如果表型分布极端、离群点多直接用非参数模型np会更稳。选模型的核心是匹配表型数据的真实分布。很多人习惯性填normal结果遇到一个 0/1 表型虽然也能跑出结果但效应估计和显著性会有偏差。这里没有绝对的“最佳”但最简单的判断是先看表型直方图再决定用什么模型。4.4 step、error.prob 这些参数设置的权衡calc.genoprob()里的step设得越小扫描的位置越多结果越平滑但计算量也越大。对于几百个标记的数据step 1完全够用如果标记上万建议先用step 2或step 5做初筛找到候选区间后再加密。error.prob我前面提过一般设 0.001 比较稳。它太大或太小都会影响图谱距离估计尤其会对重组率计算造成影响。你可以在不同参数下各跑一遍scanone()看看峰值位置和 LOD 是否稳定这本身就是一种很实用的敏感性检验。5. 常见报错与排查这些问题我几乎每隔几天就会见到5.1 读入CSV时的行列错位新手最常见的报错是read.cross()在读入时提示行列数不对或者后半段数据全是缺失。多数原因是 CSV 模板里元信息行和数据行之间的列数不一致或者某些单元格里有不可见空格。解决办法也别复杂重新用write.cross()导一个模板把模板原封不动地打开然后一列一列替换数据不要自己新起一行或者加列。如果数据是从 Excel 复制过来的先全选统一成纯文本格式再复制进 CSV 文件。5.2 calc.genoprob报错基因型概率计算不了如果calc.genoprob()报错首先查一下summary(cross)里的标记数量和缺失情况。某个标记在所有个体里都是缺失或者染色体编号里面混入了文本字符都会让程序算不下去。R/qtl 对染色体编号的要求很严格如果是常染色体就写数字如果是性染色体需要有特定写法。别在染色体号里写“chr1”直接写“1”就行省掉一堆麻烦。另外标记名称必须唯一重复的标记名也会导致奇怪的报错。5.3 scanone结果全空或LOD为0如果你跑完scanone()发现所有位置的 LOD 都是 0 或特别小先检查有没有执行calc.genoprob()。有人换了自己的数据以后忘了重新计算基因型概率直接用旧 cross 对象扫描结果全错。另外如果表型列里全是同一个值也就是表型没有变异scanone()当然扫不出任何信号。这时候先画表型直方图看看是不是数据导入时把表型列读错了。5.4 permutation跑得太慢置换检验要重复很多次scanone()慢是正常的。但如果你用的是method em并且n.perm 1000可能一个小时都跑不完。优化思路有三个一是改method hk速度能提升一个量级二是先跑n.perm 100确定大概阈值三是调大step减少扫描位置数。我习惯先用n.perm 100跑一个临时阈值等确认峰值显著后再用 1000 次做最终报告。5.5 常见问题速查表症状常见原因处理办法读入 CSV 报行列数错误元信息行与数据行列数不一致用 write.cross 重新导出模板替换染色体号不对写了 chr1 或混入文本统一改纯数字编号所有标记都缺失基因型编码与 genotypes 参数不匹配检查模板里的等位基因编码calc.genoprob 报错存在全缺失标记或重复标记名用 summary 和 nmissing 检查scanone 全是 0忘记 calc.genoprob 或表型无变异补上概率计算检查表型分布permutation 太慢method 用了 em或 n.perm 过大改用 hk分两步跑阈值6. 再往深走一点多QTL模型与协变量6.1 为什么要进入多QTL上面的流程处理的是单 QTL 模型也就是假设全基因组只有一个主要 QTL。但真实性状往往由多个位点共同控制还可能存在上位性互作。如果你只用单 QTL 扫描两个效应相近的 QTL 可能会只出现一个峰甚至被掩盖。R/qtl 提供了条件扫描和逐步选择的函数比如addqtl()、addint()、fitqtl()、stepwiseqtl()等。新手可以先不急着全上但至少要懂得单 QTL 只是入门。6.2 addqtl和fitqtl的入门用法当你通过单 QTL 扫描得到一个候选 QTL 后可以在已定位 QTL 的基础上继续加第二个 QTLqtlobj - makeqtl(hyper, chr peak_row$chr, pos peak_row$pos) fit - fitqtl(hyper, qtl qtlobj, pheno.col 1, method hk) summary(fit)makeqtl()是用来构建 QTL 模型的起点fitqtl()会估计这个模型里 QTL 的效应、方差解释比例和显著性。如果你想在已有 QTL 基础上再看有没有第二个 QTL可以用addqtl(hyper, qtl qtlobj, pheno.col 1, method hk)这个函数会在控制已有 QTL 后对全基因组再做一次条件扫描。我个人的建议是单 QTL 流程跑熟了以后再用多 QTL 模型去验证候选位点不要一上来就用复杂模型把自己绕晕。6.3 扩展方向协变量与R/qtl2很多实验数据里还有性别、批次、年龄等协变量。scanone()里有一个addcovar参数可以传入表型数据中的一列或多列把协变量效应先拟合掉再扫描 QTL。比如你的hyper数据中包含性别可以这样做addcovar - hyper$pheno$sex out_cov - scanone(hyper, pheno.col 1, method hk, addcovar addcovar)如果以后标记数量特别大或者要分析的性状特别多可以考虑新版的 R/qtl2 包它在处理高密度 SNP 数据上更高效。但前提是把 R/qtl 的基本逻辑吃透否则换到哪个包都会觉得懵。我在实际项目里反复体会最深的一点是新手做 QTL 定位第一个目标不是“找到最精确的那个位点”而是“把整条流程稳定跑通”。先用自带数据验证代码再用自己的数据出结果最后再回头调参数、加模型这个顺序基本不会出大错。另外一个小建议是每次做 permutation 之前都设置随机数种子比如set.seed(20240117)不然第二天重新跑一遍阈值可能会轻微变化文档记录也对不上。把代码、数据、sessionInfo()一起归档保存这对你回溯分析或者写方法部分都有很大帮助。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门