认识单细胞分析中的各种数据结构

作者:椰子糖
审稿:童蒙
编辑:amethyst

单细胞分析世界里数据结构多种多样,主流的四种数据结构分别是Bioconductor主导的SingleCellExperiment,Seurat中的SeuratObject格式,scanpy中的AnnData格式,以及大型数据存储的loom格式。通常一种数据结构对应的内容可以包含所有的分析,例如seurat就可以一用到底,那么我们只要掌握好其中一种数据结构就基本够用,但也许这样就可能会错过其他比较好用的函数。为了更深入的了解更多好用的函数,就可以来看看各种数据结构的转换。


01 各种数据结构的介绍

单细胞数据中每一个基因可以看做是单细胞的一个特征,因此单细胞数据中每一个细胞就有n个特征,在空间上可以看做是细胞具有的n个特征维度。


1、SingleCellExperiment

单细胞分析中的非常常用的S4对象,里面包罗万象,那么它是如何组织的?存储了什么内容?以下这张图片中就已经整体进行了展示说明。



图中最核心的部分,是蓝色的data部分;另外还有绿色的基因注释信息feature metadata、橙色的细胞注释信息cell metadata。除了这三大件,还会包含一些下游分析结果,比如PCA、tSNE降维结果就会保存在紫色的Dimension Reductions,现在Bioconductor上的70多个单细胞相关的R包都使用了这个对象,是单细胞分析中非常常用的一种数据结构。
SingleCellExperment对象又简称sce,就好比是一辆装了各种集装箱的大货车,每一个集装箱里装着不同的物资,每一个集装箱都有不同的数据接口(有的接口必须是数值型矩阵结构,有的就需要数据框结构),又都铆合在一起共同在货车上前进。也有人把这些对象结构比作是一个流水线,经过不同的步骤,传送带上的对象盒子里就装上了经过不同函数处理的独立又相互关联的数据内容,中间每一步的处理有画图函数和指标来进行质控。



创建一个sce对象只需要一个assays,这个assay里包含一个元素,这个元素的内容是表达量的数据,行代表的是基因,列代表的是细胞或者样本。首先我们来创建一个矩阵,包含10个基因,3个细胞。
counts_matrix <- data.frame(cell_1 = rpois(10, 10), 
                    cell_2 = rpois(10, 10), 
                    cell_3 = rpois(10, 30))
rownames(counts_matrix) <- paste0("gene_", 1:10)

counts_matrix <- as.matrix(counts_matrix) 

有了这个矩阵,就可以用一个list构建SingleCellExperiment对象了。

#BiocManager::install('SingleCellExperiment')
sce <- SingleCellExperiment(assays = list(counts = counts_matrix))

>sce
## class: SingleCellExperiment 
## dim: 10 3 
## metadata(0):
## assays(1): counts
## rownames(10): gene_1 gene_2 ... gene_9 gene_10
## rowData names(0):
## colnames(3): cell_1 cell_2 cell_3
## colData names(0):
## reducedDimNames(0):
## spikeNames(0):
## altExpNames(0):

要获取这个对象中的矩阵,可以使用assay(sce,"count")也可以使用counts(sce),其中第一种方式也可以是其他名称的矩阵,而第二种方式只能获得为counts名称的矩阵。

构建好assays的核心后,可继续进行拓展,例如使用标准函数进行扩展,扩展到归一化的矩阵,例如:

sce <- scran::computeSumFactors(sce)
sce <- scater::normalize(sce)

> sce
## class: SingleCellExperiment 
## dim: 10 3 
## metadata(1): log.exprs.offset
## assays(2): counts logcounts
## rownames(10): gene_1 gene_2 ... gene_9 gene_10
## rowData names(0):
## colnames(3): cell_1 cell_2 cell_3
## colData names(0):
## reducedDimNames(0):
## spikeNames(0):
## altExpNames(0):

这个assays就从原来存储原始矩阵的counts,增加了一个归一化矩阵logcounts。这里对两个矩阵进行比较发现normalization这一步主要是去除细胞间或者样本文库间的差异,是所有的细胞或者样本具有了可比性。目前我们的sce对象中就有了2个元素。如果构建了新的矩阵也可以增加到sce对象中去。

assays(sce)
## List of length 2
## names(2): counts logcounts

除了有assays外,还有列的注释信息colData,列的注释信息通常就是细胞的注释信息,例如细胞名称或者样本名称、批次、作者等等,对应的上图中的橙色部分。首先我们来设置一个细胞批次的注释信息:

cell_metadata <- data.frame(batch = c(1, 1, 2))
rownames(cell_metadata) <- paste0("cell_", 1:3)

接着添加到sce对象中,通过直接构建的方式添加:

sce <- SingleCellExperiment(assays = list(counts = counts_matrix),
                           colData = cell_metadata)

也可以后续添加:

colData(sce) <- DataFrame(cell_metadata)

加入了sce对象后,可以通过colData(sce)来进行获取,或者sce$batch查看。

细胞注释的信息除了手动添加外,还可以通过scater包的calculateQCMetrics()进行计算,得到几十项细胞的质量信息,并添加进去。

sce <- scater::addPerCellQC(sce)
colData(sce)[, 1:5]
## DataFrame with 3 rows and 5 columns
##            batch       sum  detected percent_top_50 percent_top_100
##        <numeric> <integer> <integer>      <numeric>       <numeric>
## cell_1         1        80        10            100             100
## cell_2         1        88        10            100             100
## cell_3         2       309        10            100             100

rowData是行的注释信息,同细胞或者样本一样,基因也有自己的注释信息,它是一个数据框的结构。

 # 一开始rowData(sce)是空的,可以添加
 sce <- scater::addPerFeatureQC(sce)
 rowData(sce)
 
 ## DataFrame with 10 rows and 2 columns
 ##              mean  detected
 ##         <numeric> <numeric>
 ## gene_1    16.0000       100
 ## gene_2    14.3333       100
## gene_3    16.0000       100
## gene_4    18.6667       100
## gene_5    15.3333       100
## gene_6    16.6667       100
## gene_7    17.6667       100
## gene_8    13.0000       100
## gene_9    16.6667       100
## gene_10   14.6667       100

除了以上这些基本的,由于基因的注释信息是数据框的结构,因此可以按照位置或者名字来取子集,sce[c("gene_1","gene_2")]。另外还有reduceDims,用来存储原始矩阵的降维结构,可以通过PCA,tSNE和Umap获得。

SingleCellExperiment对象的兼容性强,可以用在多种单细胞分析的R包中作为输入或者中间文件,方便了对数据的传输和协作。

2、SeuratObject

单细胞数据中的另一常用对象是seurat对象,主要用在seurat包、monocle包等单细胞分析的R包中。其结构与SingleCellExperiment对象非常相似,在R中,这两个对象也能够通过as函数进行转换。seurat对象结构包含以下这些slots。



有人把seurat对象比作是流水线上的具有不同盒子的容器,经过不同的工序,会在不同的盒子里(slots)增加内容,而这部分工序就叫convertor,包括NormallizeData()、FindVariableFeeature()等,而在每一步工序中对数据进行监管,控制的工具就叫Inspector,包括VlnPlot(),pbmc[['RNA']]@data等。



例如,通过pbmc4k的数据构建一个seurat对象,那么在这个对象中包含哪些内容呢?

Assays:assays里有一个元素“RNA”,访问assays对象的内部结构,例如pbmc[['RNA']]。

RNA:是assay,其可以包含多个matrix:

  • Counts:原始的表达量count矩阵;
  • Data:原始数据经过normalized的数据;
  • Scale.data:数据经过scaling后,存放位置;
  • Key:每个assay对象都有一个key,例如‘rna_’;
  • Var.features:普通向量,高表达变异的基因。

Meta.data:是细胞的注释信息的数据框,行是细胞,列是细胞的属性。

Active.assay:当前激活的assay对象。

标准的seurat流程可以这样进行,包括创建对象,标准化,寻找高突变特征,归一化,聚类等:

pbmc.counts <- Read10X(data.dir ="~/Downloads/pbmc3k/filtered_gene_bc_matrices/hg19/")
pbmc <- CreateSeuratObject(counts = pbmc.counts)
pbmc <- NormalizeData(object = pbmc)
pbmc <- FindVariableFeatures(object = pbmc)
pbmc <- ScaleData(object = pbmc)
pbmc <- RunPCA(object = pbmc)
pbmc <- FindNeighbors(object = pbmc)
pbmc <- FindClusters(object = pbmc)
pbmc <- RunTSNE(object = pbmc)
DimPlot(object = pbmc, reduction = "tsne")

seurat对象操作如下:

# Get cell and feature names, and total numbers
colnames(x = pbmc) ##细胞
Cells(object = pbmc) ##同上获取细胞名称
rownames(x = pbmc) ##获取基因名称
ncol(x = pbmc) ##获取细胞数量
nrow(x = pbmc)  ##获取基因数目

可以使用一些函数对行列等进行获取,也可以用Idents获取细胞的分类情况。

# Get cell identity classes
Idents(object = pbmc)
levels(x = pbmc)
table(Idents(pbmc))  ##获取每个细胞类型数目的表格

# Stash cell identity classes
pbmc[["old.ident"]] <- Idents(object = pbmc)
pbmc <- StashIdent(object = pbmc, save.name = "old.ident")
##设置细胞类别
# Set identity classes
Idents(object = pbmc) <- "CD4 T cells"
Idents(object = pbmc, cells = 1:10) <- "CD4 T cells"

# Rename identity classes
pbmc <- RenameIdents(object = pbmc, `CD4 T cells` = "T Helper cells")

另外也可以通过subset来取对象子集,或者通过merge来合并多个seurat对象。

由于seurat包的好用易用,所以了解和学习seurat对象结构也是很有必要的。

3、AnnData

随着单细胞技术的发展,单细胞测序的技术门槛也越来越低,单细胞的数据呈几何倍数的增加,要分析大百万级细胞数量的单细胞样本,给依赖R分析的软件带来了一定的困难(由于R的存储方式的限制)。于是就有了python版的对象AnnData:



可以通过 sc.pl.highest_expr_genes(adata, n_top=20)获取表达量top20的基因。



也可以通过对象处理对数据进行过滤
sc.pp.filter_cells(adata, min_genes=200) # 去除表达基因200以下的细胞 sc.pp.filter_genes(adata, min_cells=3) # 去除在3个细胞以下表达的基因

在anndata对象中对线粒体基因进行过滤,以及标准化数据和后续的差异分析及聚类的代码如下:

mito_genes = adata.var_names.str.startswith('MT-') 
adata.obs['percent_mito'] = np.sum( adata[:, mito_genes].X, axis=1).A1 / np.sum(adata.X, axis=1).A1 
adata.obs['n_counts'] = adata.X.sum(axis=1).A1
sc.pl.scatter(adata, x='n_counts', y='percent_mito') 
sc.pl.scatter(adata, x='n_counts', y='n_genes')

adata = adata[adata.obs['n_genes'] < 4000, :] 
adata = adata[adata.obs['percent_mito'] < 0.3, :]

#数据标准化
sc.pp.normalize_per_cell(adata, counts_per_cell_after=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, min_mean=0.0125, max_mean=3, min_disp=0.5)
sc.pl.highly_variable_genes(adata)

##保留差异基因进行后续分析
adata = adata[:, adata.var['highly_variable']]
sc.pp.regress_out(adata, ['n_counts', 'percent_mito'])
sc.pp.scale(adata, max_value=10)
sc.tl.pca(adata, svd_solver='arpack') # PCA分析 sc.pl.pca(adata, color='CST3') #绘图

##碎石图,选择多少PCA进行后续分析,用于计算细胞间的相邻距离
sc.pl.pca_variance_ratio(adata, log=True)
adata.write("pca_results.h5ad")
聚类分析
sc.pp.neighbors(adata, n_neighbors=10, n_pcs=40) ##neighbor个数越多,聚类越少
sc.tl.umap(adata) 
sc.pl.umap(adata, color=['CST3', 'NKG7', 'PPBP'])
sc.tl.louvain(adata)
sc.pl.umap(adata, color=['louvain'])

由上也可以看出anndata对象具有切片特性,熟悉pandas的人,操作起来就很熟练。

4、loom

Loom是非常大的omics数据集的有效文件格式,由主矩阵、可选的附加层、可变数量的行和列注释以及稀疏的图形对象组成。我们使用loom文件存储单细胞基因表达数据:主矩阵包含实际的表达值(每个细胞一列,每个基因一行);行注释和列注释包含基因和细胞的元数据,例如名称、染色体、位置(对于基因)和品系、性别、年龄(对于细胞)。图对象用于存储用于基于图的聚类的最近邻图。



在这里附上loom处理的相关代码,以供大家参考:

##创建loom对象
library(Seurat) 
library(loomR) 
library(dplyr) 
pbmc_small_loom<-create(filename = "pbmc.small.loom",data = pbmc_small@assays$RNA@counts,overwrite = T)
 #这里也可以直接as.loom()
##查看loom文件中的信息
pbmc_small_loom pbmc_small_loom$matrix[1:6,1:6] pbmc_small_loom$col.attrs$CellID[1:6] pbmc_small_loom$row.attrs$Gene[1:6]
##提取loom中的信息:
pbmc_small_loom$get.attribute.df(MARGIN = 1,attributes = "Gene")[1:6,] pbmc_small_loom$get.attribute.df(MARGIN = 2,attributes = "CellID")[1:6,]
##添加信息
# Generate random ENSEMBL IDs for demonstration purposes 
ensembl.ids <- paste0("ENSG0000", 1:length(x = pbmc_small_loom$row.attrs$Gene[])) 
pbmc_small_loom$add.row.attribute(list(ensembl.id = ensembl.ids), overwrite = TRUE) pbmc_small_loom$get.attribute.df(MARGIN = 1)[1:6,]
##进行seurat操作
pbmc_small_seurat<-as.Seurat(pbmc_small_loom) pbmc_small_seurat<-NormalizeData(pbmc_small_seurat)%>%ScaleData()
##关闭loom
pbmc_small_loom$close_all()

操作loom结构时,一定注意关闭loom文件,否则再次读取的时候,就会有异常。

02 数据结构中结构转换的软件

虽然各软件都有对应的整套的分析流程,但是也可能碰巧有你想用的软件或者包,不是你熟悉的数据格式,那么我们就可以通过以下一些方法进行转换。

方法一:seurat中的函数直接转换

导入对应的包:

1# install scater https://bioconductor.org/packages/release/bioc/html/scater.html
2library(scater)
3library(Seurat)
4# install SeuratDisk from GitHub using the remotes package remotes::install_github(repo =
5# 'mojaveazure/seurat-disk', ref = 'develop')
6library(SeuratDisk)
7library(patchwork)

进行转换:
converting to/from SingleCellExperiment:

1pbmc <- readRDS(file = "../data/pbmc3k_final.rds")
2pbmc.sce <- as.SingleCellExperiment(pbmc)
3p1 <- plotExpression(pbmc.sce, features = "MS4A1", x = "ident") + theme(axis.text.x = element_text(angle = 45, 
4    hjust = 1))
5p2 <- plotPCA(pbmc.sce, colour_by = "ident")
6p1 + p2
7
 1# download from hemberg lab
 2# https://scrnaseq-public-datasets.s3.amazonaws.com/scater-objects/manno_human.rds
 3manno <- readRDS(file = "../data/manno_human.rds")
 4manno <- runPCA(manno)
 5manno.seurat <- as.Seurat(manno, counts = "counts", data = "logcounts")
 6# gives the same results; but omits defaults provided in the last line
 7manno.seurat <- as.Seurat(manno)
 8Idents(manno.seurat) <- "cell_type1"
 9p1 <- DimPlot(manno.seurat, reduction = "PCA", group.by = "Source") + NoLegend()
10p2 <- RidgePlot(manno.seurat, features = "ACTB", group.by = "Source")
11p1 + p2

Converting to/from loom:

1# download from linnarsson lab
2# https://storage.googleapis.com/linnarsson-lab-loom/l6_r1_immune_cells.loom
3l6.immune <- Connect(filename = "../data/l6_r1_immune_cells.loom", mode = "r")
4l6.seurat <- as.Seurat(l6.immune)
5Idents(l6.seurat) <- "ClusterName"
6VlnPlot(l6.seurat, features = c("Sparc", "Ftl1", "Junb", "Ccl4"), ncol = 2)

方法二:sceasy进行转换

1##安装
2BiocManager::install(c("LoomExperiment", "SingleCellExperiment"), update=F)
3install.packages('reticulate')
4devtools::install_github("cellgeni/sceasy")
5
6##python安装anndata包(版本 < 0.6.20), oompy包(版本 < 3.0.0)使用library(sceasy)
7library(reticulate)
8use_condaenv('EnvironmentName')
9loompy <- reticulate::import('loompy')

安装好软件后,就可以进行随意的转换啦。

 1###Seurat to AnnData
 2sceasy::convertFormat(seurat_object, from="seurat", to="anndata", outFile='filename.h5ad')
 3
 4###AnnData to Seurat
 5sceasy::convertFormat(h5ad_file, from="anndata", to="seurat", outFile='filename.rds')
 6
 7###Seurat to SingleCellExperiment
 8sceasy::convertFormat(seurat_object, from="seurat", to="sce", outFile='filename.rds')
 9
10###SingleCellExperiment to AnnData
11sceasy::convertFormat(sce_object, from="sce", to="anndata", outFile='filename.h5ad')
12
13###SingleCellExperiment to Loom
14sceasy::convertFormat(sce_object, from="sce", to="loom", outFile='filename.loom')
15
16###Loom to AnnData
17sceasy::convertFormat('filename.loom', from="loom", to="anndata", outFile='filename.h5ad')
18
19###Loom to SingleCellExperiment
20sceasy::convertFormat('filename.loom', from="loom", to="sce", outFile='filename.rds')

有了这些工具,就可以让我们在单细胞数据的海洋的惬意遨游。

参考资料:

1、https://osca.bioconductor.org/data-infrastructure.html
2、https://bioc.ism.ac.jp/packages/3.6/bioc/vignettes/SingleCellExperiment/inst/doc/intro.html
3、https://satijalab.org/seurat/articles/essential_commands.html
4、http://linnarssonlab.org/loompy/format/index.html
5、https://satijalab.org/seurat/mca_loom.html
6、https://satijalab.org/seurat/articles/conversion_vignette.html
7、https://github.com/cellgeni/sceasy
8、https://www.bilibili.com/read/cv8675277

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容