seurat-AverageExpression()源码解析

前段时间,一个单细胞分析同行提问,发现AverageExpression()和通过aggregate计算得到的基因表达均值,两者的结果是不一样的,疑惑问题出在哪里。

这个问题我们或多或少都有过疑问,自己手动计算基因在每个cluster的表达均值,绘制heatmap热图;跟AverageExpression()处理后绘制DoHeatmap会有差异。网上网友也提过类似的问题,也不知其解。决心看下AverageExpression()的源码。

image.png

一、测试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

image.png

问题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()处理。

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

推荐阅读更多精彩内容