AnnData 是 Scanpy 的核心数据结构,相当于单细胞分析里的"万能容器"。很多初学者只会基础操作,但掌握这些进阶技巧能让你的分析效率提升一个数量级。
AnnData 结构回顾
csharp
AnnData (n_obs × n_vars)
├── .X → 主数据矩阵(细胞 × 基因)
├── .obs → 细胞元数据(DataFrame)
├── .var → 基因元数据(DataFrame)
├── .obsm → 细胞多维嵌入(PCA, UMAP...)
├── .obsp → 细胞间距离/图(kNN graph...)
├── .varm → 基因多维嵌入
├── .uns → 非结构化数据(颜色、参数等)
└── .layers → 多个数据矩阵(raw, normalized...)
技巧1:用 layers 保存多个数据状态
ini
import scanpy as sc
import numpy as np
adata = sc.read_10x_h5("sample.h5")
# 保存原始 counts
adata.layers["counts"] = adata.X.copy()
# 标准化
sc.pp.normalize_total(adata, target_sum=1e4)
adata.layers["normalized"] = adata.X.copy()
# 对数变换
sc.pp.log1p(adata)
adata.layers["log1p"] = adata.X.copy()
# 随时切换
adata.X = adata.layers["counts"] # 切回原始数据
adata.X = adata.layers["log1p"] # 切回对数变换数据
技巧2:批量操作 obs 元数据
csharp
# 添加多个元数据列
import pandas as pd
# 方法一:直接赋值
adata.obs["sample_id"] = "sample_01"
adata.obs["condition"] = "control"
# 方法二:从外部文件合并
meta_df = pd.read_csv("sample_metadata.csv", index_col=0)
adata.obs = adata.obs.join(meta_df, how="left")
# 方法三:基于现有列计算新列
adata.obs["high_mt"] = adata.obs["pct_counts_mt"] > 20
adata.obs["cell_quality"] = np.where(
adata.obs["high_mt"], "low", "high"
)
技巧3:高效切片和子集
ini
# 布尔索引
adata_filtered = adata[adata.obs["n_genes_by_counts"] > 500].copy()
# 按类别筛选
adata_cd4 = adata[adata.obs["cell_type"] == "CD4+ T cell"].copy()
# 多条件组合
mask = (
(adata.obs["total_counts"] > 1000) &
(adata.obs["pct_counts_mt"] < 20) &
(adata.obs["n_genes_by_counts"] > 500)
)
adata_clean = adata[mask].copy()
# 按基因筛选
hvg_genes = adata.var_names[adata.var["highly_variable"]]
adata_hvg = adata[:, hvg_genes].copy()
技巧4:合并多个 AnnData 对象
python
import anndata as ad
# 拼接多个样本
adatas = []
for sample_id in ["s1", "s2", "s3"]:
tmp = sc.read_10x_h5(f"{sample_id}/filtered_feature_bc_matrix.h5")
tmp.obs["sample_id"] = sample_id
# 确保 cell barcode 唯一
tmp.obs_names = [f"{sample_id}_{bc}" for bc in tmp.obs_names]
adatas.append(tmp)
adata_merged = ad.concat(adatas, join="outer", fill_value=0)
print(f"合并后: {adata_merged.shape}")
技巧5:读写优化
ini
# 保存为 h5ad(推荐格式,读写最快)
adata.write_h5ad("analysis.h5ad", compression="gzip")
# 只读取部分数据(大文件神器)
adata_partial = sc.read_h5ad("analysis.h5ad", backed="r")
# backed模式下,.X 是内存映射,不会全部加载进内存
# 读取特定列的 obs
import h5py
with h5py.File("analysis.h5ad", "r") as f:
cell_types = f["obs/cell_type"][:]
技巧6:obsm 存储降维结果
scss
# 标准嵌入键名
print(adata.obsm.keys())
# odict_keys(['X_pca', 'X_umap', 'X_tsne', 'X_harmony'])
# 手动添加自定义嵌入
from sklearn.decomposition import NMF
nmf = NMF(n_components=20)
adata.obsm["X_nmf"] = nmf.fit_transform(adata.X.toarray())
# 使用自定义嵌入做 UMAP
sc.pp.neighbors(adata, use_rep="X_nmf")
sc.tl.umap(adata)
技巧7:uns 存储分析参数
bash
# uns 用来记录分析过程中的重要参数
adata.uns["analysis_params"] = {
"qc_min_genes": 500,
"qc_max_mt_pct": 20,
"n_hvg": 3000,
"n_pca": 50,
"n_neighbors": 15,
"leiden_resolution": 0.5
}
# 查看颜色配置
print(adata.uns.get("leiden_colors", "未设置"))
技巧8:快速查看数据摘要
python
# 打印 AnnData 摘要
print(adata)
# 详细统计
def adata_summary(adata):
print(f"细胞数: {adata.n_obs:,}")
print(f"基因数: {adata.n_vars:,}")
print(f"obs 列: {list(adata.obs.columns)}")
print(f"obsm 键: {list(adata.obsm.keys())}")
print(f"layers: {list(adata.layers.keys())}")
if adata.X is not None:
import scipy.sparse as sp
if sp.issparse(adata.X):
print(f"稀疏度: {1 - adata.X.nnz / (adata.n_obs * adata.n_vars):.2%}")
adata_summary(adata)
技巧9:复制 vs 视图
ini
# 切片默认返回视图,修改可能影响原数据
view = adata[adata.obs["condition"] == "control"]
print(view.is_view) # True
# 用 .copy() 获取独立副本
copy = adata[adata.obs["condition"] == "control"].copy()
print(copy.is_view) # False
# 规则:需要修改数据时,先 .copy()
技巧10:与 pandas/numpy 无缝互操作
ini
# obs 就是 pandas DataFrame
df_obs = adata.obs # 直接就是 DataFrame
grouped = df_obs.groupby("cell_type")["total_counts"].mean()
# var 也是 DataFrame
high_expr_genes = adata.var[adata.var["mean_counts"] > 1].index.tolist()
# .X 转 numpy(小数据集可以这样)
matrix_np = adata.X.toarray() # 稀疏→密集
# 反向操作
import scipy.sparse as sp
adata.X = sp.csr_matrix(matrix_np) # 密集→稀疏
掌握这 10 个技巧,AnnData 操作效率能提升至少 3 倍。