劉小澤寫於2020.7.19
爲何取名叫“交響樂”?因爲單細胞分析就像一個大樂團,需要各個流程的協同配合
單細胞交響樂1-常用的數據結構SingleCellExperiment
單細胞交響樂2-scRNAseq從實驗到下游簡介
單細胞交響樂3-細胞質控
單細胞交響樂4-歸一化
單細胞交響樂5-挑選高變化基因
單細胞交響樂6-降維
單細胞交響樂7-聚類分羣
單細胞交響樂8-marker基因檢測
單細胞交響樂9-細胞類型註釋
單細胞交響樂9-細胞類型註釋
單細胞交響樂10-數據集整合後的批次矯正
單細胞交響樂11-多樣本間差異分析
單細胞交響樂12-檢測Doublet
單細胞交響樂13-細胞週期推斷
單細胞交響樂14-細胞軌跡推斷
單細胞交響樂15-scRNA與蛋白丰度信息結合
單細胞交響樂16-處理大型數據
單細胞交響樂17-不同單細胞R包的數據格式相互轉換
單細胞交響樂18-實戰一 Smart-seq2
1 前言
前面的種種都是作爲知識儲備,但是不實戰還是記不住前面的知識
這是第二個實戰練習
使用了一個存在異質性的數據集,是研究小鼠大腦的 (Zeisel et al. 2015)
其中大約包含3000個細胞,包括少突膠質細胞,小膠質細胞和神經元等,使用的細胞分離平臺是Fluidigm C1微流控系統,屬於比較早期的系統【單細胞測序的知識】
文庫製備時加入了UMI
UMI簡單解釋:
UMI就是爲了去除PCR擴增偏差的。一般一個基因對應多個UMI時,出現多個reads含有同一個UMI時,這裏只計數一次。UMI英文解釋:
Each transcript molecule can only produce one UMI count but can yield many reads after fragmentationUMI詳細解釋:
不管是bulk RNA還是scRNA,都需要進行PCR擴增,但是不可避免有一些轉錄本會被擴增太多次,超過了真實表達量。當起始文庫很小時(比如單細胞數據),就需要更多次的PCR過程,這個次數越多,引入的誤差就越大。UMI就是Unique Molecular Identifier,由4-10個隨機核苷酸組成,在mRNA反轉錄後,進入到文庫中,每一個mRNA隨機連上一個UMI,根據PCR結果可以計數不同的UMI,最終統計mRNA的數量。UMI圖片解釋:
UMI有幾個要求:
- 不能是均聚物 ,如AAAAAAAAAA
- 不能有N鹼基
- 不能包含鹼基質量低於10的鹼基
2 數據準備
# 自己下載
library(scRNAseq)
sce.zeisel <- ZeiselBrainData()
# 或者使用之前分享的RData
load('sce.zeisel.RData')
sce.zeisel
# class: SingleCellExperiment
# dim: 20006 3005
# metadata(0):
# assays(1): counts
# rownames(20006): Tspan12 Tshz1 ... mt-Rnr1
# mt-Nd4l
# rowData names(1): featureType
# colnames(3005): 1772071015_C02 1772071017_G12
# ... 1772066098_A12 1772058148_F03
# colData names(10): tissue group # ...
# level1class level2class
# reducedDimNames(0):
# altExpNames(2): ERCC repeat
看到這麼幾個信息:2萬多基因,3005個樣本;只有原始count矩陣;使用了symbol ID;加入了ERCC
# 有57個ERCC
> dim(altExp(sce.zeisel,'ERCC'))
[1] 57 3005
# 而且已經標註了線粒體基因
> table(rowData(sce.zeisel))
endogenous mito
19972 34
一個重要操作:aggregateAcrossFeatures
英文解釋是:Sum together expression values (by default, counts) for each feature set in each cell.
但是隻看說明還是不好理解,舉個例子:
可以看到下面會有很多基因具有多個loc
比如OTTMUSG00000016609_loc4
、OTTMUSG00000016609_loc3
其實可以算作一個基因
head(rownames(sce.zeisel)[grep("_loc[0-9]+$",rownames(sce.zeisel))])
# [1] "Syne1_loc2" "Hist1h2ap_loc1"
# [3] "Inadl_loc1" "OTTMUSG00000016609_loc4"
# [5] "OTTMUSG00000016609_loc3" "Gm5643_loc2"
# 這樣的有300多個
> length(grep("_loc[0-9]+$",rownames(sce.zeisel)))
[1] 330
如果拿一個基因來看
Syne1有Syne1_loc1和Syne1_loc2
> length(grep("Syne1",rownames(sce.zeisel)))
[1] 2
counts(sce.zeisel)[grep("Syne1",rownames(sce.zeisel)),][1:2,1:3]
# 1772071015_C02 1772071017_G12 1772071017_A05
# Syne1_loc2 11 2 4
# Syne1_loc1 0 0 4
如果使用這個函數,會有怎樣效果
test <- aggregateAcrossFeatures(sce.zeisel,
id=sub("_loc[0-9]+$", "", rownames(sce.zeisel)))
# 只剩一個了,也就是合二爲一
> length(grep("Syne1",rownames(test)))
[1] 1
# 看錶達量,也是合二爲一
> counts(test)[grep("Syne1",rownames(test)),][1:3]
1772071015_C02 1772071017_G12 1772071017_A05
11 2 8
因此,明白了,這個函數就是處理相同行:把幾個相同的行的值加在一起變爲一行
也就明白了,下面👇爲什麼要進行sub
操作,其實就是爲了把loc去掉,暴露出相同的基因名,才能執行aggregateAcrossFeatures
函數
sce.zeisel <- aggregateAcrossFeatures(sce.zeisel,
id=sub("_loc[0-9]+$", "", rownames(sce.zeisel)))
> dim(sce.zeisel)
[1] 19839 3005
再添加Ensembl ID
library(org.Mm.eg.db)
rowData(sce.zeisel)$Ensembl <- mapIds(org.Mm.eg.db,
keys=rownames(sce.zeisel), keytype="SYMBOL", column="ENSEMBL")
3 質控
還是備份一下,把unfiltered數據主要用在質控的探索上
unfiltered <- sce.zeisel
這個公共數據的作者在發表文章時將數據的低質量細胞去掉了,但並不妨礙我們做個質控,也可以看看它去除的怎樣
stats <- perCellQCMetrics(sce.zeisel, subsets=list(
Mt=rowData(sce.zeisel)$featureType=="mito"))
qc <- quickPerCellQC(stats, percent_subsets=c("altexps_ERCC_percent",
"subsets_Mt_percent"))
sce.zeisel <- sce.zeisel[,!qc$discard]
> sum(qc$discard)
[1] 189
> dim(sce.zeisel)
[1] 19839 2816
根據原來的數據,加上質控標準作圖
colData(unfiltered) <- cbind(colData(unfiltered), stats)
unfiltered$discard <- qc$discard
# 做個圖
gridExtra::grid.arrange(
plotColData(unfiltered, y="sum", colour_by="discard") +
scale_y_log10() + ggtitle("Total count"),
plotColData(unfiltered, y="detected", colour_by="discard") +
scale_y_log10() + ggtitle("Detected features"),
plotColData(unfiltered, y="altexps_ERCC_percent",
colour_by="discard") + ggtitle("ERCC percent"),
plotColData(unfiltered, y="subsets_Mt_percent",
colour_by="discard") + ggtitle("Mito percent"),
ncol=2
)
再看下文庫大小和ERCC分別和線粒體含量的關係
gridExtra::grid.arrange(
plotColData(unfiltered, x="sum", y="subsets_Mt_percent",
colour_by="discard") + scale_x_log10(),
plotColData(unfiltered, x="altexps_ERCC_percent", y="subsets_Mt_percent",
colour_by="discard"),
ncol=2
)
然後檢查一下被過濾的原因
## low_lib_size low_n_features high_altexps_ERCC_percent
## 0 3 65
## high_subsets_Mt_percent discard
## 128 189
4 歸一化
這裏細胞數量較多,因此需要預先分羣+去卷積計算size factor
library(scran)
set.seed(1000)
clusters <- quickCluster(sce.zeisel)
sce.zeisel <- computeSumFactors(sce.zeisel, cluster=clusters)
sce.zeisel <- logNormCounts(sce.zeisel)
summary(sizeFactors(sce.zeisel))
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.119 0.486 0.831 1.000 1.321 4.509
看看兩種歸一化方法的差異
# 常規:最簡單的只考慮文庫大小
summary(librarySizeFactors(sce.zeisel))
# Min. 1st Qu. Median Mean 3rd Qu. Max.
# 0.1757 0.5680 0.8680 1.0000 1.2783 4.0839
plot(librarySizeFactors(sce.zeisel), sizeFactors(sce.zeisel), pch=16,
xlab="Library size factors", ylab="Deconvolution factors", log="xy")
5 找高變異基因
理論上,應該對每個細胞都標記批次信息,添加block
信息。但是這裏由於技術不同,每個板子上只有20-40個細胞,並且細胞羣體具有高度異質性,不能假設每個板上的細胞類型的分佈是相同的,因此這裏不使用block
將批次信息“鎖住”是合適的
既然有ERCC,就可以用第三種方法【在之前單細胞交響樂5-挑選高變化基因的2.3 考慮技術噪音】:
dec.zeisel <- modelGeneVarWithSpikes(sce.zeisel, "ERCC")
top.hvgs <- getTopHVGs(dec.zeisel, prop=0.1)
> length(top.hvgs)
[1] 1816
同樣使用了spike-in,對比一下,看到這裏不管總體方差還是技術因素方差都要比之前smart-seq2要小。
smart-seq2是按read計數,這裏由於添加了UMI,是按molecule計數,也就是說,UMI的加入,確實減少了PCR擴增的偏差影響
另外圖中看到,這裏STRT-seq的spike-in方差一直要比內源基因的方差小,也就是說內源基因的變化幅度一直保持高位,體現了數據中包含多種細胞類型而導致的異質性,異質性導致了基因表達極度不均衡
6 降維
library(BiocSingular)
set.seed(101011001)
sce.zeisel <- denoisePCA(sce.zeisel, technical=dec.zeisel, subset.row=top.hvgs)
sce.zeisel <- runTSNE(sce.zeisel, dimred="PCA")
看一下得到的PC數
ncol(reducedDim(sce.zeisel, "PCA"))
## [1] 50
7 聚類
snn.gr <- buildSNNGraph(sce.zeisel, use.dimred="PCA")
colLabels(sce.zeisel) <- factor(igraph::cluster_walktrap(snn.gr)$membership)
看一下結果
table(colLabels(sce.zeisel))
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14
## 283 451 114 143 599 167 191 128 350 70 199 58 39 24
畫圖
plotTSNE(sce.zeisel, colour_by="label")
8 找marker基因並解釋結果
主要還是關注上調基因,可以幫我們快速判斷出異質性羣體中各個細胞類型的差異
比如還是針對cluster1看看
markers <- findMarkers(sce.zeisel, direction="up")
marker.set <- markers[["1"]]
> ncol(marker.set)
[1] 17
> colnames(marker.set)
[1] "Top" "p.value" "FDR" "summary.logFC" "logFC.2" "logFC.3" "logFC.4"
[8] "logFC.5" "logFC.6" "logFC.7" "logFC.8" "logFC.9" "logFC.10" "logFC.11"
[15] "logFC.12" "logFC.13" "logFC.14"
head(marker.set[,1:8], 10)
使用cluster1的Top10基因(但不一定只是10個)畫熱圖
top.markers <- rownames(marker.set)[marker.set$Top <= 10]
> length(top.markers)
[1] 58
plotHeatmap(sce.zeisel, features=top.markers, order_columns_by="label")
接下來就是根據背景知識了,比如看到Gad1、Slc6a1表達量都很高,可能表明cluster1屬於中間神經元
另一種方法:基於logFC
比如可以挑出cluster1的計算結果marker.set
中前50個基因(這裏就是50個,而不是Top50),然後根據cluster1與其他clusters的logFC,對每個基因表達量做熱圖
library(pheatmap)
logFCs <- getMarkerEffects(marker.set[1:50,])
pheatmap(logFCs, breaks=seq(-5, 5, length.out=101))
那麼這個函數到底做了什麼呢?看圖就知道:
也就是把每個基因在其他clusters的logFC結果挑出來,彙集成了一個新矩陣,我們自己手動也是可以做到的
歡迎關注我們的公衆號~_~
我們是兩個農轉生信的小碩,打造生信星球,想讓它成爲一個不拽術語、通俗易懂的生信知識平臺。需要幫助或提出意見請後臺留言或發送郵件到[email protected]