
最近生信圈在转的一件事应该就是这份“中国人群视网膜衰老单细胞图谱”的公开。做眼科研究的人在等它做衰老研究的人在等它不少想系统练一遍单细胞流程的初学者也在等它。等它发布后大家发现作者不仅把数据放了上去还把完整的注释结果、marker基因列表、核心绘图代码一起公开等于把一份带参考答案的习题集摆在了桌上。这篇文章我就围绕这份图谱展开把数据背后做了什么、为什么值得复现、获取数据后怎么从零开始跑通全流程、以及实际复现中容易掉进去的坑全部梳理一遍。哪怕你之前没跑过单细胞项目对照这份图谱也能把Seurat那套核心分析吃透。1. 这份图谱凭什么引人关注项目定位与核心价值拆解1.1 视网膜为什么是衰老研究的理想模型视网膜是中枢神经系统里少数可以直接被观察到的组织。它结构分层清晰细胞类型明确主要由感光细胞视杆和视锥、双极细胞、无长突细胞、水平细胞、神经节细胞RGC、Müller胶质细胞、小胶质细胞、血管内皮细胞等组成。不同细胞各司其职又彼此通过突触和旁分泌信号紧密耦合任何一个细胞亚群的衰老变化都可能牵动整条视觉传导链。更重要的是视网膜的衰老和多种高发疾病直接相关。年龄相关性黄斑变性AMD是老年人视力丧失的主要原因糖尿病视网膜病变、青光眼的发病率也随年龄显著上升。研究视网膜衰老本质上是在为这些退行性疾病寻找早期标记和干预窗口。而之前公开的单细胞视网膜数据大多来自欧美人群东亚人群的参考图谱长期缺乏。这份中国人群视网膜衰老图谱恰好补上了这个缺口。1.2 图谱数据集的整体构成从发布资料来看这份图谱覆盖了不同年龄阶段的中国人群视网膜样本经过严格的单细胞转录组测序之后系统性地完成了细胞注释和衰老相关分析。数据公开部分主要包括以下几个层级原始测序数据或比对后的表达矩阵用于后续独立分析经过质控、降维聚类和注释后的细胞亚群信息每一类细胞都有对应的marker基因列表衰老相关的差异表达结果、功能富集结果用于生成论文核心图表的完整分析代码。对于复现者来说表达矩阵加上注释信息就是最值钱的部分。你不用重复跑一遍cell ranger比对可以直接从质控后的矩阵起步节省大量时间和计算资源。这一点对没有服务器集群的个人研究者特别友好。1.3 这份图谱适合谁去复现我大致把适合复现的人群分成三类。第一类是做眼科和衰老基础研究的人。他们关心的是中国人群视网膜里到底有哪些细胞亚群在衰老中变化最大哪些基因可以作为候选靶点。复现一遍注释和差异分析后可以得到自己的分析结果也可以在这些结果基础上直接做后续功能实验。第二类是刚入门的单细胞生信学习者。这份数据注释完整、代码公开、图谱结构清晰比自建课题数据练手要舒服得多。你可以拿着它完整走一遍“质控-聚类-注释-差异-可视化”的标准流程遇到问题还有论文结果可以对照。第三类是做生信工具开发或流程搭建的人。有了标准数据集就能在本地验证新方法、做多数据集整合或者用来跑自动注释工具的性能评测。公开图谱某种程度上承担了benchmark数据集的角色。2. 图谱背后的技术逻辑单细胞分析流程是怎么设计的2.1 建库方式与表达矩阵的格式判断视网膜组织比较特殊细胞种类多、组织致密度高而且感光细胞等大细胞比例高。从这类公开图谱的通例来看大多采用10x Genomics Chromium平台进行单细胞转录组建库。你拿到手的矩阵通常是一个高度稀疏的计数矩阵行是基因列是细胞数值表示每个细胞中检测到的转录本count数。判断建库方式最直接的方法看文件格式。如果下载目录里包含filtered_feature_bc_matrix这样带barcode和feature的文件夹基本就是10x平台的产物。如果拿到的是.csv.gz或者matrix.mtx.gz也大概率是10x格式转换过来的。复现前先确认矩阵格式避免后面读取时反复报错。2.2 质控参数的选择逻辑单细胞质控的核心是筛掉低质量细胞和空液滴。常规做法看三个指标每个细胞检测到的基因数nFeature_RNA过低说明细胞破裂或转录本捕获失败过高可能是一个液滴包含了两个细胞UMI总数nCount_RNA是测序深度的直接体现线粒体基因比例percent.mt过高说明细胞状态差胞质RNA流失而线粒体RNA相对富集。很多人直接在Seurat里写subset(nFeature_RNA 200 nFeature_RNA 5000 percent.mt 20)但这份图谱发布在不同组织上视网膜感光细胞转录本数量本身偏高Müller胶质细胞代谢活跃线粒体比例分布也和外周血完全不同。我的建议是先画violin图和QC散点图观察分布边界再结合实际确定的阈值。数据发布方如果给出了QC阈值优先以原始阈值为主复现时再去套通用参数很容易得到和原文不一致的细胞数。2.3 降维聚类与注释方法的组合思路拿到质控后的细胞后标准路径是归一化、高变基因筛选、PCA降维然后用UMAP或t-SNE展示最后做聚类分群。Seurat里的FindClusters使用的Louvain算法需要指定分辨率分辨率越高分群越多。不要一个分辨率跑到底可以按0.5、0.8、1.0分别试对比marker表达后选择生物学上最合理的分群数。细胞注释是整条流程中最依赖经验的环节。这个项目里我推测采用的是“marker基因打分参考图谱映射人工核对”的组合方式。视网膜的经典marker比较明确视杆细胞看RHO、NRL视锥细胞看ARR3、OPN1MW双极细胞看VSX2无长突细胞看TFAP2A、GAD1RGC看RBPMS、SNCGMüller胶质看RLBP1、GLUL小胶质看P2RY12、C1QA血管内皮看CLDN5、PECAM1。先拿这些经典marker做DoHeatmap和FeaturePlot按表达模式把大类分出来再往下分亚群。2.4 衰老相关分析的常用方法图谱既然叫“衰老图谱”分析重点自然落在年龄相关的细胞状态变化上。常见做法有几类分年龄组做差异表达分析看每个细胞亚群中随年龄上调或下调的基因用基因集打分AddModuleScore评估衰老相关通路活性比如炎症、氧化应激、DNA损伤修复、线粒体功能障碍相关基因集做细胞通讯分析比较不同年龄组之间配体-受体相互作用的强度变化细胞通讯常用的工具是CellChat做拟时序分析用Monocle3等工具推断细胞分化或状态转变轨迹。这些分析在公开代码里大概率都有对应脚本。复现的时候不需要每个脚本都跑先挑和你研究问题最相关的部分跑通了再补其他分析。3. 复现之前的关键准备数据获取、环境搭建与资源评估3.1 数据去哪里找先回答一个很多人私信问的问题数据从哪下。这类公开图谱数据的存放通常分两个地方。其一国际通用数据库GEOGene Expression Omnibus检索关键词用“retina aging single cell Chinese”之类找到对应编号的GSE数据即可下载。其二国内的国家基因组科学数据中心NGDC下的GSA子库检索“视网膜 衰老 单细胞”或项目名称也可以找到。作者通常还会在论文的Data availability段落写明两个数据库的访问编号优先按论文给的编号去检索最准确。下载时注意区分两个概念raw data是原始测序下机数据体积很大按T计算processed data是处理后的表达矩阵通常几十到几百MB复现分析用它就够了。非必要不下raw data除非你要自己重新比对。3.2 复现环境与依赖清单跑这套流程主流方案是R Seurat。除了Seurat本身还需要一批配套包。我建议按功能分组安装数据读取与矩阵格式转换Seurat、SeuratData、Matrix质控与可视化ggplot2、patchwork、dplyr、RColorBrewer批次整合与多样本合并harmony或者Seurat的IntegrateData差异分析Seurat内置的FindMarkers就够了但有时需要MAST、DESeq2做补充功能富集clusterProfiler、org.Hs.eg.db细胞通讯和拟时序CellChat、monocle3这两个包依赖比较多建议单独花时间装。R版本建议用4.2以上Seurat用5.x版本。注意Seurat 5和Seurat 4的对象结构有差异如果作者代码是基于旧版写的读入后可能要用UpdateSeuratObject做一次对象升级否则某些函数会报错。3.3 计算资源与目录规划很多人低估了单细胞分析对内存的需求。一个典型的视网膜图谱细胞数量通常在几万到十几万之间基因数两万以上。用Seurat做完整流程16GB内存勉强能跑32GB会比较舒服。如果你打算在本地笔记本上玩建议先把读取矩阵后的对象用saveRDS存下来后续聚类、差异分析都从RDS对象读取不要反复从原始矩阵重来。内存不够还有一个折中方案用Python的Scanpy做完整流程Scanpy对内存的管理比R好一些不过可视化风格和Seurat差异比较大和原文代码对照起来不如R方便。目录规划上我建议按这个结构放文件retina_atlas/ ├── data/ │ ├── raw_matrix/ │ └── metadata/ ├── scripts/ │ ├── 01_qc.R │ ├── 02_cluster.R │ ├── 03_annotation.R │ └── 04_diff_exp.R ├── output/ │ ├── figures/ │ └── tables/ └── rds/ └── retina_seurat.rds这样每一步的输入输出都很清晰回头排查问题也方便。4. 全流程复现实操从只读矩阵到核心图表4.1 第一步读入矩阵与基础质控确认你拿到的是filtered矩阵只包含细胞barcode就可以用Read10x读入。如果拿到的是其他格式用readRDS或read.csv读入后转换成Seurat对象。library(Seurat) library(dplyr) library(Matrix) # 读取10x格式矩阵 data_dir - data/raw_matrix/filtered_feature_bc_matrix counts - Read10X(data.dir data_dir) # 创建Seurat对象 so - CreateSeuratObject(counts counts, project Retina_Aging_CN, min.cells 3, min.features 200) # 计算线粒体基因比例 so[[percent.mt]] - PercentageFeatureSet(so, pattern ^MT-)这里有两个参数值得说明。min.cells 3要求基因至少在3个细胞中表达用来过滤在组织中几乎不表达的基因。min.features 200用来过滤基因检出数过少的细胞。这两个值属于通用设定之后还会做更严格的质控过滤。质控可视化是决定阈值的第一步VlnPlot(so, features c(nFeature_RNA, nCount_RNA, percent.mt), ncol 3, pt.size 0.01) # 根据分布进行过滤阈值需要结合数据分布调整 so - subset(so, subset nFeature_RNA 200 nFeature_RNA 6000 percent.mt 15)我做完这一步通常会再把过滤前后的细胞数对比一下。如果过滤掉了超过30%的细胞说明原始数据质量不好或阈值设得太严此时不要强行继续先回到分布图上重新判断。4.2 第二步归一化、高变基因与批次整合视网膜图谱如果包含多个样本样本之间必然存在测序深度、批次效应。处理批次最常用的方式就是harmony。它是Python的harmony-py和R的harmony包本质是迭代聚类降维把混杂信息从PCA嵌入中移除。so - NormalizeData(so) so - FindVariableFeatures(so, selection.method vst, nfeatures 3000) so - ScaleData(so) so - RunPCA(so, npcs 30) # 如果数据来自多个个体建议按样本ID做harmony整合 so - RunHarmony(so, group.by.vars sample_id)多单样本整合后再聚类得到的分群通常比直接用原始PCA聚类更干净。有人会直接跳过harmony但我觉得对于这种跨年龄、多个体样本的图谱整合这一步不要省否则后续注释看到的可能不是生物学差异而是批次差异。4.3 第三步聚类分群与细胞注释聚类分辨率的选择我前面提过实际操作是从低到高扫一遍。so - FindNeighbors(so, reduction harmony, dims 1:30) so - FindClusters(so, resolution c(0.5, 0.8, 1.0)) # 先看不同分辨率的聚类数 sapply(seq_along(levels(someta.data$RNA_snn_res.0.8)), function(i) i)选定合适分辨率后先用经典marker确认大类。比如我想确认感光细胞群就画这几个基因的FeaturePlot和DotPlotFeaturePlot(so, features c(RHO, ARR3, RBPMS, RLBP1, P2RY12), cols c(lightgrey, red), ncol 3) DotPlot(so, features c(RHO, ARR3, NRL, VSX2, GAD1, RBPMS, SNCG, RLBP1, GLUL, P2RY12, C1QA, CLDN5))看到特征表达模式后手动给每个cluster赋细胞类型标签。需要注意亚群注释这一步主观性很强不同人会得出不同粒度。比如Müller胶质细胞可能被分成一个群也可能在不同状态下被分成应激态和静息态小胶质细胞也可能存在homeostatic和activated两种状态。这些细节和论文报道不一定完全一致我的处理原则是大类一定要和原文一致亚群按自己的聚类结果重新判断并在方法部分说明差异。4.4 第四步衰老差异分析与可视化注释完成后就可以做年龄相关的差异分析。把metadata里加上年龄分组比如young和old然后按细胞类型分别跑FindMarkers。so$age_group - ifelse(so$age 60, young, old) so$age_group - factor(so$age_group, levels c(young, old)) Idents(so) - celltype diff_list - list() for (ct in levels(so$celltype)) { diff - FindMarkers(so, ident.1 old, ident.2 young, subset.ident ct, min.pct 0.1, logfc.threshold 0.25) diff$gene - rownames(diff) diff_list[[ct]] - diff }这里subset.ident参数直接指定在特定细胞类型内部做比较逻辑是“同一个细胞类型中老年组相对年轻组有哪些基因变化”。如果样本来自多个个体建议用test.use MAST或者混合模型可以更好处理个体间的随机效应。拿到差异基因后接着做功能富集library(clusterProfiler) library(org.Hs.eg.db) # 以Müller胶质细胞为例 genes_up - diff_list$Muller$gene[diff_list$Muller$avg_log2FC 0.5] ego - enrichGO(gene genes_up, OrgDb org.Hs.eg.db, keyType SYMBOL, ont BP, pAdjustMethod BH)最后把UMAP、marker气泡图、差异火山图、富集条形图一起输出复现基本就完成了。论文里的核心结论一般都能在你自己跑出来的图上看到对应趋势如果趋势完全相反那就要回到前面的步骤找问题。5. 复现过程中的常见问题与排查心得5.1 数据读取阶段报错这个阶段最常见的报错是Read10X找不到barcodes.tsv.gz、features.tsv.gz、matrix.mtx.gz三个文件或者目录名不对。10x新版输出目录是filtered_feature_bc_matrix里面又分了一级目录。直接指向包含三个文件的文件夹即可不要多指一层。另一个高频问题是矩阵中基因名格式。人类视网膜数据如果上游用Ensembl ID做基因名后面所有marker检测都会失效。看到ENSG00000169174这种格式先做ID转换再继续不要硬着头皮往下跑。5.2 内存不足和会话崩溃R跑单细胞流程长期运行后内存碎片化严重最后直接session terminated。我的经验是分步执行每跑完一个大步骤就saveRDS一次下次从头文件恢复而不是把全部代码放在一个脚本里一口气跑完。如果16GB内存机器上还是OOM可以限制工作线程数并开启gcoptions(future.globals.maxSize 8 * 1024^3) options(future.rng.onMisuse ignore) # 减少并行线程 plan(strategy sequential) # 手动触发垃圾回收 gc()另外FindMarkers跑几十个细胞类型时非常占用资源循环里每跑完一个类型就gc()一次能明显降低峰值内存。5.3 注释结果和论文不一致拿到自己聚类结果后很可能发现cluster数量和论文不完全一致。这是正常现象。可能原因包括过滤阈值略有差异、harmony参数设置不同、分辨率选择不同、甚至Seurat和Scanpy的聚类算法本身就有差异。我会把“大类注释一致、亚群合理解释了marker表达”作为复现成功的标准而不是追求cluster编号和论文完全一样。5.4 复现质量怎么验证我常用的验证方法有三种。第一看关键marker是否只在对应细胞群表达比如RBPMS只在RGC群表达如果它在感光细胞群里也高表达说明注释出了问题。第二看细胞比例趋势。论文报道中老年组某种细胞比例降低或升高在你的复现结果里方向应该一致。第三看差异基因的特征。比如老年视网膜中炎症相关基因在小胶质和Müller胶质中上调这类生物学趋势是稳定的。方向不对就回去检查分组变量是否设置反了。我把常见问题整理成一个速查表方便你对照排查问题现象可能原因排查方法Read10x报错目录缺失目录层级不对或三个文件不完整检查目录下是否有三个gz文件基因名全是ENSG上游未进行ID转换用bitr转换成SYMBOLUMAP分群乱、批次明显未做harmony整合在PCA嵌入上运行RunHarmony注释marker无表达矩阵物种或基因名格式错误检查物种前缀检查Symbol格式FindMarkers跑不完内存不足或线程过多设置sequential并定期gc绘制气泡图报错Seurat对象版本不一致先用UpdateSeuratObject升级富集分析无结果差异基因数量太少降低logfc.threshold再次筛选6. 关于这份公开图谱的几点个人体会与扩展思路6.1 复现不能只跑代码更要读代码我见过不少人下载公开数据后把作者代码从头到尾跑一遍图出来了就觉得自己会了。但单细胞分析的可复制性本来就有限服务器环境、R包版本、数据路径都影响结果。复现的真正价值在于读懂每一步为什么要这么做。比如为什么要用harmony而不是Seurat的IntegrateData为什么质控阈值在视网膜上和外周血不一样为什么注释要分两个层次而不是一步到位。这些思考才是在你以后自己处理课题数据时真正能带走的。我自己的习惯是拿到公开代码后先看README或代码注释理清每个脚本的输入输出关系再在代码里加自己的注释改成适合本地路径的版本。这样一套流程下来既复现了论文结果也顺手搭好了一套以后可以直接套用的分析模板。6.2 这份图谱还能做什么扩展复现基础分析只是起点。这份数据真正值钱的地方在于后续可以衍生出很多新分析我简要列几个方向一是跨数据集整合。把这份中国人群图谱和之前发布的欧美人群视网膜衰老数据整合比较不同人种间的衰老共性变化和特异变化。这类分析可以直接写成一篇方法学或比较研究型论文。二是衰老模型构建。用不同细胞类型的衰老特征基因做打分模型在独立数据集上验证评分是否能区分年龄组再和疾病状态做关联分析。三是细胞通讯网络的时间变化。用CellChat比较年轻和老年组的配体受体网络重点找“指向某个关键靶点”的通路比如炎症通路、补体通路。这些通路往往就是后续湿实验验证的候选机制。四是把图谱作为参考做反卷积。对大量bulk转录组数据做去卷积估计样本中细胞类型比例的变化看哪些组织性疾病样本的细胞组成向“衰老模式”偏移。这个方向对临床转化的价值很高。6.3 最后给复现者的一句话说实话我踩过最深的坑是不看数据规模就盲目开跑结果内存爆掉、代码中断、注释返工来回折腾一两周。先看数据说明、先确认矩阵格式、先把QC阈值画出来再过滤、每跑完一步就存RDS这四件事做好复现过程会顺畅太多。公开图谱的价值不仅在于结论本身更在于它给了大家一套可以对照练习的标准流程。认认真真从头到尾复现一遍你收获的不仅是一套图而是整个单细胞转录组分析框架的完整认知。这套认知会在你以后处理任何组织来源的单细胞数据时反复用到。