
上个月我在整理发育生物学课题时翻到一篇最新的Cell子刊论文。让我印象最深的并不是它发现了多少新细胞类型而是打开作者公开的GitHub仓库时发现从GEO下载原始公共数据到单细胞质控、聚类注释、拟时序分析再到最后用来支撑“发育命运决定”故事的那几张关键图表全部都有可复现代码。这意味着什么意味着今天做发育生物学研究拉开差距的不再是“谁手里握着稀缺数据”而是“谁能把公共数据用更合适的角度重新讲出一个新故事”。这篇文章想聊的就是这条完整的思路如何利用公共数据以复现代码为主线搭建一套发育生物学分析流程并最终把结果组织成一篇逻辑自洽的研究叙事。在正文中我会结合自己实际跑通这类流程的经验把数据库选择、分析管线、轨迹推断、调控网络、工程化复现以及最容易踩到的坑全部拆开来讲。无论你是刚入门的生信小白还是正在准备一篇基于公共数据论文的在读研究生这套方法都可以直接套用。1. 公共数据素材库发育生物学研究该去哪找数据1.1 数据库选型不只GEO一个答案不少人的第一反应是“公共数据GEO”这个思路没错但发育生物学场景里它往往不是最优起点。发育研究最核心的需求是“动态变化”同一个器官在不同发育阶段的样子、某个谱系从祖细胞到成熟细胞的过渡状态。这类数据散落在多个平台而且每个平台的元数据结构差异很大。我列一张自己常用的素材地图按发育生物学场景做了归类数据库数据类型发育生物学常用场景注意点NCBI GEObulk RNA-seq、scRNA-seq、ChIP-seq下载原始数据、跨数据集重分析元数据经常不完整需人工确认样本分组ArrayExpress / BioStudies与GEO类似欧洲为主补充检索、部分数据仅在此发布接口友好适合程序化下载Single Cell PortalBroadscRNA-seq、snRNA-seq查看已处理矩阵、快速筛选数据集部分数据集下载有访问限制CellxGene单细胞 h5ad / h5 格式跨组织、跨发育阶段整合分析元数据字段标准直接喂给ScanPyAllen Brain Atlas / 发育图谱资源原位杂交、scRNA-seq神经发育、空间表达模式参考数据格式不够统一需要转换Expression Atlas标准化基因表达数据验证关键基因的时空表达趋势注意基因ID版本转换从维护成本来看我最推荐的是CellxGene GEO 的组合。CellxGene 的优势在于数据已经经过统一坐标系和基因命名处理很多发育阶段的细胞图谱都能直接以 h5ad 格式拿到省去了大量格式清洗工作。但它的缺点也很明显只能拿处理后的矩阵拿不到原始测序数据所以如果你需要重新走比对、定量流程还是得回到 GEO 下载原始文件。1.2 判断一个数据集值不值得复现的四个标准公共数据量很大但不是每一个都值得花两周时间去复现。我判断标准大概有四条元数据覆盖度样本有没有明确的发育阶段、组织部位、生物学重复。很多公共数据集的元数据只写着“胚胎样本”四个字这种数据跑出来的发育轨迹很难落到具体生物学问题上故事自然也难讲。时间序列完整性发育生物学最怕“中间状态缺失”。理想的数据集应该是连续发育阶段比如 E10.5、E11.5、E12.5 每个阶段至少两个重复。如果只有出生前和出生后两个时间点轨迹推断大概率会失真。技术平台一致性同一批数据最好来自同一测序平台、同一建库方案。10x 的 v2 和 v3 化学试剂在捕获效率和基因检出数上差异明显混在一起分析会产生严重的批次效应。如果作者已经提供了整合好的对象文件优先用作者给的版本。原始数据可得性要复现代码就必须能看到原始数据或至少处理后的 count 矩阵。很多文章只提供 R 语言 Seurat 对象不提供 raw count这会让复现者在基因表达定量环节失去控制权。这四个标准筛下来真正值得投入的数据集可能只剩两三个。但在这两三个里深入做下去产出比远高于去“大而全”地把所有数据先跑一遍。我自己的习惯是筛选阶段花三天确定主数据集后后续分析基本全部围绕它可以延伸出两到三个子问题。2. 从原始公共数据到细胞状态矩阵管线搭建与选型逻辑2.1 为什么每个复现项目都要区分上游处理和下游分析拿到公共数据到正式分析中间隔着一条很容易被忽略的环节上游处理。很多拿到公共数据的同学直接下载作者上传的矩阵或 Seurat 对象就开始聚类。这在很多时候可行但如果你要讲一个“新故事”我建议还是把上游掌握在自己手里因为这决定了后续分析的自主权。上游处理指的是原始测序文件FASTQ或者原始计数表经过质量评估、比对、基因定量得到基因-细胞表达矩阵。这一步使用的工具取决于数据来源。如果是 10x Genomics 数据可以用 Cell Ranger 或 STARsolo如果是 Smart-seq2 这类全长转录组通常用 salmon 或 RSEM 做定量。工具选型的底层逻辑很简单每类建库方案对应的转录本生物信息特征不同定量算法必须与之匹配。下游分析则是从表达矩阵开始质控过滤、归一化、高变基因识别、降维、聚类、注释、拟时序等。这一层我基本都在 ScanPy 或 Seurat 之间二选一。个人偏好是 ScanPy其中很重要的一个原因是ScanPy 的数据结构 AnnData 在生产实践中更容易做模块化处理中间结果保存、跨步骤共享都更干净。对于复现别人工作场景ScanPy 还具备一个额外优势——它天然适合从 h5ad 格式直接开始分析能快速接入 CellxGene 和很多新发布的公共单细胞图谱数据。2.2 质控指标里的门道过滤阈值不能照搬质控是复现项目里最容易被“无脑照搬”的一步。很多人直接照抄教程里的min_genes200, percent_mt20然后发现自己的数据聚类结果乱成一团。原因很简单不同数据集的技术噪声水平、样本保存条件、组织解离难度差异非常大。发育组织样本尤其容易遇到解离应激线粒体基因比例普遍偏高。我会用一套“分布驱动式”的质控策略而不是死阈值。核心思路是先看基因数、UMI 数、线粒体占比的分布再用数据的分布特征来判断阈值。import scanpy as sc adata sc.read_10x_h5(sample_filtered_feature_bc_matrix.h5) adata.var_names_make_unique() # 计算质控指标 adata.obs[mt_pct] ( adata[:, adata.var[gene_symbols].str.startswith(MT-)].X.sum(axis1) ).A1 if hasattr(adata.X, A) else adata[:, adata.var[gene_symbols].str.startswith(MT-)].X.sum(axis1) sc.pp.filter_cells(adata, min_counts1000) sc.pp.filter_genes(adata, min_cells10) # 观察分布再决定阈值而不是套用固定值 sc.pl.violin(adata, [n_genes_by_counts, total_counts, mt_pct], multi_panelTrue)这个过程的经验点是先做宽松过滤只去掉明显空滴和极低表达基因然后可视化分布再根据每个样本的峰值位置收紧阈值。另外提醒一点发育生物学数据里往往存在生理性高线粒体比例的细胞类型比如早期胚胎细胞线粒体活跃度就很高。如果机械地按 20% 卡阈值很可能把关键的前体细胞全部过滤掉——这种情况我踩过一次重新读完原始论文的补充方法才发现对方是因为有大量线粒体高占比的“合法细胞”才特意放宽容纳度。双细胞检测也值得纳入常规流程。Scrublet 是性价比很高的选择它通过模拟双细胞来估计每个细胞的双细胞分数阈值可以结合分数分布手动调整。公共数据的双细胞污染情况远比想象中严重特别是在细胞类型异质性高的胚胎组织中。3. 把“时间”装进数据拟时序、RNA速率与轨迹推断的复现细节3.1 拟时序工具到底在算什么发育生物学故事的叙事核心永远是“从A到B”。在单细胞数据里这种“从A到B”是通过拟时序来体现的。拟时序的基本假设是细胞在分化过程中存在一个连续状态空间我们观测到的每一个单个细胞都只是这个连续过程中的一张瞬时快照。Monocle3、Slingshot 和 scVelo 是三个最常用轨迹推断方案但它们解决的是不完全相同的问题。Slingshot先在降维空间中找到聚类后的细胞群再通过最小生成树构造主路径。优点是稳健、抽象程度低适合你“大概知道有哪些细胞群、想验证它们之间的先后关系”的场景。Monocle3把细胞嵌入到一个由分区图构成的流形中再学习根节点到终末细胞状态的最短路径。它的最大优势是学习到的轨迹更贴合数据本身的结构但参数敏感度较高对根细胞的选择非常敏感。scVelo基于 RNA 剪接动力学未剪接/剪接 mRNA 比例直接估计细胞的“速度向量”不需要事先指定根细胞。它最接近真实的发育时间但前提是数据里能测到足够的未剪接 reads。选型上我的经验是先用 Slingshot 或 scVelo 做“粗确认”再用 Monocle3 做“精修”。如果你从一开始就用 Monocle3且对根细胞指定错误后续所有伪时间轴的生物学解读都会被带偏。3.2 轨迹推断了怎样验证它可信轨迹推断出来不代表故事成立。你还需要三个维度的证据来交叉验证基因表达连续性真正沿着轨迹分化的基因表达变化通常平滑而不是跳变。选定一组谱系标记基因把它们按伪时间轴绘制出来观察是否存在符合预期的梯度表达。与已知生物学一致如果轨迹推断说 T 祖细胞来源于某个内皮样群体那么该群体里应该能检测到内皮相关的转录特征。如果完全矛盾优先怀疑轨迹推断参数。RNA 速率方向一致性scVelo 估算出的速度向量在嵌入空间中应该是“平滑指向终末状态”的。如果速度箭头混乱一种可能是切换率矩阵学习不充分另一种可能是数据本身捕获的转录动力学信号太弱。代码上用 scVelo 做 RNA 速率的大致流程是这样import scvelo as scv adata scv.read(processed_adata.h5ad) scv.pp.filter_and_normalize(adata, min_shared_counts20, n_top_genes2000) scv.pp.moments(adata, n_pcs30, n_neighbors30) scv.tl.recover_dynamics(adata, n_jobs8) scv.tl.velocity(adata, modedynamical) scv.tl.velocity_graph(adata) scv.pl.velocity_embedding_stream(adata, basisumap, colorcell_type)跑通 RNA 速率有个非常容易被忽略的要点recover_dynamics是一个非常耗时的步骤在 10 万细胞级别数据上如果不开多线程可能要跑一晚上。而且它不是每次都收敛良好如果发现速度流场一片混乱可以先尝试提高n_top_genes或换用更严格的高变基因选择而不是急着调速度模型参数。4. 从聚类到生物学故事注释、富集与调控网络串联证据链4.1 细胞类型注释不能只靠一个 marker 列表发育生物学数据里的细胞类型注释比成年组织的注释要棘手得多。原因在于发育过程中细胞“身份”是高度可塑的同一个基因在 E11.5 可能是某个前体细胞的 marker到了 E14.5 可能变成了另一个分化细胞的 marker。如果只凭一篇文献里的 marker 列表直接打标签很容易把处于中间状态的细胞强行归到最终命运。我目前常用的注释策略是“三层证据法”已知 marker 集合打分用 AddModuleScore 或 AUCell 对一组代表不同谱系的 marker 基因打分得到每个细胞的谱系倾向。公共参考图谱映射如果研究组织有已经发表的胚胎图谱用 celltypist 或 scArches 把查询数据映射到参考数据上用参考注释作为第二层证据。差异基因人工复核自动注释结果最终一定要回到差异表达基因列表里人工过一遍。特别是对发育细胞我会重点看转录因子表达比如 Foxc2 出现意味着淋巴内皮前体特征GATA 家族高表达常与造血谱系相关。这三层注释经常会出现结果不一致的情况。以我的经验取交集并保留“状态型注释”例如“向XX谱系分化的前体”而不是绝对化的“XX细胞”是更稳妥的做法。4.2 差异表达、GO/KEGG 与 GSEA证据链怎么串注释完成后故事要往前推一步这一群细胞和那一群细胞之间到底发生了什么生物学变化差异表达是这个环节的基础。但发育故事里我们看到的大量差异基因其实是连锁反应的结果它们背后的调控程序才是关键。所以我的固定流程是先做差异表达再从中筛选潜在关键转录因子然后用 GSEA 看整条通路级别的变化趋势。GO/KEGG 富集解决的是“哪些功能被激活”GSEA 解决的是“哪些连续基因集整体发生偏移”后者对发育分化事件更敏感。import scanpy as sc sc.tl.rank_genes_groups(adata, groupbycell_type, methodwilcoxon) sc.tl.gsea(adata, groupbycell_type, gene_setsKEGG)实际项目里别只对“细胞类型”做差异建议同时对“发育阶段”这个维度的差异做一次。两次差异结果放在一起对比经常能发现很有意思的规律某个基因没有在细胞类型间出现差异但在发育阶段间出现显著变化——这类基因很可能参与的是阶段转换调控而不是谱系维持这本身就是很好的故事切入点。4.3 转录调控推断SCENIC 是如何让发育故事拥有“因果感”的发育生物学故事的终极问题是“是什么决定了这个细胞做出这个命运选择”单靠差异表达不能回答这个问题因为大量基因表达变化只是结果并不是原因。转录调控推断的价值就在这里。SCENIC 这类工具的做法分两步第一步基于共表达和 DNA 基序信息推断出转录因子-靶基因调控模块regulon第二步用 AUCell 计算每个细胞中每个 regulon 的活性。活性高的 regulon往往能对应到驱动细胞状态转换的核心转录因子。# 假设已有 pySCENIC 环境 pyscenic grn loom/adata.loom \ hg38__refseq-r80__10kb_up_and_down_tss.mc9nr.feather \ --output grn_adj.csv \ --num_workers 8 \ --seed 42 pyscenic ctx grn_adj.csv \ hg38__refseq-r80__10kb_up_and_down_tss.mc9nr.feather \ --annotations_fname motifs-v9-nr.hgnc-m0.001-o0.0.tbl \ --expression_mtx_fname loom/adata.loom \ --output ctx_output.csv pyscenic aucell loom/adata.loom \ ctx_output.csv \ --output scenic_output.loom \ --num_workers 8SCENIC 跑完后的关键产出是 regulon 特异性的“活性热点”比如某个 regulon 在祖细胞群活性很高在分化成熟细胞中活性消失这就是一个非常漂亮的“窗口期信号”。把这个信号和拟时序分析结合起来你就能在轨迹上看到“那扇命运之门在什么时候打开、什么时候关上”。这里要提醒一下SCENIC 的计算成本很高公共数据的细胞量动辄十几万建议先随机抽 5 万细胞做初步分析确认 regulon 候选后再在全量数据上做 AUCell 打分。这样能节省大量时间和内存且初步分析的结论通常不会因为抽样而改变。5. 工程化复现代码结构、环境和“一键跑通”的经验5.1 一个可复现仓库里真正值钱的文件是什么复现代码这件事很多人理解成“把分析代码传上 GitHub 就行”。但经历过真正复现的人都知道一个仓库能否被他人顺利跑通取决于几个隐性因素。依赖锁定一个environment.yml或requirements.txt只要写了包名而没锁版本时间过去半年后大概率已经跑不起来。优秀的复现仓库会把核心分析包版本精确锁定。数据路径说明公共数据下载来源、下载日期、下载时所用版本。GEO 里的文件偶尔会被重新处理如果不记录复现结果就会对不上。中间产物管理分析链路的每一步都保存中间产物是一个被低估的好习惯。它既方便你自己断点续跑也方便别人在任意环节接入自己的数据。我见过的复现仓库最糟糕的状态是只有一个run_all.R从头跑到尾中途任何一个包升级都会导致全盘崩溃。稍微好一点的做法是分模块、分脚本、有明确命名。更好一点用 Snakemake 或 Nextflow 这类流程化管理工具。5.2 本地跑通全流程的工程习惯在实际动手复现的时候我的目录结构一般是这样project/ ├── data/ │ ├── raw/ # 原始公共数据 │ ├── processed/ # 质控后矩阵 │ └── metadata/ # 样本信息 ├── scripts/ │ ├── 01_preprocess.py │ ├── 02_clustering.py │ ├── 03_trajectory.py │ ├── 04_differential_expression.py │ └── 05_regulatory_network.py ├── results/ │ ├── figures/ │ ├── tables/ │ └── models/ ├── env/ │ └── environment.yml └── README.md每个分析脚本只做一件事输入输出路径全部通过配置文件或命令行参数传入。一开始做这样的工程拆分会感觉多花了一点时间但当数据集从 3 万细胞扩展到 20 万细胞时你会发现能模块化重跑的价值巨大。还有一个小习惯跑完关键步骤后立刻把输出图的 PDF 和高分辨率 PNG 同时保存。很多期刊要求矢量图你不可能每次为了插图再重新跑一遍全流程。5.3 记录复现过程中的“差异”复现一个公共数据集最常遇到的问题不是“跑不通”而是“结果和原文不完全一致”。有几个地方最容易出现数字对不上基因版本同一个 gene symbol 在不同参考基因组注释版本下可能对应不同 ID。过滤阈值文章补充方法里经常只写“按照标准流程”标准流程本身就有不同版本。批次校正算法Harmony 的参数不同整合后 UMAP 明显不同这不算错误但会影响图表视觉呈现。我的建议是把这些差异当作正常现象。复现工作的核心目标不是复刻像素级相同的图而是确认分析结论的可重复性原文说这群细胞高表达 A、B、C 基因并且沿着某条轨迹分化你的结果能不能重现同样的模式。能说明这个公共数据的故事是稳固的不能恰恰说明你有机会挖出“新故事”。6. 公共数据复现中的高频坑位批次效应、内存和随机种子6.1 批次效应不是靠 Harmony 一个按钮就能解决公共数据最让人头疼的就是批次效应。发育生物学数据尤其明显不同胚胎、不同解离批次、不同测序深度带来的技术噪声经常比真实生物学差异还大。整合工具的选择要视情况而定工具核心思路适用场景注意事项Harmony迭代聚类 校正嵌入大群体、细胞类型谱系一致的整合校正过度时会出现“假融合”把真正的分化状态合并BBKNN基于邻居图的批次连接快速探索、批次间重叠少无法直接输出校正后表达矩阵下游拟时序需另想办法Scanorama共享基因的相似性拼接批次间有较大表达差异时对内存占用较高容易在特征选择阶段出错scVI深度生成模型想用概率框架处理噪声需要 GPU 或较长训练时间不适合快速验证我的核心经验是**把“整合”视为一种分析选择而不是默认动作。**如果每个批次本身包含了足够的完整发育阶段我甚至不会做显式整合而是用“合并后聚类 查看批次分布”的方式评估。只有当不同批次的细胞类型构成高度倾斜时才考虑对校正矩阵做处理。做整合后一定马上画一张“批次分布 UMAP”再看一眼“已知 marker 的表达是否仍然保留了原有的空间格局”。批次校正若做得好细胞类型应形成紧凑的 cluster而不是按批次颜色分堆。6.2 内存、临时文件和重复计算公共数据单细胞图谱的规模远超多数人的本地电脑能承受的范围。我遇到过 60 万细胞的数据集UMAP 直接吃了一上午 CPU临时文件把磁盘写满的事也发生过。几个有效的实践矩阵格式优先用稀疏存储ScanPy 的.X如果是 numpy 稠密矩阵内存占用可能直接翻十倍。用.csr_matrix或 AnnData 默认的稀疏格式能省下大量内存。计算时用backed模式AnnData 支持backedTrue可以把数据留在磁盘上按需加载到内存。这对超大数据集非常实用。把跑过的中间结果写成.h5ad保存一次聚类跑了三小时随手存盘后面调参数直接用缓存数据而不是重新加载原始矩阵再重跑全套。6.3 随机种子与参数锁定单细胞聚类分析里UMAP 和基于图的社区检测Leiden 聚类都依赖随机初始化。同一个数据两次运行得出的 cluster 编号可能不同这个不是 bug而是算法特性。但复现代码时这就很要命。所以从一开始就要random_seed42一以贯之。ScanPy 里常见的种子位置包括sc.pp.neighbors(..., random_state42)、sc.tl.leiden(..., random_state42)、sc.tl.umap(..., random_state42)。我还会在脚本最顶端统一设置全局随机种子import numpy as np import random np.random.seed(42) random.seed(42)另一个容易忽略的是**Harmony 和 scVI 这类整合方法也有随机成分运行前一定要记录种子。**否则今天跑出来的 UMAP 图明天再跑可能就换了布局审稿人重新运行你的代码后自然会产生质疑。7. 怎么把复现结果讲成一篇“发育新故事”7.1 先设计故事骨架再画图很多人分析做完了才开始想“我要讲个什么故事”这其实是本末倒置。以我复现多篇公共数据论文的经验一个扎实的发育生物学故事骨架通常长这样从一张整体细胞图谱切入告诉读者“这个器官在某个发育窗口里有哪些细胞状态”然后锁定某一个特别有意思的谱系细胞群展开轨迹分析展示从祖细胞状态到成熟状态的连续分化过程。接下来用差异表达和富集分析指出“分化过程中发生了什么”最后用转录调控推断回答“为什么是这个时候发生。”这条线索对应到图表上就是图谱总览 → 谱系追溯 → 轨迹推断 → 关键通路变化 → 调控网络窗口。这基本是“用公共数据讲新故事”最通用的结构也和我们前面做的分析模块完全对应。动手分析之前先把这个结构画在纸上后面每一步都是往里填内容而不是漫无目的地探索。7.2 三条做出新意的路线基于公共数据的发育研究最怕的事情是“把别人的结果重新命名之后又发一遍”。要让故事有增量我建议走这三条路线。第一条跨物种比较。同一个器官、同一发育阶段不同物种的数据放在一起比较可以回答“哪些发育程序是保守的哪些是物种特异的”。这类分析只需要公共数据但结论的普适性很强。第二条跨时间整合。同一器官不同发育阶段的数据往往分散在多个数据集里将它们拼接起来本身就是新数据集。很多时候单个数据集的样本量不足以支撑完整轨迹但把三四个数据集整合后就能看到一段完整的命运变化。第三条重注释与重分析。有些公共数据本身已经发表过但当时的分析限于工具和方法没有深挖调控机制。多年后你用 SCENIC、RNA 速率这些新工具重新分析往往能发现新的调控窗口。这类“旧数据新方法”的组合是当前低成本撬动高质量故事的捷径。我举一个具体例子假设有两个公共数据分别覆盖了心脏发育早期和晚期阶段但原始文章都只是做了细胞类型普查。你把他们整合以后关注一个此前未被重视的、处于“内皮-间充质过渡”状态的细胞群用拟时序做出这个过渡的完整轨迹再结合 SCENIC 发现一个调控这个过渡的关键转录因子家族。这样一来你既没有做任何实验却讲述了一个关于特定谱系命运决定窗口的完整故事——这就是公共数据真正的价值。7.3 复现项目如何给别人留下可用资产最后强调一点既然你的工作建立在复现和公共数据上就更有责任把自己的复现资产也完整开放出来。这包括三件事代码仓库要写清楚运行顺序和依赖环境每个关键图表要注明对应分析脚本和参数公共数据下载信息要精确到数据编号、下载日期和版本。这样做不仅符合学术共享的通行规则还能让这个项目在公开意义上“立得住”。别人基于你的复现成果继续往下做你的工作才有可能被引用进而成为下一段新故事的起点。从我个人的经验来看项目初期多花一个周末时间把工程细节收拾整洁后面节省的时间和沟通成本远远超过这点投入。