GWAS与单细胞整合分析:用seismic定位疾病关键细胞亚群与驱动基因
1. 为什么GWAS结果总是“差最后一公里”做遗传流行病学或者生信分析的朋友大概率都经历过这种落差GWAS跑出来一堆显著位点曼哈顿图漂漂亮亮可一旦要回答“这个位点到底影响了哪种细胞、通过哪个基因起作用”就卡住了。GWAS给的是统计学关联是群体水平上SNP与表型的相关性它不告诉你因果链更不告诉你效应发生在哪一类细胞里。这个“从关联到机制”的鸿沟就是过去十年里功能基因组学最想填的坑。我最早接触这个方向的时候主流做法还是eQTL共定位加TWAS思路是把GWAS位点映射到基因表达上。但这套方法有个根本局限它用的是组织块bulk tissue的表达数据。一块组织里混着上皮、免疫、基质、内皮等好几种细胞eQTL信号是这些细胞平均之后的结果。如果某个风险位点只在一种占比很小的细胞亚群里起作用bulk信号会被稀释到几乎看不见。这就是为什么很多GWAS位点在做完eQTL之后依然找不到明确的靶基因——不是没有是被平均掉了。单细胞测序的成熟改变了这个局面。它把表达量拆到了单个细胞的分辨率让我们第一次有能力问这个风险基因到底是在CD8效应T细胞里高表达还是在调节性T细胞里高表达这两者的生物学含义完全不同。而seismic这个包就是专门为“把GWAS信号投射到单细胞亚群上”这件事设计的工具。它的核心逻辑不是简单地把GWAS基因列表和单细胞marker基因取交集而是通过一个叫“特异性评分”specificity score的统计量量化每个基因在每种细胞类型中的表达特异性再结合GWAS的关联强度找出那些既与疾病显著相关、又在特定细胞亚群中特异性表达的基因。这篇文章我想聊的是完整的一套实操思路从GWAS summary statistics出发经过基因映射和评分到单细胞数据的注释与整合最后用seismic定位关键细胞亚群和驱动基因。我会把每一步的参数选择、踩过的坑、以及那些文档里不会写的经验都摊开讲。适合已经做过基础单细胞分析、想往疾病机制方向深入的读者也适合做GWAS但想拓展功能解读的同行。2. seismic的核心设计逻辑与方案选型2.1 它到底解决了什么问题先说清楚seismic不是万能的。它不做因果推断不做孟德尔随机化它做的是“优先级排序”。你给它一个GWAS基因列表带关联统计量再给它一个单细胞表达矩阵带细胞类型注释它输出的是每个细胞类型的一个富集评分以及每个基因在每种细胞类型中的特异性评分。评分高的细胞类型就是最可能承载遗传风险的细胞亚群评分高的基因就是该亚群中最可能的驱动基因。这个设计的巧妙之处在于它没有假设“GWAS基因一定在某种细胞里高表达”。它用的是表达特异性而不是表达量。一个基因可能在所有细胞里都表达但在某种细胞里表达量显著高于其他细胞这种“相对特异性”才是功能相关的信号。seismic通过计算每个基因在每种细胞类型中的表达分布与背景分布做比较得到一个标准化的特异性分数。这个分数再和GWAS的关联p值或效应量做整合最终得到一个综合排名。2.2 为什么选seismic而不是其他方案市面上做类似事情的工具不少比如scDRS、CELLEX、sc-linker等。我选seismic主要基于三个考虑。第一它对输入数据的宽容度比较高不需要原始count矩阵log-normalized的数据也能跑这对已经做过标准Seurat或Scanpy流程的人来说省事。第二它的评分逻辑是透明的你可以自己调整GWAS信号的权重和特异性信号的权重不像某些工具是个黑箱。第三它支持多种细胞类型注释的输入格式无论是手动注释的cluster label还是自动注释的结果都能直接对接。当然它也有局限。如果你的GWAS基因列表里基因数量太少比如只有几十个评分会不稳定。一般建议至少几百个基因最好是全基因组显著位点加上 suggestive 位点一起用。另外它对细胞类型注释的质量非常敏感。如果注释本身是错的seismic只会给你一个精确的错误答案。所以我在实际项目里永远是把注释质量放在第一位seismic是最后一步的“放大器”不是“纠错器”。2.3 整体分析框架的搭建我习惯把整个流程拆成四个阶段。第一阶段是GWAS数据的清洗和基因映射把SNP位点变成基因列表这一步决定了后续所有分析的输入质量。第二阶段是单细胞数据的预处理和注释重点是确保细胞类型注释的生物学合理性。第三阶段是seismic评分计算包括特异性评分和GWAS整合评分。第四阶段是结果解读和验证包括驱动基因的筛选、通路富集、以及可能的实验验证方向。这四个阶段里第一阶段和第二阶段是最耗时间的也是最容易出问题的。很多人急着跑seismic结果GWAS基因列表没做好或者单细胞注释一团糟最后出来的结果没法解释。我的建议是前两个阶段至少花整个项目70%的时间seismic本身跑起来很快但前面的准备工作决定了结果的上限。3. GWAS数据的清洗与基因映射实操3.1 从summary statistics到基因列表拿到GWAS summary statistics之后第一件事是确认列名和基因组版本。不同数据库的格式差异很大有的用SNP有的用rsid有的用marker染色体列有的叫CHR有的叫chr有的叫chromosome。我一般先用head看一眼然后用一个统一的rename脚本处理。基因组版本尤其重要GRCh37和GRCh38的坐标不能混用否则后续的基因映射会全错。# 读取GWAS summary statistics gwas - read.table(gwas_summary.txt, header TRUE, stringsAsFactors FALSE) # 统一列名 colnames(gwas)[colnames(gwas) SNP] - rsid colnames(gwas)[colnames(gwas) CHR] - chr colnames(gwas)[colnames(gwas) BP] - pos colnames(gwas)[colnames(gwas) P] - pval colnames(gwas)[colnames(gwas) BETA] - beta # 确认基因组版本 # 如果原始数据是GRCh37需要liftOver到GRCh38 # 这里假设已经是GRCh38映射基因这一步我推荐用biomaRt或者本地的TxDb包。biomaRt方便但依赖网络有时候会抽风本地TxDb.Hsapiens.UCSC.hg38.knownGene更稳定适合批量处理。映射的时候要注意一个SNP可能落在多个基因的范围内这时候不要随便选一个而是保留所有可能的基因后续让seismic的评分来决定哪个更重要。library(TxDb.Hsapiens.UCSC.hg38.knownGene) library(org.Hs.eg.db) txdb - TxDb.Hsapiens.UCSC.hg38.knownGene # 将SNP位置映射到基因 # 这里用promoter区域TSS上下游2kb作为映射窗口 promoter_gr - promoters(txdb, upstream 2000, downstream 2000) # 假设gwas_gr是GWAS显著位点的GRanges对象 hits - findOverlaps(gwas_gr, promoter_gr) mapped_genes - promoter_gr$gene_id[subjectHits(hits)]3.2 基因列表的筛选策略映射完之后你会得到一个基因列表。这时候要决定用哪些基因进入seismic。我的经验是分两档第一档是全基因组显著位点p 5e-8映射到的基因这是核心信号第二档是suggestive位点p 1e-5映射到的基因作为补充。两档合并之后去重得到一个几百到几千个基因的列表。如果基因数少于200我会考虑放宽到p 1e-4但要在结果里注明阈值避免过度解读。还有一个细节是基因的物理距离。有些SNP落在基因间区离最近的基因有好几百kb。这种远距离映射要谨慎我一般只保留TSS上下游100kb以内的映射超过这个距离的要么是调控元件需要额外的表观数据支持要么就是噪音。seismic本身不处理这个距离问题它只接收基因列表所以距离筛选必须在输入之前做好。注意GWAS的效应量方向beta的正负在seismic里不是必须的但如果你有可以保留在注释信息里后续解读驱动基因时有用。比如某个基因的beta是正的说明风险等位基因增加表达那它在细胞亚群中的高表达就更可能是“驱动”方向。3.3 数据质量控制的几个硬指标在把基因列表交给seismic之前我会做三个检查。第一基因ID的格式是否统一是Ensembl ID还是Gene Symbolseismic默认接受Gene Symbol如果是Ensembl ID需要转换。第二基因列表里有没有线粒体基因、核糖体基因这些常见的技术噪音这些基因在单细胞数据里往往高表达但生物学意义有限建议剔除。第三检查基因列表和单细胞表达矩阵的基因重叠率如果重叠率低于50%说明两个数据集的基因注释版本可能不一致需要重新对齐。# 检查基因重叠 sc_genes - rownames(sc_data) # 单细胞表达矩阵的基因 gwas_genes - unique(mapped_genes) overlap - intersect(sc_genes, gwas_genes) overlap_rate - length(overlap) / length(gwas_genes) # 如果overlap_rate 0.5需要检查基因ID格式 # 常见问题Ensembl ID vs Gene Symbol或者版本号后缀我踩过的一个坑是GWAS数据用的是Ensembl ID带版本号如ENSG00000123456.7而单细胞数据用的是不带版本号的ENSG00000123456直接取交集全是空的。后来写了一个去版本号的函数才解决。这种问题看起来低级但在实际项目里非常常见尤其是当GWAS和单细胞数据来自不同团队的时候。4. 单细胞数据的注释与seismic评分计算4.1 细胞类型注释的质量决定一切seismic的评分是建立在细胞类型注释之上的。如果注释把T细胞和NK细胞混在一起那seismic给出的“T细胞特异性基因”里就会混入NK细胞的信号结果没法解释。所以我在跑seismic之前一定会花大量时间做注释的验证。手动注释是金标准但手动注释需要marker基因的知识。我通常先用自动注释工具如SingleR、scType跑一遍得到一个初步的label然后用手动marker基因去验证每个cluster。验证的方法是对每个cluster计算一组经典marker基因的平均表达画一个dot plot或者heatmap。比如CD3D、CD3E、CD8A、CD4、FOXP3、NKG7、GNLY这些基因能清楚地区分T细胞亚群和NK细胞。如果某个cluster的marker表达模棱两可我会把它标记为“Unknown”或者“Mixed”不进入seismic分析。宁可少几种细胞类型也不要引入错误的注释。# 手动注释验证 marker_genes - list( T_cell c(CD3D, CD3E, CD2), CD8_T c(CD8A, CD8B, GZMK), CD4_T c(CD4, IL7R, CCR7), Treg c(FOXP3, IL2RA, CTLA4), NK c(NKG7, GNLY, KLRD1), B_cell c(CD79A, MS4A1, CD19), Monocyte c(CD14, LYZ, FCGR3A), Dendritic c(FCER1A, CST3, CLEC10A) ) # 用Seurat的DotPlot可视化 DotPlot(sc_obj, features marker_genes, group.by cluster) RotatedAxis()4.2 seismic的输入格式与参数设置seismic的输入主要有两个一个是单细胞表达矩阵log-normalized一个是细胞类型标签向量。表达矩阵的行是基因列是细胞标签向量的长度等于细胞数每个元素是细胞类型名称。GWAS基因列表作为一个单独的向量传入可以带权重比如-log10(p)也可以不带。library(seismic) # 准备输入 # sc_data: log-normalized表达矩阵行基因列细胞 # cell_labels: 细胞类型标签向量 # gwas_genes: GWAS映射的基因列表 # gwas_weights: 可选基因的权重如-log10(p) # 计算特异性评分 spec_scores - calc_specificity( sc_data, cell_labels, method wilcox # 或 t-test ) # 整合GWAS信号 seismic_results - seismic_score( spec_scores, gwas_genes, weights gwas_weights, n_perm 1000 # 置换检验次数 )method参数我一般用wilcox因为单细胞数据不满足正态分布Wilcoxon秩和检验更稳健。n_perm建议至少1000次如果结果接近显著性阈值可以加到10000次。置换检验的目的是评估观察到的评分是否显著高于随机期望这一步会给出p值方便后续筛选。4.3 评分结果的解读与驱动基因筛选seismic的输出是一个矩阵行是细胞类型列是评分指标如特异性评分、整合评分、p值。我通常按整合评分排序取前几名的细胞类型作为“风险相关亚群”。然后对每个亚群提取评分最高的基因作为“驱动基因候选”。这些基因需要满足两个条件在GWAS中显著关联在该亚群中特异性高表达。# 提取top细胞类型 top_celltypes - seismic_results$celltype[order(seismic_results$integrated_score, decreasing TRUE)][1:5] # 提取每个top细胞类型的驱动基因 driver_genes - list() for (ct in top_celltypes) { genes - seismic_results$gene[seismic_results$celltype ct] scores - seismic_results$gene_score[seismic_results$celltype ct] driver_genes[[ct]] - genes[order(scores, decreasing TRUE)][1:20] }这里有个经验不要只看top 1的细胞类型。有时候top 2和top 3的评分很接近可能反映了生物学上的连续性比如CD4记忆T细胞和CD4效应T细胞。这时候可以把它们合并成一个更大的类别或者分别解读但注明相关性。另外驱动基因列表不要只看排名还要看基因的生物学功能。如果top基因里有一堆lncRNA或者假基因我会降低它们的优先级优先关注蛋白编码基因。提示seismic的评分对基因数量敏感。如果GWAS基因列表很大2000评分会趋于平均top细胞类型的区分度下降。这时候可以尝试用更严格的GWAS阈值如p 1e-8重新跑一遍看看结果是否更清晰。5. 常见问题排查与避坑经验实录5.1 评分结果不显著怎么办这是最常见的问题。跑完seismic所有细胞类型的p值都大于0.05或者top细胞类型的评分和背景差不多。原因通常有三个。第一GWAS基因列表和单细胞数据的基因重叠率太低导致有效信号不足。解决办法是检查基因ID格式确保两个数据集用的是同一套注释。第二细胞类型注释太粗比如把所有T细胞当成一个类别特异性信号被平均掉了。解决办法是细分亚群至少区分CD4、CD8、Treg、NK。第三GWAS信号本身太弱基因列表里大部分是suggestive位点。解决办法是提高GWAS阈值只用全基因组显著位点。我遇到过一次GWAS基因列表有3000多个基因但和单细胞数据的重叠只有400个。检查发现GWAS用的是GRCh37的坐标而单细胞数据是GRCh38基因映射全错了。重新liftOver之后重叠率升到85%seismic结果立刻显著了。所以遇到问题先查数据对齐再查统计方法。5.2 驱动基因列表里全是“老面孔”怎么办有时候跑出来的驱动基因翻来覆去就是那几个已知的疾病基因比如自身免疫病里的HLA-DRB1、PTPN22。这说明你的分析是可靠的但没有新发现。想要挖掘新基因可以尝试几个策略。第一把GWAS阈值放宽到1e-5纳入更多suggestive位点但要在结果里注明。第二用seismic的“条件分析”功能把已知基因作为协变量剔除看看剩下的信号里有没有新基因。第三结合其他数据源比如CRISPR筛选或药物靶点数据库对驱动基因做优先级排序。# 条件分析示例剔除已知基因后重新评分 known_genes - c(HLA-DRB1, PTPN22, IL2RA) gwas_genes_filtered - setdiff(gwas_genes, known_genes) seismic_results_conditional - seismic_score( spec_scores, gwas_genes_filtered, weights gwas_weights[!gwas_genes %in% known_genes], n_perm 1000 )5.3 细胞亚群注释的边界问题单细胞数据里细胞类型的边界往往是模糊的。比如CD4记忆T细胞和CD4效应T细胞在UMAP上可能是连续分布的强行切成两个cluster可能不自然。seismic对这种边界模糊的注释很敏感因为特异性评分依赖于清晰的类别划分。我的处理方式是如果两个cluster的marker基因高度重叠就合并成一个如果marker基因有明确差异就保留分开。另外可以用FindClusters的resolution参数调整聚类粒度resolution越高cluster越多但要注意过拟合。还有一个坑是批次效应。如果单细胞数据来自多个样本或多个实验批次批次效应会导致同一细胞类型在不同批次里形成不同的cluster。这时候必须先做整合如Harmony、Seurat CCA再注释再跑seismic。我试过不做整合直接跑结果seismic把批次效应当成了细胞类型差异top细胞类型全是某个批次的特定cluster完全没有生物学意义。5.4 常见问题速查表问题现象可能原因排查方法解决方案所有细胞类型p值不显著基因重叠率低检查基因ID格式和基因组版本统一ID格式liftOver坐标top细胞类型评分接近注释太粗检查marker基因表达细分亚群提高聚类resolution驱动基因全是已知基因GWAS阈值太严检查基因列表大小放宽阈值到1e-5或做条件分析结果随批次变化批次效应未校正检查样本来源先做Harmony整合再注释评分不稳定置换次数太少检查n_perm增加到10000次某些细胞类型缺失注释时被过滤检查cluster大小小cluster合并或标记为Unknown6. 从结果到机制驱动基因的验证与延伸6.1 通路富集与调控网络分析拿到驱动基因列表之后下一步是理解这些基因的生物学功能。我通常做两件事通路富集和调控网络分析。通路富集用clusterProfiler跑GO和KEGG看看驱动基因是否集中在某些信号通路上。比如如果top细胞类型是CD8效应T细胞驱动基因富集在细胞毒性通路如GZMB、PRF1、NKG7那就很合理。如果富集在出乎意料的通路上比如代谢通路那可能提示新的机制方向。library(clusterProfiler) library(org.Hs.eg.db) # 驱动基因通路富集 ego - enrichGO( gene driver_genes[[1]], OrgDb org.Hs.eg.db, keyType SYMBOL, ont BP, pAdjustMethod BH, pvalueCutoff 0.05 ) # 可视化 dotplot(ego, showCategory 20)调控网络分析可以用SCENIC或者CellChat。SCENIC能推断驱动基因上游的转录因子如果某个转录因子在top细胞类型中特异性高表达且调控多个驱动基因那它就是一个关键的调控节点。CellChat能分析细胞类型之间的通讯如果top细胞类型通过某种配体-受体对与其他细胞类型通讯那这个通讯轴可能就是疾病相关的微环境机制。6.2 与公共数据的交叉验证seismic的结果需要外部验证。我常用的公共数据源有三个。第一GWAS Catalog看看驱动基因是否在之前的GWAS研究中被报道过。第二单细胞图谱如Human Cell Atlas看看驱动基因在正常组织中的表达模式是否与你的数据一致。第三eQTL数据库如GTEx看看驱动基因是否有eQTL支持。如果驱动基因在多个独立数据源中都有信号那可信度就很高。还有一个验证策略是“反向分析”用另一个独立的单细胞数据集跑同样的seismic流程看看top细胞类型和驱动基因是否可重复。如果两个数据集的结果高度一致那结论就很稳。如果差异很大可能是数据集之间的技术差异如测序深度、平台或者生物学差异如疾病亚型、组织来源。这时候需要仔细比较两个数据集的注释和批次信息。6.3 实验验证的方向建议如果条件允许实验验证是最终的金标准。对于top细胞类型可以用流式细胞术或者免疫组化验证其在疾病组织中的比例和状态。对于驱动基因可以用CRISPR敲低或者过表达看看是否影响细胞功能。我一般建议优先验证那些“评分高且生物学功能明确”的基因比如如果驱动基因是一个已知的免疫检查点分子那验证起来就很有方向。如果实验条件有限也可以做“计算验证”。比如用孟德尔随机化分析看看驱动基因的表达是否与疾病有因果关联。或者用药物数据库如CMap看看有没有药物能靶向驱动基因。这些计算验证虽然不如实验直接但能提供额外的证据链增强结论的说服力。注意seismic的结果是“假设生成”而不是“假设验证”。它给你的是优先级排序不是因果证明。所以解读的时候要谨慎不要过度声称因果关系。我通常会在文章里写“seismic分析提示XX细胞亚群和XX基因可能是疾病相关的关键因素需要进一步实验验证”而不是“seismic证明了XX是驱动基因”。7. 我个人在实际操作中的几点体会这套流程我跑过好几个项目有自身免疫病、有肿瘤、也有神经退行性疾病。最大的体会是seismic本身不难难的是前面的数据准备和后面的结果解读。GWAS数据的清洗和基因映射单细胞数据的注释和整合这两步决定了整个分析的质量。我见过太多人急着跑seismic结果因为基因ID不匹配或者注释错误出来的结果完全没法用。另一个体会是不要迷信top 1的细胞类型。有时候top 1和top 2的评分差异很小可能只是统计波动。我会把top 3-5的细胞类型都拿出来看结合marker基因和通路富集判断哪些是真正有生物学意义的。有时候top 1是一个罕见的细胞亚群样本量很小评分虽然高但稳定性差这时候我会更关注top 2或top 3里样本量充足的细胞类型。最后seismic的结果要和领域知识结合。如果跑出来的top细胞类型和已知的疾病机制完全不符比如一个自身免疫病跑出来top是神经元那大概率是数据有问题而不是发现了新机制。这时候要回头检查数据对齐、注释质量、批次效应。如果跑出来的结果和已知机制一致那说明流程是可靠的可以进一步挖掘新基因。如果跑出来的是“合理的意外”比如top细胞类型是之前没关注过的基质细胞那可能就是一个真正的新发现值得深入追下去。这套流程后续还可以扩展。比如结合空间转录组数据看看驱动基因在组织空间上的分布或者结合ATAC-seq数据看看驱动基因的调控区域是否开放。seismic只是一个起点它帮你把GWAS和单细胞连起来后面的机制研究才是真正有意思的部分。