
开头就得说人话。做过单细胞测序分析的人都知道拿到一张UMAP图只是万里长征第一步——细胞亚群注释完了下一步最常问的问题就是这些细胞之间是怎么变化的在肿瘤、发育、纤维化这类疾病研究里你拿到的永远是某个时间点的“静态快照”但疾病进程本身是动态的细胞从正常状态逐步转变到病理状态中间要经过哪些中间态、哪些基因在驱动这种转变这些才是真正能发好文章、能落到机制上的关键信息。单细胞轨迹分析也叫拟时序分析pseudotime analysis就是干这个的而monocle3是目前用得最多、生态最完善的R包之一。这篇文章我会从零开始把monocle3做疾病研究的完整思路和实操流程捋一遍包括怎么从Seurat对象转换数据、怎么调参数学轨迹、怎么选根节点、怎么找轨迹相关基因再把我自己踩过的一些坑和排查经验写出来。适合刚接触轨迹分析、被文档绕晕的人也适合已经跑通流程但想知道怎么把结果解读到疾病机制里的朋友。整个过程我用一个肿瘤上皮-间质转化EMT的例子贯穿始终这样每个步骤你都知道自己在干什么、为什么这么干。1. 单细胞轨迹分析在疾病研究中的整体思路1.1 为什么疾病研究需要“轨迹”这个视角先说个最基本的逻辑。单细胞测序给你的是成千上万个细胞各自的转录组每个细胞是一个独立样本但细胞和细胞之间不是孤立的——它们在疾病过程中往往处于不同的分化阶段、不同的激活状态或者正沿着某条连续的路径从一种状态滑向另一种状态。举个最常见的例子肿瘤里的癌细胞。一个上皮来源的肿瘤早期细胞还保留着上皮特征表达E-cadherinCDH1到了晚期很多癌细胞会丢失上皮标记、获得间质特征表达VimentinVIM这个过程就是EMT。但EMT不是开关而是一个渐变过程肿瘤组织里同时存在纯上皮、部分上皮、部分间质、纯间质这几种状态的细胞。如果你只看注释后的细胞簇你会觉得它们像几个孤立的岛但实际上它们之间有一条连续的通路癌细胞正在沿着这条路一步步“变形”。轨迹分析就是把这些离散的点重新串成一条或几条线。它根据细胞之间转录组的相似程度推断出细胞状态转变的路径再给每个细胞计算一个“拟时序值”pseudotime代表它在转变过程中所处的相对位置。这样一来你就能回答很多普通差异分析回答不了的问题某个致病基因是在转变早期上调还是晚期上调哪些基因模块先变化、哪些后变化哪群细胞是转变的起点、哪群是终点这对于理解疾病机制、寻找早期干预靶点非常有价值。1.2 monocle3在众多轨迹工具里的定位轨迹分析的工具很多Slingshot、SCORPIUS、PAGA、scVeloRNA velocity各有各的长处但我个人在疾病研究场景里用得最多的还是monocle3理由有三条第一monocle3的学习目标是“主图”principal graph相当于在细胞分布里拟合出一条可以分叉、可以成环的骨架。这个骨架能很好地表达“从一个状态分化成多个方向”这类生物学事件比如造血干祖细胞向下游多个谱系分化或者肿瘤细胞一部分走向EMT、一部分走向增殖这些都能可视化得很清楚。第二monocle3在确定细胞轨迹后自带一套完整的差异分析、基因模块分析工具不需要你再切换到别的R包去折腾。graph_test、find_gene_modules这些函数用起来很顺手能从轨迹里直接找到随拟时序变化的基因再聚成模块做富集整个下游链路是闭环的。第三monocle3的设计从单细胞数据出发底层用UMAP降维、Leiden聚类处理十万级细胞也没太大压力。而且它支持把不同样本、不同批次的细胞放进一个数据集里做分析用align参数校正批次效应这对疾病研究里常见的“正常组疾病组多例样本合并”场景非常友好。当然monocle3也有它的短板UMAP本身有一定随机性不同seed跑出来的轨迹可能略有差异轨迹推断是计算推测不等于真实的谱系追踪结论必须要结合实验或者至少结合RNA velocity做交叉验证。这些后文会详细说怎么处理。2. 从Seurat对象到monocle3的数据准备工作2.1 安装和环境配置如果是从零开始先确认你的R版本在4.0以上建议R 4.2或者4.3。monocle3对依赖包的要求比较杂包括BiocManager安装的一部分Bioconductor包以及一些GitHub上的开发版包。推荐用下面这套顺序装能避开大部分依赖报错# 先安装BiocManager如果还没有 if (!requireNamespace(BiocManager, quietly TRUE)) { install.packages(BiocManager) } # 安装monocle3所需的Bioconductor依赖 BiocManager::install(c(BiocGenerics, DelayedArray, DelayedMatrixStats, S4Vectors, SingleCellExperiment, SummarizedExperiment, batchelor, Matrix, Rcpp, RcppHNSW, irlba)) # 安装monocle3本体来自Trapnell实验室的GitHub install.packages(devtools) devtools::install_github(cole-trapnell-lab/monocle3)安装过程中最容易出问题的是RcppHNSW和batchelor前者在部分Linux服务器上需要系统有较新的gcc后者需要正确安装BiocManager版本。如果报错优先看是不是缺系统依赖比如libgdal、libgeos之类用apt或者yum装上再重试。装好之后加载时如果报“package monocle3 was built under R version xxx”的警告一般不影响使用但如果报错提示找不到某个函数比如preprocess_cds不存在先检查是不是装了monocle2而不是monocle3——这两个包的API差别非常大很多新手在这里被坑过。可以用packageVersion(monocle3)确认版本号正常应该是1.x。2.2 从Seurat对象转换为cell_data_set绝大多数人做单细胞上游分析用的是Seurat做完标准化、降维、聚类、注释之后再切到monocle3做轨迹。这里有一个非常关键的点monocle3最标准的做法不是把原始count矩阵丢给它重新走一遍流程而是直接把Seurat里已经降维好的UMAP坐标和聚类结果转过去。为什么因为你在Seurat里已经做了仔细的QC、批次整合比如Harmony或者CCA、聚类粒度调整这些前期投入不应该浪费而且monocle3的learn_graph是在UMAP空间里做主图学习直接用你熟悉的、已经注释好的UMAP后续解读轨迹和细胞簇身份时心智负担会小很多。转换代码很简单library(Seurat) library(monocle3) # 假设你有一个已经做完注释的Seurat对象seu # 关键确保seu里有UMAP坐标和Cluster信息 cds - as.cell_data_set(seu) # 把Seurat里的UMAP坐标赋值给cds cdsreduce_dim_aux[[UMAP]] - NULL reducedDims(cds) - list(UMAP seureductions$umapcell.embeddings) # 把Seurat的聚类结果赋给cds cdsclusters[[UMAP]] - list( cluster_result NULL, partitions seumeta.data$seurat_clusters, clusters seumeta.data$seurat_clusters ) # 建议把细胞类型注释也存进来方便后面看图 cdscolDatalistData[[cell_type]] - as.character(seumeta.data$cell_type)这里有个小坑as.cell_data_set(seu)转换后cds里的UMAP坐标默认可能是空的所以需要手动从seu里拉出来塞回去。另外cdsclusters[[UMAP]]这个list结构必须包含partitions和clusters两个名字partitions表示大的分区通常就是不同谱系clusters表示更细的细胞簇。如果你只有一级注释两者赋成一样的东西问题不大后面还可以再跑一次monocle3自己的聚类来细分。如果你没有现成的Seurat对象也可以从count矩阵直接构建cds然后走monocle3自己的QC和标准化流程但这意味着前期细致的细胞注释工作要在monocle3里重做效率低很多我不建议。实际项目里几乎都是先Seurat后monocle3这个路径。3. monocle3完整分析流程与核心参数3.1 预处理与降维数据转换好之后正式分析的第一步是preprocess_cds。这个函数做的事情本质上和Seurat里的ScaleDataRunPCA类似但它内置了自己的参数和逻辑用来为后续的UMAP降维做铺垫。cds - preprocess_cds(cds, num_dim 50, method PCA)先解释一下num_dim。这个参数控制你保留多少个主成分作为后续UMAP的输入。我的习惯是先看Seurat里ElbowPlot的拐点如果拐点不明显就直接用50。注意preprocess_cds这一步会重新计算PCA所以它在某些情况下可能和你Seurat里已有的PCA结果有差异但不要紧——只要UMAP坐标我们已经从Seurat带过来了后面轨迹学习主要依赖的是UMAP空间preprocess_cds更多是补全内部数据结构和表达矩阵的行列信息。降维这一步用reduce_dimensioncds - reduce_dimension(cds, reduction_method UMAP, preprocess_method PCA)默认情况下它会在cds内部重新算一遍UMAP覆盖掉我们之前从Seurat带过来的坐标。如果你想让monocle3用Seurat的UMAP结果可以在参数里加umap.fast TRUE然后不传坐标但更省事的做法是直接接受monocle3自己算的这个UMAP因为后续learn_graph和可视化都是基于这个内部UMAP的。为了确保可重复性这一步一定要设置随机种子比如set.seed(2024)否则不同次运行轨迹可能漂移。3.2 聚类与分区partition的生物学意义降维完就是聚类。monocle3默认用Leiden算法这是目前公认在单细胞聚类里速度和效果比较均衡的算法。cds - cluster_cells(cds, resolution 1e-3)resolution是Leiden算法里控制聚类粒度的参数默认1e-3。如果你发现聚类粒度过粗或者过细可以调大或者调小。这里要特别强调的是cluster_cells会同时产生两个层级的结果clusters和partitions。partitions是把差异特别大的细胞群分开的大分区比如一个数据集里同时有免疫细胞和上皮细胞大概率会被分成两个分区clusters则是在分区内部更细的亚群。为什么分区这么重要因为monocle3的轨迹学习原则是“每个分区内部学一条主图”也就是说如果某两个细胞类型被算法分到了不同partition它们就不会被强行走同一条轨迹。这对疾病研究来说非常合理——你不能把T细胞和上皮细胞硬拉到一个分化轨迹里那是没有生物学意义的。但如果你的研究问题恰恰是跨谱系转变比如上皮细胞转分化成间质细胞本身形成了一个连续过渡那你可能需要调大resolution让它们落进同一个分区或者手动合并分区这个后面会讲。聚类完成后建议先用plot_cells快速检查一下分区和聚类的分布确认和你在Seurat里注释的细胞类型是否对应如果明显错位回头检查是不是UMAP坐标赋值出了问题。3.3 学习轨迹主图learn_graph的原理与调参接下来是最核心的一步——learn_graph也就是在UMAP空间里学习一条主图。cds - learn_graph(cds, use_partition TRUE)use_partition TRUE表示每个partition分别学习轨迹。这一步背后的算法是先在细胞分布上构建一个最近邻图然后在此基础上寻找一条能够经过数据主体区域、同时保留主要分叉节点的曲线骨架。这个过程类似用一根可弯曲的金属丝去“穿过”一团棉花糖金属丝的形态就是轨迹骨架。这一步骤中我最常遇到的问题是主图节点principal graph nodes过少或过多。节点太少轨迹会过于简化把真实存在的分支抹掉了节点太多轨迹会过度拟合出现很多琐碎的小分叉反而不好解读。monocle3里有一个隐藏参数learn_graph_control里面可以调ncenter或者geodesic_neighbor_number等但默认参数在大多数数据集上表现都还行我建议先跑默认再根据可视化结果决定是否调整。如果learn_graph跑完后轨迹图上有一些“飞出去”的孤立短枝很多时候是少数离群细胞导致的可以在learn_graph之前先用choose_graph_segments清理一下或者直接在umap坐标层面把离群细胞过滤掉。这个属于细活等到后面“常见问题”部分细说。3.4 根节点选择决定拟时序的解释方向轨迹学完紧接着是order_cells——这一步决定整个拟时序分析的成败。order_cells需要你指定一个“根节点”root node也就是轨迹的起点然后monocle3会沿着主图给每个细胞计算拟时序值。# 先可视化手动找到根节点编号 plot_cells(cds, color_cells_by pseudotime, label_groups_by_cluster TRUE, label_leaves TRUE) # 运行交互式选择根节点 cds - order_cells(cds, reduction_method UMAP)运行order_cells(cds)之后R会弹出一个图窗口你可以点击主图上的某个节点位置设定根。如果是在服务器上跑没有图形界面可以用下面的方式在代码里指定根节点# 先获取主图节点列表 # 然后根据节点编号设置根 cds - order_cells(cds, root_prune_graph TRUE, root_cells colnames(cds[, clusters(cds) 某个根细胞簇]))order_cells支持两种指定方式一种是指定root_cells即给出一批细胞作为轨迹起点另一种是交互式点击root_nodes。实际项目中我更喜欢用root_cells——因为我可以根据已有的生物学知识明确指定“这簇注释为正常上皮的细胞是EMT的起点”这比在图里手动点一个节点更可解释、也更好复现。根节点选错拟时序结果会完全变样。比如EMT轨迹里如果你把间质态细胞选为根那拟时序就会从间质往上皮方向跑下游所有基因模块的解读方向就反了。所以选根之前务必回到你的细胞注释和marker表达上确认哪群细胞是“最初始”的状态。对肿瘤EMT这个例子正常、非转化或上皮特征最明显的细胞簇就是合理的起点。4. 下游分析从轨迹到疾病机制的解读4.1 用graph_test找到随拟时序变化的基因轨迹构建好了拟时序也算了接下来才是真正和疾病研究结合的部分。第一个常用操作是用graph_test检测哪些基因的表达显著沿着轨迹变化——这才是你发文章时“轨迹分析发现XX基因动态变化”的依据来源。# 检测沿轨迹变化的基因 gene_fits - graph_test(cds, neighbor_graph principal_graph, cores 4) # 查看结果 head(gene_fits[order(gene_fits$q_value), ])graph_test的输出里最关键的两列是morans_I和q_value。morans_I是莫兰指数衡量基因表达在轨迹空间里的空间自相关性值越大说明基因表达越规律地沿着轨迹变化q_value是校正后的显著性。实际筛选时一般取q_value 0.05且morans_I 0.25的基因作为候选轨迹相关基因。注意morans_I的阈值没有绝对标准要根据你的数据分布看有些数据集0.15就能筛出很多有意义基因有些则需要0.3以上。先跑一遍把基因按morans_I从高到低排我看一下排序里前20个基因是不是marker大概就能判断该卡多少。筛出来的基因可以画热图直观展示它们在拟时序上的表达趋势# 挑top显著基因 top_genes - gene_fits %% filter(q_value 0.05) %% arrange(desc(morans_I)) %% pull(gene_short_name) %% head(30) # 提取它们的表达数据并绘制热图 plot_genes_in_pseudotime(cds[top_genes, ], color_cells_by cell_type, min_expr 0.5)这个热图能非常清晰地看出基因表达的先后顺序比如EMT研究里EPCAM、CDH1这类上皮基因在拟时序早期高表达随后下降VIM、FN1、ZEB1这类间质基因在后期上升。这种动态趋势本身就是疾病机制的直接证据。4.2 用find_gene_modules聚合基因模块单独看几百个基因很累这时候用find_gene_modules把所有轨迹相关基因聚成模块每个模块代表一组协同变化的基因然后对每个模块做GO/KEGG富集。# 提取所有显著基因构建子集 pr_graph_test_res - gene_fits pr_genes - row.names(subset(pr_graph_test_res, q_value 0.05)) # 构建基因模块 gene_module_df - find_gene_modules(cds[pr_genes, ], resolution 0.001) # 按模块画表达热图 plot_cells(cds, genes gene_module_df, label_cell_groups FALSE, show_trajectory_graph FALSE)find_gene_modules内部也是用Leiden聚类把基因分组resolution控制分组的细致程度。模块分完之后每个模块对应一个“表达随时间变化的基因程序”。你可以把模块导出用clusterProfiler做富集分析看哪些通路在轨迹早期激活哪些在晚期激活。这个结果对解释疾病机制非常有力比如EMT过程中模块A富集到“细胞黏附”和“上皮发育”拟时序早期活跃模块B富集到“细胞迁移”“细胞外基质组织”晚期活跃这就构成了一个完整的“上皮特征丢失-间质特征获得”的分子叙事。4.3 结合细胞类型和疾病分组解读轨迹单纯看基因还不够疾病研究里更常问的问题是正常细胞和疾病细胞在轨迹上的分布有没有差异比如EMT轨迹里正常组织来源的上皮细胞是不是集中在早期拟时序段而肿瘤组织来源的细胞是不是分布在后段这可以直接用拟时序值做分组比较# 提取拟时序值 pseudo_df - data.frame( pseudotime pseudotime(cds), cell_type cdscolData$cell_type, group cdscolData$sample_group ) # 用ggplot2画分组密度分布 ggplot(pseudo_df, aes(x pseudotime, fill group)) geom_density(alpha 0.5) theme_classic()如果两组细胞的拟时序分布有明显偏移说明疾病状态下更多细胞处于轨迹的“晚期”状态也就是疾病推动了细胞沿这条转变路径前进。这类图在实际文章里非常常见审稿人也容易接受。还有一个常用技巧是计算每个样本的“平均拟时序”mean pseudotime然后和临床指标做相关分析。比如在肿瘤里平均拟时序越高的样本生存期越短这就能把单细胞轨迹和临床预后关联起来研究档次一下就上去了。当然做这种分析时要注意样本量单细胞数据来自几个病人的话谨慎下结论最好用bulk RNA-seq的大队列做验证。5. 常见问题与排查技巧实录5.1 我在实际项目中踩过的五个坑第一个坑UMAP坐标错位导致轨迹断裂。我刚开始用as.cell_data_set转Seurat对象时没有重新赋值UMAP坐标结果learn_graph画出来的轨迹乱成一团有些细胞被孤立在主图外。排查了半天发现是reducedDims(cds)里的坐标是空的或者和meta不对应。解决办法就是我前面写的转换后必须手动reducedDims(cds) - list(UMAP seureductions$umapcell.embeddings)然后把clusters也一并赋好。第二个坑分区太多导致轨迹分散。有一个免疫细胞数据集CD4和CD8 T细胞明明在UMAP上接近但cluster_cells把它们分到了两个partitionlearn_graph结果就成了两条互不相干的轨迹。这时候如果想看它们共同的活化轨迹可以在learn_graph里设置use_partition FALSE或者在cluster_cells之前把resolution调大让它们别被拆开。第三个坑根节点选错整个方向反转。我在一个纤维化例子里一开始想当然选了成纤维细胞作为起点结果拟时序把炎症细胞放在“后期”和已知病理过程完全相反。后来改回损伤初始状态的上皮细胞作为根轨迹才合理。记住根节点决定一切不是随便点一个位置就算完。第四个坑graph_test跑起来特别慢。几十万细胞的数据集graph_test默认会比较耗内存。解决办法一个是设置cores参数做并行另一个是提前过滤低表达基因比如在至少10个细胞里表达量大于0.1把输入基因数降下来。第五个坑monocle3版本的函数名和教程对不上。网上很多教程是旧版monocle3的写法比如partition_cells这个函数现在已经不推荐直接用了。遇到函数找不到或者参数报错先看packageVersion(monocle3)再查对应版本的官方文档不要死磕旧代码。5.2 如何验证轨迹的可靠性我觉得这是整个分析里最容易被忽视、但其实最重要的问题。轨迹分析本质是计算推断你用UMAP结构推出来的“拟时序”不代表真实时间轴必须要做验证。第一个验证手段是RNA velocity。用velocyto或者scVelo估算每个细胞的转录本剪接动态得到“细胞正在向哪个方向转变”的速度场然后把速度场和monocle3的轨迹叠加看是否一致。如果RNA velocity箭头方向和你轨迹方向吻合结论就靠谱很多。虽然scVelo要在Python环境里跑但它的结果可以导出成csv再回到R里可视化这个跨工具流程不复杂强烈推荐做。第二个验证手段是转录因子活性或者蛋白水平的验证。比如轨迹推断T细胞正在从naive向exhausted转变那就应该看到TOX、PDCD1等关键转录因子和蛋白的表达量变化如果能结合流式或免疫组化的时间序列数据说服力更强。第三个更朴素的验证是marker基因分布的“排列感”。在UMAP图上如果一个轨迹真的有生物学意义它的关键marker表达变化应该是渐变的而不是随机斑驳的。我每次学完轨迹第一件事就是挨个把已知marker画一遍确认渐变顺序符合文献报告再做下游分析。5.3 问题速查表现象可能原因解决办法轨迹图断裂细胞散落各处UMAP坐标未正确赋值从Seurat重新赋值reducedDims检查细胞名是否一一对应learn_graph后出现大量孤立短枝离群细胞过多或分区过细过滤离群细胞适当调大resolution合并分区轨迹分叉难以解释分区切分不当或者数据里真的存在多谱系检查每组branch对应的marker考虑分开分析根节点找不到想要的细胞簇聚类粒度不匹配手动指定root_cells来代替点击root_nodesgraph_test速度极慢基因数太多过滤低表达基因设置cores并行两次运行轨迹结果不同UMAP/Leiden的随机性set.seed固定随机种子记录版本号转换后细胞类型丢失colData未正确继承手动把meta.data里的列赋值给cdscolData6. 工具选型解析何时必须用monocle36.1 几个主流轨迹工具的横向对比工具原理优势局限适合场景monocle3主图学习principal graph可视化好、内置基因模块、支持复杂分叉运行较慢UMAP随机性疾病分期、分化轨迹、EMT等连续渐变monocle2DDRTree反向图嵌入对简单线性轨迹稳定、经典处理不了复杂分叉数据量大很慢早期数据、简单分化轨迹的复现Slingshot利用聚类结果构建最小生成树速度快、接口灵活、能接入Seurat轨迹形状依赖聚类质量已有良好聚类结果想快速出轨迹scVeloRNA velocity剪接动力学模型真正反映细胞转变方向、不依赖根选择需要Python环境、对数据质量要求高验证拟时序方向、推断短期细胞命运PAGA基于分区和连接的可解释图超大数据集下速度优势明显轨迹不是连续曲线是拓扑图大规模图谱项目如人类细胞图谱看到这个表格你可能发现了工具之间不是互斥关系而是互补关系。在我实际项目里monocle3往往是主力工具负责产出轨迹曲线和拟时序排序scVelo做方向验证Slingshot偶尔用来做敏感性分析——如果换个算法还能得出类似的轨迹结构那就说明结论是稳健的。6.2 我的选择逻辑如果是以下场景我优先用monocle3你已经有画好的UMAP和细胞注释想快速产出轨迹图。你的研究问题是连续的细胞状态转变比如EMT、巨噬细胞极化、T细胞耗竭、纤维化进展。你希望一套代码搞定轨迹、差异基因、基因模块、富集分析。你的项目里需要跨样本比较拟时序分布比如疾病组和对照组的细胞在轨迹上位置不同。如果是以下场景我会考虑换工具或者做补充你关注的是细胞“真实分化方向”而不是抽象拟时序——这时候RNA velocity是必须的。你的数据形状非常复杂比如多个发育分支同时存在PAGA的拓扑结构可能比连续主图更好展示。你只想快速验证一个分化方向Slingshot可能半小时就能出结果。7. 从分析到文章结果呈现和叙事技巧7.1 主图的呈现方式轨迹分析最终要变成文章里的图。我用monocle3出图时一般会保存两种风格第一种是UMAP叠加主图用plot_cells(cds, color_cells_by cell_type, label_groups_by_cluster TRUE)这是所有论文里最常见的形式用来展示不同细胞类型在轨迹上的分布。第二种是UMAP叠加伪时序的连续色标图用plot_cells(cds, color_cells_by pseudotime)。我通常会把颜色调成从蓝到红渐变并在图注里写清楚“蓝色代表轨迹早期红色代表轨迹晚期”。第三种是对关键基因的“双联图”——左边是UMAP上该基因的表达量右边是拟时序热图。这种方式能在同一张图里同时呈现空间位置和时间动态很有说服力。7.2 与疾病分组的关联呈现文章里如果要把疾病和轨迹关联起来我推荐一个“三件套”第一张图画两种状态下细胞的拟时序密度分布第二张图画每个样本的平均拟时序分组做boxplot第三张图把平均拟时序和临床指标做相关散点图。这套组合拳能让你从“轨迹存在”进阶到“轨迹变化与疾病相关”逻辑链完整且容易复现。当然做疾病关联时样本分组设计一定要提前规划好。如果你想比较“正常 vs 疾病”的拟时序差异最少要有3个生物学重复单细胞数据尤其要注意批次效应。如果不同组样本是分批测的建议在Seurat整合时用Harmony处理再转到monocle3做轨迹否则轨迹本身可能反映的是批次差异而不是疾病差异这是一个非常严重的潜在假阳性来源。8. 最后再分享一个实用小技巧流程走多了以后我有一个习惯每次跑轨迹分析都会把sessionInfo()连同核心参数存成一个txt放在项目文件夹里。这不是形式主义——monocle3的版本迭代确实会带来结果变化两年后如果你要复现这篇文章的分析没记录版本和参数几乎是灾难。另外一个小技巧是关于结果保存的。learn_graph和order_cells的结果都在cds对象里但我强烈建议单独保存拟时序表、模块表和graph_test结果表而不是只保存整个cds。因为cds文件非常大几个G下游做差异分析、画图每次加载都浪费时间。表格才是你后边真正用得多的东西。单细胞轨迹分析这条路线说难是真不难——装好包、跑通流程只需要一天但说容易也不容易——真正让轨迹分析有生物学价值、能被审稿人认可靠的是对数据细节的把控、对根节点选择的理解、以及交叉验证的习惯。希望这篇实操笔记能帮你少走一些弯路。