單細胞交響樂19-實戰二 STRT-Seq

劉小澤寫於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 fragmentation

UMI詳細解釋:
不管是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_loc4OTTMUSG00000016609_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]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章