ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单细胞转录组数据查找指南:从质控到跨数据集检索的代码包拆解

单细胞转录组数据查找指南:从质控到跨数据集检索的代码包拆解 简介这份单细胞转录组数据查找指南配套项目代码面向刚接触单细胞分析的生信初学者与需要快速定位公共数据的研究人员帮助解决数据来源分散、检索效率低、下载易出错等问题。资源包共3个文件以inscode项目配置、html页面和gitignore为主整体约9KB结构轻量便于直接打开查阅或嵌入现有分析流程。内容围绕GEO、Single Cell Portal、Human Cell Atlas等常用数据库展开重点演示如何用布尔逻辑运算符构建精确查询、利用GEO分类功能细化结果并说明数据格式识别、质量检查、版本选择及许可协议与引用规则等下载注意事项。已有131人学习适合希望系统掌握单细胞数据检索与获取思路、为后续细胞分化、肿瘤异质性或发育生物学分析打基础的读者参考。1. 单细胞转录组数据查找指南从一份代码包说起做单细胞分析的人大概都有过这种体验手头一堆 fastq 或矩阵文件却不知道从哪一步开始查、用什么工具查、查完怎么对齐到参考注释。这份「单细胞转录组数据查找指南」代码包解决的就是这个从原始数据到可解释结果的检索链路问题。它不是某个具体分析流程的教程而是一套围绕「查找」这个动作组织起来的代码资源——帮你定位细胞类型、查找标记基因、检索公共数据集里的可比样本。适合已经跑过 CellRanger 或 STARsolo、手里有表达矩阵但卡在注释和检索环节的从业者。如果你还在纠结 Seurat 和 Scanpy 选哪个这份资源不解决那个问题但如果你已经有了矩阵想知道怎么系统地「查」出生物学意义它值得拆开看。2. 数据查找的底层逻辑为什么不能直接上聚类2.1 查找的前提是矩阵已经过质控和标准化很多人拿到表达矩阵第一件事就是跑聚类然后对着 t-SNE 图发呆——这堆颜色不同的点到底代表什么问题出在跳过了一个关键认知单细胞数据的「查找」不是从聚类开始的是从质控和标准化之后才真正有意义。原始计数矩阵里混着低质量细胞、双细胞、线粒体基因高表达细胞这些噪声不清理后面查出来的标记基因全是假阳性。常见做法是先用 MAD 或百分位数法过滤掉 nFeature_RNA 过高或过低的细胞再根据线粒体基因比例卡一道阈值。这一步没有统一标准组织类型不同阈值差异很大。我一般会先把 nFeature_RNA 的分布画出来看拐点在哪而不是死记「200 到 2500」这种通用区间。标准化则优先选 SCTransform 或 logNorm前者对测序深度的校正更稳后者兼容性更好。import scanpy as sc # 读取 10x 格式矩阵 adata sc.read_10x_mtx( filtered_feature_bc_matrix/, var_namesgene_symbols, cacheTrue ) # 基础质控指标计算 adata.var[mt] adata.var_names.str.startswith(MT-) sc.pp.calculate_qc_metrics( adata, qc_vars[mt], percent_topNone, log1pFalse, inplaceTrue ) # 按分位数过滤比固定阈值更适应不同组织 sc.pp.filter_cells(adata, min_genes200) sc.pp.filter_genes(adata, min_cells3) adata adata[adata.obs[pct_counts_mt] 20, :].copy()这段代码的逻辑是先算质控指标再过滤顺序不能反。min_genes200是下限保护防止空液滴被当成细胞pct_counts_mt 20对大多数组织够用但心肌或肌肉样本可能要放宽到 30 甚至 40。参数改动的依据永远是你自己的数据分布不是别人的经验值。2.2 查找动作分三层细胞类型、标记基因、公共数据质控完的矩阵进入查找阶段实际上有三个不同层面的「查」在同时发生。第一层是查细胞类型——这堆细胞属于什么类别靠的是聚类加注释。第二层是查标记基因——每个簇里哪些基因显著高表达用来支撑注释结论。第三层是查公共数据——你的样本和已发表数据集里的哪些细胞类型可以对齐用来验证或补充。这三层不是串行关系而是互相印证。聚类结果告诉你「这里有五个簇」标记基因告诉你「簇 2 高表达 CD3D 和 CD3E」公共数据检索告诉你「这个表达模式和记忆 T 细胞一致」。缺了任何一层结论都站不住。代码包里把这三层拆成了独立模块可以单独调用也可以串起来跑。# 三层查找的串联示例 # 第一层聚类 sc.pp.normalize_total(adata, target_sum1e4) sc.pp.log1p(adata) sc.pp.highly_variable_genes(adata, n_top_genes2000) sc.pp.pca(adata, n_comps50) sc.pp.neighbors(adata, n_neighbors15, n_pcs30) sc.tl.leiden(adata, resolution0.8) # 第二层标记基因查找 sc.tl.rank_genes_groups(adata, leiden, methodwilcoxon) marker_df sc.get.rank_genes_groups_df(adata, group0) # 第三层公共数据比对以 CellTypist 为例 # 需提前安装 celltypist 并下载模型 import celltypist predictions celltypist.annotate(adata, modelImmune_All_Low.pkl) adata.obs[celltypist_label] predictions.predicted_labelsresolution0.8是 Leiden 聚类的分辨率参数值越大簇越多。0.8 是个折中起点实际跑的时候我会从 0.4 到 1.2 各跑一遍看哪个分辨率下簇的标记基因最干净。n_neighbors15和n_pcs30也是经验起点数据量大的时候邻居数可以提到 20 到 30。CellTypist 的模型选择取决于你的组织类型免疫细胞用 Immune_All_Low其他组织要去官网查对应模型文件。3. 代码包拆解查找模块怎么调、参数怎么设3.1 目录结构与模块职责代码包解压后通常能看到几个核心目录scripts/放可执行脚本configs/放参数配置文件utils/放公共函数data/放示例数据或下载脚本。这种结构的好处是查找逻辑和参数分离换数据集的时候只改 config 不动代码。我一般先看configs/里的 yaml 或 json 文件那里定义了所有可调参数质控阈值、聚类分辨率、标记基因筛选的 logFC 和 p 值 cutoff、公共数据检索的模型路径。把这些参数集中管理比散落在各个脚本里强得多。utils/里的函数通常是数据加载、格式转换、结果导出这些重复动作读一遍能省很多重复造轮子的时间。# 典型的代码包目录结构 single_cell_finder/ ├── scripts/ │ ├── 01_qc_filter.py │ ├── 02_cluster_annotate.py │ └── 03_marker_search.py ├── configs/ │ └── default_params.yaml ├── utils/ │ ├── io_utils.py │ └── plot_utils.py └── data/ └── download_example.sh01_qc_filter.py负责质控和过滤02_cluster_annotate.py负责聚类和自动注释03_marker_search.py负责标记基因查找和导出。三个脚本可以独立跑也可以按顺序串。default_params.yaml里通常有注释说明每个参数的推荐范围和调整场景这是最该先读的文件。3.2 参数配置与运行方式参数配置的核心原则是先跑默认值看结果再针对性调。不要一上来就改一堆参数那样出了问题根本不知道是哪个参数导致的。默认参数一般是在通用数据集上验证过的对大多数场景够用。# default_params.yaml 示例 qc: min_genes: 200 max_genes: 6000 max_mt_pct: 20 min_cells: 3 cluster: n_top_genes: 2000 n_pcs: 30 n_neighbors: 15 resolution: 0.8 marker: method: wilcoxon logfc_threshold: 0.25 pval_cutoff: 0.05 min_pct: 0.1 annotation: model: Immune_All_Low.pkl majority_voting: truemax_genes: 6000是上限保护防止双细胞被保留。logfc_threshold: 0.25是标记基因筛选的 log 倍数变化阈值低于这个值的基因即使 p 值显著也可能没有生物学意义。min_pct: 0.1要求基因至少在 10% 的细胞里表达过滤掉那些只在极少数细胞里出现的噪声。majority_voting: true是 CellTypist 的一个选项让注释结果在聚类簇层面做多数投票减少单细胞层面的抖动。运行方式通常是命令行传参覆盖默认值python scripts/02_cluster_annotate.py \ --input data/processed.h5ad \ --config configs/default_params.yaml \ --resolution 1.0 \ --output results/clustered.h5ad--resolution 1.0覆盖了配置文件里的 0.8这种命令行覆盖的优先级高于配置文件。跑完之后检查results/目录下的输出文件通常包括聚类后的 h5ad、标记基因表格、注释结果表格和几张质控图。3.3 查找结果的验证与导出跑完查找流程不等于结束验证环节才是决定结论能不能用的关键。我一般会做三件事第一把标记基因的 top 10 画成热图看每个簇的基因表达模式是否干净第二把自动注释结果和手动注释结果做交叉表看一致性第三把关键标记基因的表达量画在 UMAP 上确认空间分布合理。# 验证查找结果的三步操作 import pandas as pd # 第一步标记基因热图 sc.pl.rank_genes_groups_heatmap( adata, n_genes10, groupbyleiden, show_gene_labelsTrue, save_marker_heatmap.pdf ) # 第二步注释一致性交叉表 cross_tab pd.crosstab( adata.obs[leiden], adata.obs[celltypist_label] ) print(cross_tab) # 第三步关键基因 UMAP sc.pl.umap( adata, color[CD3D, CD19, CD14, celltypist_label], ncols2, save_key_markers.pdf )热图看的是簇内一致性如果某个簇的 top 基因热图很花说明这个簇可能没分干净。交叉表看的是自动注释和聚类编号的对应关系如果某个簇的注释结果特别分散说明这个簇的细胞类型不纯。UMAP 看的是空间分布CD3D 应该富集在 T 细胞区域CD19 在 B 细胞区域如果这些基因到处都有表达说明数据质量有问题。4. 避坑与排查查找流程里最容易翻车的五个地方4.1 现象聚类簇数远多于预期标记基因全是核糖体基因原因通常是质控没做干净低质量细胞和双细胞混在里面形成了假簇。核糖体基因高表达是低质量细胞的典型特征它们聚在一起不是因为生物学相似而是因为都快死了。解决方法是回头检查质控步骤把max_genes调低、max_mt_pct调严重新跑一遍。如果核糖体基因仍然占主导可以在高变基因选择时排除RPS和RPL开头的基因。4.2 现象自动注释结果和已知生物学完全对不上原因可能是模型选错了。CellTypist 的 Immune_All_Low 模型只适用于免疫细胞如果你拿它注释上皮细胞或神经元结果必然离谱。另一个可能是输入数据的基因命名和模型训练时不一致比如模型用 Ensembl ID 而你用 gene symbol。解决方法是先确认组织类型去模型列表里找对应的模型文件。基因命名不一致的话用celltypist自带的转换函数或者手动做 ID 映射。跑之前先用adata.var_names[:5]看一眼基因名格式。4.3 现象标记基因查找结果里 logFC 很大但 p 值不显著原因通常是该基因只在极少数细胞里表达虽然表达量差异大但统计检验的样本量不够。min_pct参数没卡住这类基因。解决方法是在标记基因筛选时同时卡logfc_threshold和min_pct两个条件都满足才保留。如果某个簇的细胞数本来就少可以适当放宽min_pct但要在结果里标注出来。4.4 现象公共数据检索时找不到可比数据集原因可能是检索关键词太窄或者你的数据本身是罕见组织类型。公共数据库里的单细胞数据集覆盖度有限不是所有组织都有现成的可比样本。解决方法是先用标记基因去查而不是用组织名去查。比如你查不到「胰腺导管上皮」的数据集但可以用「EPCAM 高表达」这个特征去检索。另外可以放宽物种限制小鼠和人的同源基因比对也能提供参考。4.5 现象整个流程跑完但 h5ad 文件打不开原因通常是写入时用了不兼容的格式或者磁盘空间不足导致文件截断。h5ad 对写入完整性要求很高中途中断就会损坏。解决方法是每次写入后立刻用sc.read_h5ad读一遍验证确认文件完整再删中间文件。磁盘空间至少留出数据量三倍的余量因为写入过程中会有临时文件。5. 进阶技巧用标记基因做跨数据集检索查找流程跑通之后最有价值的进阶用法是把标记基因列表当成「检索指纹」去公共数据里找相似样本。具体做法是从你的数据里导出每个簇的 top 50 标记基因然后用这些基因去比对公共数据集的注释文件或表达矩阵算 Jaccard 相似度或超几何检验 p 值。# 用标记基因做跨数据集检索 from scipy.stats import hypergeom import numpy as np def marker_similarity(query_markers, ref_markers): query_markers: 你的数据里某个簇的标记基因列表 ref_markers: 公共数据集里某个细胞类型的标记基因列表 返回超几何检验的 p 值和 Jaccard 指数 query_set set(query_markers) ref_set set(ref_markers) overlap query_set ref_set union query_set | ref_set # 超几何检验 M 20000 # 人类基因总数近似值 n len(ref_set) N len(query_set) k len(overlap) pval hypergeom.sf(k - 1, M, n, N) jaccard len(overlap) / len(union) if union else 0 return pval, jaccard # 示例比对两个标记基因列表 query [CD3D, CD3E, CD2, IL7R, CCR7] ref [CD3D, CD3E, CD2, CD28, ICOS, CTLA4] pval, jac marker_similarity(query, ref) print(fp-value: {pval:.2e}, Jaccard: {jac:.3f})M20000是人类基因总数的近似值做超几何检验时作为背景。hypergeom.sf算的是「至少重叠 k 个基因」的概率p 值越小说明两个标记基因列表越相似。Jaccard 指数是重叠数除以并集数值越接近 1 越相似。这两个指标要一起看p 值显著但 Jaccard 很低的情况说明重叠基因虽少但统计学上不偶然可能是功能相关的核心基因。我一般会把所有簇的标记基因和公共数据集里所有细胞类型的标记基因做两两比对生成一个相似度矩阵然后看每个簇最匹配的公共细胞类型是什么。这个矩阵还能用来发现数据集之间的批次效应——如果两个数据集里同一种细胞类型的标记基因相似度很低说明批次效应严重需要做整合。从那以后我每次跑完查找流程都会强制走一遍标记基因相似度矩阵确认没有哪个簇是「孤儿簇」——和任何公共细胞类型都对不上。这种簇要么是新发现的稀有细胞类型要么是质控没做干净留下的假簇两种情况都值得回头查。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进