384_单细胞核RNA测序snRNA-seq
一句话说明
snRNA-seq(单细胞核测序)是"从细胞核里提取RNA来分析",解决了新鲜组织难以获取的问题,特别适合冷冻保存的样本和难以消化的组织(如脑、肌肉)。
核心知识点
要点1:snRNA-seq vs scRNA-seq 的核心差异
| 特征 | scRNA-seq | snRNA-seq |
|---|
| RNA来源 | 整个细胞 | 仅细胞核 |
| 样本要求 | 新鲜、可消化 | 冷冻样本也可以 |
| 适合组织 | 血液、软组织 | 脑、心脏、肌肉、脂肪 |
| 未剪接比例 | 低(~5-15%) | 高(~20-40%,前mRNA多) |
| 细胞完整性要求 | 高 | 低(细胞核更稳定) |
要点2:snRNA-seq 特有的技术挑战
- 环境RNA污染:细胞裂解时释放的胞质RNA会污染细胞核制备物(用 SoupX 去除)
- 未剪接reads比例高:pre-mRNA多,需要宽松的比对参数(--include-introns)
- 细胞类型低估:一些分泌细胞(如分泌大量mRNA的浆细胞)在细胞核层面特征不明显
要点3:数据预处理关键参数
- CellRanger/STARsolo 需要加
--include-introns 参数(统计内含子reads) - 否则会丢失大量核内pre-mRNA,导致每个核的reads数量很少
- 质控阈值:基因数通常比scRNA-seq低(每核500-3000基因,而非1000-5000)
要点4:SoupX 去除环境RNA
- "汤X"的名字来自"RNA soup":裂解细胞时释放到液体中的游离RNA
- 估计"汤"的组成(来自空液滴droplets),然后从每个细胞中减去
- 尤其重要的组织:脑(神经元大量释放特定mRNA)
要点5:双细胞检测的特殊考量
- snRNA-seq中"双核"(两个核被一起捕获)比scRNA-seq双细胞更常见
- 检测工具同样适用:Scrublet、DoubletFinder
- 核制备质量差时双核比例可达20%+
实战代码
STARsolo/CellRanger 内含子统计
# CellRanger 统计内含子reads(snRNA-seq必须开启)
cellranger count \
--id sample_nuclear \
--fastqs /path/to/fastq/ \
--sample sample_name \
--transcriptome /path/to/refdata-gex-GRCh38-2024-A \
--include-introns true \ # 【关键】包含内含子reads(默认false,scRNA-seq才是false)
--localcores 16 \
--localmem 64
# STARsolo 等价参数
STARsolo \
--soloType CB_UMI_Simple \
--soloCBwhitelist 3M-february-2018.txt \
--soloFeatures Gene GeneFull \ # GeneFull = 包含内含子
--readFilesIn R2.fastq.gz R1.fastq.gz \
--runThreadN 16
# 查看每个核的统计数据(过滤标准与scRNA-seq不同)
echo "snRNA-seq 推荐质控阈值:"
echo " 每核基因数:200-5000(scRNA-seq是200-6000)"
echo " 线粒体基因比例:<5%(核内线粒体基因少)"
echo " 双核比例:<15%"
SoupX 去除环境RNA(R)
library(SoupX) # 环境RNA去除
library(Seurat)
# ============================================================
# 方法A:从 CellRanger 输出自动运行(推荐)
# ============================================================
# CellRanger 输出目录结构:
# outs/
# filtered_feature_bc_matrix/ <- 过滤后的矩阵
# raw_feature_bc_matrix/ <- 原始矩阵(包含空液滴)
# molecule_info.h5
sc <- load10X("outs/") # 自动加载 filtered + raw 两个矩阵
# SoupX 估计"汤"的组成
sc <- autoEstCont(sc) # 自动估计环境RNA污染比例
cat("估计污染比例:", sc$soupProfile[1], "\n") # 通常5-20%
# 去除环境RNA(矫正count矩阵)
out_matrix <- adjustCounts(
sc,
roundToInt = TRUE # 输出整数(DESeq2等需要整数counts)
)
# 创建去污染后的 Seurat 对象
seurat_clean <- CreateSeuratObject(
counts = out_matrix,
project = "snRNA_clean",
min.cells = 3,
min.features = 200
)
# ============================================================
# 方法B:手动指定marker基因辅助估计(当自动估计不准时)
# ============================================================
# 例如,MALAT1 是核RNA的标志(高表达=真核,对外部RNA也高=污染严重)
sc <- setSoupProfile(sc, "MALAT1") # 用MALAT1辅助校正
sc <- autoEstCont(sc, priorRho = 0.1) # prior=0.1表示预期10%污染
snRNA-seq 质控与分析(Python)
import scanpy as sc
import scrublet as scr # 双核检测(pip install scrublet)
import numpy as np
import matplotlib.pyplot as plt
# 读取 CellRanger 输出(GeneFull矩阵,包含内含子)
adata = sc.read_10x_mtx(
"outs/filtered_feature_bc_matrix/",
var_names="gene_symbols",
cache=True
)
adata.var_names_make_unique()
# ============================================================
# 质控(snRNA-seq有特殊阈值)
# ============================================================
# 计算线粒体比例(核内线粒体DNA转录的mRNA很少)
adata.var["mt"] = adata.var_names.str.startswith("MT-")
sc.pp.calculate_qc_metrics(adata, qc_vars=["mt"], inplace=True)
# snRNA-seq 特殊质控:标记核糖体基因(核内rRNA很少)
adata.var["ribo"] = adata.var_names.str.startswith(("RPS", "RPL"))
sc.pp.calculate_qc_metrics(adata, qc_vars=["ribo"], inplace=True)
# 核的质量可视化
sc.pl.violin(adata,
["n_genes_by_counts", "total_counts", "pct_counts_mt"],
jitter=0.4,
multi_panel=True,
save="_snRNA_qc.pdf"
)
# 过滤(snRNA-seq阈值宽松一些)
sc.pp.filter_cells(adata, min_genes=200) # 最少200个基因
sc.pp.filter_genes(adata, min_cells=3) # 最少3个核表达
adata = adata[
(adata.obs.pct_counts_mt < 5) & # 线粒体比例<5%(核内更低)
(adata.obs.n_genes_by_counts < 5000) # 上限宽松些(核RNA稍少)
].copy()
print(f"过滤后:{adata.n_obs} 个核,{adata.n_vars} 个基因")
# ============================================================
# 双核检测(Scrublet)
# ============================================================
scrub = scr.Scrublet(adata.X, expected_doublet_rate=0.1) # snRNA-seq双核率更高
doublet_scores, predicted_doublets = scrub.scrub_doublets(
min_counts=2,
min_cells=3,
min_gene_variability_pctl=85
)
adata.obs["doublet_score"] = doublet_scores # 双核得分
adata.obs["predicted_doublet"] = predicted_doublets # 是否双核
# 过滤双核
n_before = adata.n_obs
adata = adata[~adata.obs.predicted_doublet].copy()
print(f"去除双核后:{adata.n_obs} 个核(去掉 {n_before - adata.n_obs} 个双核)")
# 标准化和降维
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=3000, flavor="seurat_v3")
sc.pp.scale(adata, max_value=10)
sc.tl.pca(adata, svd_solver="arpack")
sc.pp.neighbors(adata, n_pcs=30)
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)
sc.pl.umap(adata, color="leiden", save="_snRNA_clusters.pdf")
面试常问点
- 为什么脑组织常用snRNA-seq而不是scRNA-seq? 神经元很难消化(突触、轴突结构复杂),且冷冻脑样本很多,snRNA-seq对冷冻样本友好
- snRNA-seq线粒体比例阈值为什么要更低? 线粒体RNA主要在细胞质,核内很少,高线粒体比例说明细胞质RNA严重污染了核制备物
- SoupX是做什么的? 去除细胞裂解时释放到溶液中的"游离RNA"对细胞核检测结果的污染
速查表
| 参数 | scRNA-seq | snRNA-seq |
|---|
| CellRanger | 默认 | --include-introns true |
| 最少基因数 | 200 | 200 |
| 最多基因数 | 6000 | 5000 |
| 线粒体上限 | 20% | 5% |
| 双核率 | 5-10% | 10-20% |
| 环境RNA去除 | 可选 | 强烈推荐(SoupX) |