seurat-AverageExpression()源码解析
前段时间,一个单细胞分析同行提问,发现AverageExpression()和通过aggregate计算得到的基因表达均值,两者的结果是不一样的,疑惑问题出在哪里。
这个问题我们或多或少都有过疑问,自己手动计算基因在每个cluster的表达均值,绘制heatmap热图;跟AverageExpression()处理后绘制DoHeatmap会有差异。网上网友也提过类似的问题,也不知其解。决心看下AverageExpression()的源码。
一、测试AverageExpression()函数
library(Seurat)
table(Idents(seurat_obj_Bcell))
# B0 B1 B2 B3 B4 B5 B6 B7 B8
# 2573 2115 2093 1536 1355 1344 1005 92 74
cluster.averages <- AverageExpression(seurat_obj_Bcell, slot = 'data', return.seurat = TRUE)
# assays有三种模式:RNA, SCT, integrated
# -----------------------------------
dim(cluster.averages@assays$RNA@data)
# [1] 21972 9
head(cluster.averages@assays$RNA@data)
# B0 B1 B2 B3 B4
# AL627309.1 0.004422988 0.003237424 0.001007899 0.004426136 0.001509931
# AL627309.5 0.015784045 0.026414631 0.032074415 0.023697387 0.006019230
# AP006222.2 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000
# LINC01409 0.008551480 0.009181925 0.019694557 0.019493293 0.025692960
# FAM87B 0.001375785 0.002404144 0.001298187 0.001751876 0.000000000
# LINC01128 0.138134556 0.118331411 0.149791178 0.063821427 0.072906583
# B5 B6 B7 B8
# AL627309.1 0.001075568 0.003654187 0.00000000 0.0000000
# AL627309.5 0.011408478 0.029097945 0.00000000 0.0000000
# AP006222.2 0.000000000 0.000000000 0.00000000 0.0000000
# LINC01409 0.013514985 0.003152831 0.06921692 0.0000000
# FAM87B 0.000000000 0.000000000 0.00000000 0.0000000
# LINC01128 0.054664322 0.153354677 0.02744229 0.1327957
write.csv(cluster.averages@assays$RNA@data, file = "cluster.averages_assays_RNA_data.csv")
#-----------------------------------
dim(cluster.averages@assays$SCT@data)
# [1] 16717 9
head(cluster.averages@assays$SCT@data)
# B0 B1 B2 B3 B4
# AL627309.1 0.0019413712 0.0009451797 0.000477669 0.0019512201 0.0007377352
# AL627309.5 0.0061991675 0.0117510165 0.011873804 0.0122939109 0.0029476808
# LINC01409 0.0031043874 0.0047169899 0.007141187 0.0116506172 0.0110092855
# FAM87B 0.0003885759 0.0009451797 0.000477669 0.0006508298 0.0000000000
# LINC01128 0.0551916324 0.0596507087 0.058458200 0.0351811146 0.0376583238
# LINC00115 0.0177200322 0.0150167064 0.019868203 0.0225307245 0.0204535984
# B5 B6 B7 B8
# AL627309.1 0.000743771 0.0009945302 0.00000000 0.00000000
# AL627309.5 0.006674107 0.0089153637 0.00000000 0.00000000
# LINC01409 0.005934736 0.0009945302 0.03208831 0.00000000
# FAM87B 0.000000000 0.0000000000 0.00000000 0.00000000
# LINC01128 0.023530497 0.0523375251 0.02150621 0.07796154
# LINC00115 0.011834458 0.0187289851 0.02150621 0.05264373
write.csv(cluster.averages@assays$SCT@data, file = "cluster.averages_assays_SCT_data.csv")
#-----------------------------------
dim(cluster.averages@assays$integrated@data)
# [1] 2000 9
head(cluster.averages@assays$integrated@data)
# B0 B1 B2 B3 B4
# LYZ 0.005967343 0.01146686 0.010868760 6.508555e-03 0.012815378
# S100A9 0.009264568 0.03849551 0.005806276 1.388812e-05 0.018757573
# S100A8 0.003377878 0.02276050 0.010335783 1.019810e-03 0.009345999
# IGKV3-15 1.374223462 0.96204743 1.358024467 1.397423e+00 1.096290152
# IGKV3-20 1.529314027 0.96532312 1.316039517 1.776763e+00 1.215281468
# IGKV3-11 1.290120895 0.57287190 1.234111953 9.728570e-01 1.339886382
# B5 B6 B7 B8
# LYZ 0.007843731 0.009692197 -0.005189707 0.002062152
# S100A9 0.006850602 0.008352554 -0.005198381 0.002006344
# S100A8 0.001704852 0.003578382 -0.004440559 0.015471037
# IGKV3-15 1.402736193 0.711017421 0.445023238 0.727528712
# IGKV3-20 1.456745150 0.853644113 1.736057719 1.514083481
# IGKV3-11 1.137085218 0.554071284 1.533300791 0.738172971
write.csv(cluster.averages@assays$integrated@data, file = "cluster.averages_assays_integrated_data.csv")
二、测试AverageExpression()源码
从GitHub下载seurat源码,找到AverageExpression()函数的代码,把该函数的调用函数代码也找齐,运用自己的数据集,一步步执行。我们的目的是计算基因在每个cluster(B0-B8)的平均表达值。
step1: 调用AverageExpression()
AverageExpression()调用内部函数(),把自身的参数传递给它。
# step1: AverageExpression函数是传递变量给PseudobulkExpression
object <- seurat_obj_Bcell
pb.method = 'average'
assays = NULL
features = NULL
return.seurat = TRUE
group.by = 'ident'
add.ident = NULL
slot = 'data'
verbose = TRUE
PseudobulkExpression(
object = object,
pb.method = 'average',
assays = assays,
features = features,
return.seurat = return.seurat,
group.by = group.by,
add.ident = add.ident,
slot = slot,
verbose = verbose,
...
)
step2: PseudobulkExpression()
查看PseudobulkExpression()函数,pb.method参数只能选'average' 或者'aggregate'方法;aggregate方法先不考虑,只考虑average方法。该函数代码较多,只提取重要代码和参数。
操作1:获取细胞的cluster矩阵信息data;即获取每个细胞对应的cluster信息,‘sample1-AAACGGGCACCTTGTC’细胞的cluster为B4。data为12187*1的矩阵,测试数据共12187个细胞。
assays
# [1] "RNA" "integrated" "SCT"
data <- FetchData(object = object, vars = rev(x = group.by))
dim(data)
# [1] 12187 1
head(data)
# ident
# sample1-AAACGGGCACCTTGTC B4
# sample1-AAACGGGGTAACGACG B2
# sample1-AAAGATGGTACCGCTG B6
# sample1-AAAGATGGTGACGCCT B4
# sample1-AAAGTAGAGTATCGAA B2
# sample1-AAAGTAGGTCTCATCC B3
# data是每个细胞对应的cluster信息(B1-B8),共12187个细胞;
# 去除为na的数据;该案例无NA数据;
data <- data[which(rowSums(x = is.na(x = data)) == 0), , drop = F]
dim(data)
# [1] 12187 1
操作2:cluster矩阵数据data转成稀疏矩阵category.matrix,列为cluster的列名(B0, B1, B2...B8),共9列。data将由12187*1的矩阵转成12187*9的矩阵。
library(Matrix)
category.matrix <- sparse.model.matrix(object = as.formula(
object = paste0(
'~0+',
paste0(
"data[,",
1:length(x = group.by),
"]",
collapse = ":"
)
)
))
dim(category.matrix)
# [1] 12187 9
head(category.matrix)
# 6 x 9 sparse Matrix of class "dgCMatrix"
# data[, 1]B0 data[, 1]B1 data[, 1]B2 data[, 1]B3 data[, 1]B4 data[, 1]B5
# 1 . . . . 1 .
# 2 . . 1 . . .
# 3 . . . . . .
# 4 . . . . 1 .
# 5 . . 1 . . .
# 6 . . . 1 . .
# data[, 1]B6 data[, 1]B7 data[, 1]B8
# 1 . . .
# 2 . . .
# 3 1 . .
# 4 . . .
# 5 . . .
# 6 . . .
# category.matrix是转成一个稀疏矩阵;
# head(data)
# ident
# sample1-AAACGGGCACCTTGTC B4
# sample1-AAACGGGGTAACGACG B2
# sample1-AAAGATGGTACCGCTG B6
# sample1-AAAGATGGTGACGCCT B4
# sample1-AAAGTAGAGTATCGAA B2
# sample1-AAAGTAGGTCTCATCC B3
# 将12187*1的矩阵转成12187*9的矩阵,
# 如sample1-AAACGGGCACCTTGTC为B4,即data[, 1]B4为1,其余为0;
# 如sample1-AAACGGGCACCTTGTC为B2,即data[, 1]B2为1,其余为0;
# 对每列(B0-B8)求和,统计属于该cluster的细胞总数
colsums <- colSums(x = category.matrix)
colsums
# data[, 1]B0 data[, 1]B1 data[, 1]B2 data[, 1]B3 data[, 1]B4 data[, 1]B5
# 2573 2115 2093 1536 1355 1344
# data[, 1]B6 data[, 1]B7 data[, 1]B8
# 1005 92 74
table(data$ident)
# B0 B1 B2 B3 B4 B5 B6 B7 B8
# 2573 2115 2093 1536 1355 1344 1005 92 74
# 数据过滤,如果某cluster没有任一细胞,剔除该cluster
category.matrix <- category.matrix[, colsums > 0]
colsums <- colsums[colsums > 0]
操作3:稀疏矩阵category.matrix进行average操作,计算每个细胞在cluster的占比。例如,属于B0这个cluster的细胞群有2573个细胞,那每个细胞的占比是1/2573=0.00038865。
想一想为什么要这么操作?
if (pb.method == 'average') {
category.matrix <- sweep(
x = category.matrix,
MARGIN = 2,
STATS = colsums,
FUN = "/")
}
1/colsums
# data[, 1]B0 data[, 1]B1 data[, 1]B2 data[, 1]B3 data[, 1]B4 data[, 1]B5
# 0.0003886514 0.0004728132 0.0004777831 0.0006510417 0.0007380074 0.0007440476
# data[, 1]B6 data[, 1]B7 data[, 1]B8
# 0.0009950249 0.0108695652 0.0135135135
操作4:默认slot = "data",获取归一化之后每个细胞的基因表达矩阵,矩阵大小为21972*12187。
# 如果为"RNA",
data.use <- GetAssayData(
object = object,
assay = "RNA",
slot = "data"
)
# data.use为一个21972*12187的基因表达矩阵,21972为基因个数,12187为细胞个数;slot = "data"表明是归一化之后的基因表达矩阵;
dim(data.use)
# [1] 21972 12187
data.use[1:20,1:20]
操作5:如slot = "data",基因表达矩阵需进行expm1处理。这个很关键。
if (slot[i] == 'data') {
data.use <- expm1(x = data.use)
}
data.use[1:20,1:20]
问题1:为什么要进行expm1处理?
数据均一化的时候进行了log1p处理, expm1和log1p是相对的;
操作6:计算最终的每个细胞的基因在cluster的平均表达量;即基因表达矩阵data.use和细胞在cluster的占比矩阵category.matrix乘积。
两个矩阵的乘积,要求第一个矩阵的行数data.use与第二个矩阵category.matrix列数相等。
data.return[[i]] <- as.matrix(x = (data.use %*% category.matrix))
image.png
image.png
问题2:为什么通过源码推算的基因平均表达量矩阵和AverageExpression()计算的不完全一致?
猜测是category.matrix等矩阵小数点保留位数不一致,导致后面几位数不一致;
image.png
问题3:pb.method有'average' 或者'aggregate'方式,是否可以切换到aggregate方式:
否,AverageExpression()没有外置参数,调用PseudobulkExpression()函数时,已设置为pb.method = 'average',不能切换成aggregate
问题4:回归到最初的提问,AverageExpression()函数,计算出来的结果与aggregate得到的均值不一样?
尝试进行expm1()处理;
group.by <- "recluster"
mat <- as.data.frame(t(as.matrix(GetAssayData(object, assay = "RNA", slot = "data"))))
mat <- expm1(x = mat)
mat <- aggregate(mat, by=list(object@meta.data[[group.by]]), FUN="mean")
rownames(mat) <- mat$Group.1
mat <- t(mat[,-1])
head(mat)
比较expm1()处理后的aggregate得到的均值和AverageExpression()计算的均值,是一样的;
image.png
通过上面的测试,AverageExpression()函数,计算出来的结果与aggregate得到的均值不一样的原因是AverageExpression()对归一化的表达矩阵进行了expm1()处理。