單細胞交響樂15-scRNA與蛋白丰度信息結合

劉小澤寫於2020.7.16
爲何取名叫“交響樂”?因爲單細胞分析就像一個大樂團,需要各個流程的協同配合
單細胞交響樂1-常用的數據結構SingleCellExperiment
單細胞交響樂2-scRNAseq從實驗到下游簡介
單細胞交響樂3-細胞質控
單細胞交響樂4-歸一化
單細胞交響樂5-挑選高變化基因
單細胞交響樂6-降維
單細胞交響樂7-聚類分羣
單細胞交響樂8-marker基因檢測
單細胞交響樂9-細胞類型註釋
單細胞交響樂9-細胞類型註釋
單細胞交響樂10-數據集整合後的批次矯正
單細胞交響樂11-多樣本間差異分析
單細胞交響樂12-檢測Doublet
單細胞交響樂13-細胞週期推斷
單細胞交響樂14-細胞軌跡推斷

1 前言

CITE-seq(Cellular indexing of transcriptomes and epitopes by sequencing)技術是紐約基因組中心和Satija實驗室(即Seurat的開發團隊)聯合在2017年開發的,能夠對數千個單細胞的MRNA進行測定,同時還能夠對同一個單細胞中的表面蛋白標記物進行測序。

以往的研究方法是在將細胞放在平板上進行scRNA測序之前,通過流式細胞儀捕獲單個細胞的蛋白質信息,但這樣通量很低,並且受限於相對少量的蛋白質標記物。而CITE-seq可以不受抗體之間信號干擾的影響,大大增加了表面蛋白標記的數量,從而可以一次性獲得多種由抗體蛋白指示的免疫表型的信息。

在這個方法中,重點是設計抗體-寡核苷酸結合物,並預估使用量。細胞首先標記上帶有RNA標籤的抗體,如果一個細胞預估的目標蛋白丰度越高,那麼就應該帶更多的抗體,也就應該帶更多的antibody-derived tag (ADT)。之後利用液滴法將細胞分離,ADTs和內源轉錄本分別進行反轉錄、做成cDNA文庫、測序。也就是說,蛋白丰度和基因表達量是受ADTs和內源轉錄本數量影響。

下面來自:https://en.wikipedia.org/wiki/CITE-Seq

  • Step1——ADT preparation:It involves labeling an antibody directed against a cell surface protein of interest with oligonucleotides for barcoding the antibody.
  • Step2——Bind:ADT labelled cells are encapsulated within a droplet as single cells with DNA-barcoded microbeads
  • Step3——Release:Cell lysed to release both bound ADTs as well as mRNA and converted to cDNA. cDNA is prepared from both ADTs and cellular mRNAs.
  • Step4——PCR:cDNA is PCR-amplified and ADT cDNA and mRNA cDNA are separated based on size(ADT-derived cDNAs are < 180bp and mRNA-derived cDNAs are > 300bp)
  • Step5——Sequence: The independent libraries are pooled together and sequenced to obtain proteomics and transcriptomics data

圖片來自:https://www.protocols.io/view/cite-seq-protocols-ngzdbx6,這是一個關於實驗流程記錄的網站,包含了大量的實驗操作,相當於一個共享的實驗記錄本

那麼問題來了:怎麼才能把ADT數據和常規的表達量數據整合到一起呢?

首先需要明確一下:

  • 這兩種數據的本質是不同的,因此也不太可能直接把ADT行也想表達量數據一樣當做是一個feature信息;
  • 數量差異巨大:大部分實驗只選用了少量感興趣的蛋白(不超過20種),它們是研究者提前指定好的實驗設計,而轉錄組卻是整個轉錄組,包含幾萬個基因。
  • 測序資源不均等:ADTs的測序深度會比較深,因爲它們是從轉錄本中分離出來單獨測序,因此更容易使測序資源產生”二八分佈“,即ADTs會佔用更多的測序資源

數據準備

將會使用10X PBMC的數據,其中就會包括一些感興趣的表面蛋白定量結果

library(BiocFileCache)
bfc <- BiocFileCache(ask=FALSE)
stuff <- bfcrpath(bfc, file.path("http://cf.10xgenomics.com",
    "samples/cell-exp/3.0.0/pbmc_10k_protein_v3",
    "pbmc_10k_protein_v3_filtered_feature_bc_matrix.tar.gz"))
untar(stuff, exdir=tempdir())

library(DropletUtils)
sce <- read10xCounts(file.path(tempdir(), "filtered_feature_bc_matrix"))
sce
## class: SingleCellExperiment 
## dim: 33555 7865 
## metadata(1): Samples
## assays(1): counts
## rownames(33555): ENSG00000243485 ENSG00000237613 ... IgG1 IgG2b
## rowData names(3): ID Symbol Type
## colnames: NULL
## colData names(2): Sample Barcode
## reducedDimNames(0):
## altExpNames(0):

> names(rowData(sce))
[1] "ID"     "Symbol" "Type"  

# 看到行中就包含了ADT的信息
> table(rowData(sce)$Type)

Antibody Capture  Gene Expression 
              17            33538 

2 預處理

2.1 數據分離

拿到這個sce對象,它裏面是整合了轉錄組和ADT表達信息的,我們首先把ADT分離出來(用splitAltExps()),並且之前介紹sce的時候說到,sce有一個模塊是alternative Experiment,使用altExp()獲取,可以存儲其他類型的實驗結果。於是我們可以將ADT的信息放進去

sce <- splitAltExps(sce, rowData(sce)$Type)
altExpNames(sce)
## [1] "Antibody Capture"

# 現在這裏就存儲着17個ADT的信息
altExp(sce)
## class: SingleCellExperiment 
## dim: 17 7865 
## metadata(1): Samples
## assays(1): counts
## rownames(17): CD3 CD4 ... IgG1 IgG2b
## rowData names(3): ID Symbol Type
## colnames: NULL
## colData names(0):
## reducedDimNames(0):
## altExpNames(0):

如果現在看錶達量的話,它是一個稀疏矩陣

> counts(altExp(sce))[1:3,1:3]
3 x 3 sparse Matrix of class "dgCMatrix"
                
CD3   18  30  18
CD4  138 119 207
CD8a  13  19  10

需要先把它變成常規的矩陣,因爲它本身的數據並不稀疏,只是因爲使用了sce對象,才默認使用稀疏矩陣節省空間和計算資源,而下游分析與稀疏矩陣的兼容性並不好

counts(altExp(sce)) <- as.matrix(counts(altExp(sce)))
> counts(altExp(sce))[1:3,1:3]
     [,1] [,2] [,3]
CD3    18   30   18
CD4   138  119  207
CD8a   13   19   10

2.2 質控

大多數情況下,質控還是根據轉錄組數據,去掉空的液滴和低質量細胞。因爲細胞質量不好,又怎麼能體現真實的轉錄本和ADT的數量呢?這一部分,低質量細胞的衡量標準還要依靠線粒體水平,而它還是轉錄組的範疇。因此ADT數據對於質控這一步的貢獻不大

我們這裏拿到的數據其實是經過CellRanger過濾了的,已經除去了空的液滴,因此只需要關注線粒體水平去掉低質量細胞即可。

library(scater)
mito <- grep("^MT-", rowData(sce)$Symbol)
df <- perCellQCMetrics(sce, subsets=list(Mito=mito))
mito.discard <- isOutlier(df$subsets_Mito_percent, type="higher")
summary(mito.discard)
##    Mode   FALSE    TRUE 
## logical    7569     296

如果只根據轉錄組過濾感覺不全面的話,也是可以用一下ADT的。基於ADT的特點,它會佔用大量的測序資源,它的測序深度一般都很高,因此如果發現某個細胞的ADT表達量很低,那麼可以它沒有成功加上ADT信息,可以去除

isOutlier使用的是MAD指標(絕對中位差來估計方差,先計算出數據與它們的中位數之間的偏差,然後這些偏差的絕對值的中位數就是MAD,median absolute deviation)。函數認爲超過中位數3倍MAD的值就是離羣值

# 由於ADT表達量一般很大, 因此需要log2轉換
# min_diff含義: minimum difference from the median to consider as an outlier
ab.discard <- isOutlier(df$`altexps_Antibody Capture_detected`,
    log=TRUE, type="lower", min_diff=1)
# 勉強過濾掉一個細胞
summary(ab.discard)
##    Mode   FALSE    TRUE 
## logical    7864       1

最後整合一下過濾信息

discard <- ab.discard | mito.discard
> table(discard)
discard
FALSE  TRUE 
 7568   297 

sce <- sce[,!discard]

2.3 歸一化

這一部分,ADT就需要單獨進行處理了

由於內源轉錄本和ADT的生物屬性不同,導致它們的細胞捕獲效率不同,因此這兩種信息的細胞捕獲相關的數據偏差也不太可能一致,因此需要各自處理。

另外,ADT的組成差異是很明顯的:

  • 蛋白的丰度變化更加複雜,蛋白之間是存在互作關係的(試想一下PPI蛋白互作網絡),有一點”牽一髮動全身“的意思。蛋白丰度的變化,也會直接導致ADT的數量變化
  • 靶標蛋白都是提前選好的,因此細胞中也更容易出現丰度的差異

爲了減少這些非生物因素差異,有幾種方法可以對每個細胞計算size factor:

第一種:基於ADTs的文庫大小的計算

首先回顧一下什麼是文庫大小:
We define the library size as the total sum of counts across all genes for each cell, the expected value of which is assumed to scale with any cell-specific biases.

然後看看根據文庫大小得到的size factor:
The “library size factor” for each cell is then directly proportional to its library size where the proportionality constant is defined such that the mean size factor across all cells is equal to 1

這也是最直接最簡單的方法,對分羣是夠用的,但它做了一個無偏估計,忽略了細胞間原本要上調或者下調的差異,對於分羣結果的解釋,我們卻正需要差異分析的結果去判斷每羣的marker基因

sf.lib <- librarySizeFactors(altExp(sce))
summary(sf.lib)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##  0.02668  0.52418  0.90485  1.00000  1.26625 22.81740
第二種:基於DESeq的計算

來自DESeq的名詞解釋:
"ratio" uses the standard median ratio method introduced in DESeq.
The size factor is the median ratio of the sample over a "pseudosample": for each gene, the geometric mean of all samples.

這裏我們也需要指定一個pseudosample,其實最好是來自空液滴的barcode定量結果,但是這裏的數據中空液滴被去除,所以只能用其他的均值來代替

# pseudosample
ambient <- rowMeans(counts(altExp(sce)))
# DESeq-like size factors
sf.amb <- medianSizeFactors(altExp(sce), reference=ambient)
summary(sf.amb)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0030  0.5927  0.8282  1.0000  1.1439 41.7972
第三種:基於對照的計算

一些實驗包含了對照的抗體(例如IgG),這些對照與真實抗體特性相似,但在細胞中缺乏特異性靶標。我們可以做出假設:對象的ADTs在細胞間的丰度無差異。如果發現其中的任何差異,都可以基於這些差異進行歸一化。

controls <- grep("^Ig", rownames(altExp(sce)))
> controls
[1] 15 16 17
sf.control <- librarySizeFactors(altExp(sce), subset_row=controls) 
summary(sf.control)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.6854  0.8757  1.0000  1.1423 44.0155

這裏推薦使用第二種基於DESeq的計算方法,最後使用logNormCounts會對轉錄本和ADTs分別計算size factor、進行歸一化和log轉換

sizeFactors(altExp(sce)) <- sf.amb
sce <- logNormCounts(sce, use_altexps=TRUE)

assayNames(sce)
## [1] "counts"    "logcounts"
assayNames(altExp(sce))
## [1] "counts"    "logcounts"

3 聚類

與轉錄本分析不同的是,ADT數據不需要挑選特徵信息,因爲:

  • ADT的特徵信息選擇早在實驗設計階段就已經完成,我們是先選出感興趣的蛋白,然後再做的實驗得到的數據
  • 由於ADTs的數據中行數很少,因此也沒有必要去根據這些挑選HVGs或者選PCs
  • 每個ADT數據是捕獲不同的生物信號,因此這裏面的外源噪音並不大,不需要降維也可以輕鬆去除

綜上,對於ADT數據,可以直接對log歸一化的結果進行分羣和可視化

# 設置NA可以跳過PCA,直接分羣
g.adt <- buildSNNGraph(altExp(sce), d=NA) 
clusters.adt <- igraph::cluster_walktrap(g.adt)$membership

# 可視化
set.seed(1010010)
altExp(sce) <- runTSNE(altExp(sce))
colLabels(altExp(sce)) <- factor(clusters.adt)
plotTSNE(altExp(sce), colour_by="label", text_by="label", text_col="red")

因爲ADTs數量很少,所以對每個cluster的檢查也變得簡單,將每個ADT的log表達量均值畫成熱圖即可

se.averaged <- sumCountsAcrossCells(altExp(sce), clusters.adt,
    exprs_values="logcounts", average=TRUE)
# 17個ADT,28個cluster
> dim(se.averaged)
[1] 17 28

> assay(se.averaged)[1:3,1:3]
            1        2        3
CD3  4.621559 9.556522 5.673089
CD4  4.348560 9.795961 9.078986
CD8a 4.797436 5.262174 5.306986

# 熱圖
library(pheatmap)
averaged <- assay(se.averaged)
pheatmap(averaged - rowMeans(averaged),
    breaks=seq(-3, 3, length.out=101))

4 與基因表達數據的結合

4.1 繼續分亞羣

上面我們對ADT進行了分羣,現在根據這個分羣結果+轉錄組數據再分亞羣。

這個思路就很像scRNA數據分析後,再補充一個FACS實驗,都是爲了更好地分離檢查細胞類型。既然得到了ADT的許多”乾淨“分羣結果(因爲ADT每羣的表達量都很高並且代表的生物信號很強),那就相當於先得到了一些可靠的細胞類型,那麼就可以結合轉錄組表達量去研究其中更細小的變化。

下面就對ADT得到的cluster進行循環分亞羣

使用了quickSubCluster函數,它會對現在的sce對象中已有的分組,進一步分羣

其中的sce表示對這個對象進行操作,clusters.adt指定分組,prepFUN指的是在分羣之前的準備工作,clusterFUN指的是分羣的操作

set.seed(101010)
all.sce <- quickSubCluster(sce, clusters.adt,
    prepFUN=function(x) {
        dec <- modelGeneVar(x)
        top <- getTopHVGs(dec, prop=0.1)
        x <- runPCA(x, subset_row=top, ncomponents=25)
    },
    clusterFUN=function(x) {
        g.trans <- buildSNNGraph(x, use.dimred="PCA")
        igraph::cluster_walktrap(g.trans)$membership
    }
)
# 結果是一個列表
> all.sce
List of length 28
names(28): 1 2 3 4 5 6 7 8 ... 21 22 23 24 25 26 27 28

如果對應上面的t-SNE圖,看到第3羣細胞數量還蠻多的,可以看一下

> all.sce[[3]]
class: SingleCellExperiment 
dim: 33538 1828 
metadata(1): Samples
assays(2): counts logcounts
rownames(33538): ENSG00000243485 ENSG00000237613
  ... ENSG00000277475 ENSG00000268674
rowData names(3): ID Symbol Type
colnames: NULL
colData names(4): Sample Barcode sizeFactor
  subcluster
reducedDimNames(1): PCA
altExpNames(1): Antibody Capture

# 它又可以分8個亞羣
> table(all.sce[[3]]$subcluster)

3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 
 90 772  25 178 695  44  19   5 
看一下每個ADT分羣結果各自對應的亞羣
# 每個ADT羣下面有多少細胞
ncells <- sapply(all.sce, ncol,simplify = TRUE)
> ncells
   1    2    3    4    5    6    7    8    9   10   11 
 888   92 1828 1186  512   55   71   85  999  571   70 
  12   13   14   15   16   17   18   19   20   21   22 
 148   73   39  409   21   39   69   74  126   32   50 
  23   24   25   26   27   28 
  13   17   29   38   22   12 

# 每個ADT羣下面又分幾個亞羣
nsubclusters <- sapply(all.sce, function(x) length(unique(x$subcluster)),
                       simplify = TRUE)
> nsubclusters
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 
21  3  8  7  3  3  2  3  9  8  3  2  3  1 12  1  1  2 
19 20 21 22 23 24 25 26 27 28 
 3  3  1  2  1  1  1  1  1  1 

畫個圖看一下

plot(ncells, nsubclusters, xlab="Number of cells", type="n",
    ylab="Number of subclusters", log="xy")
text(ncells, nsubclusters, names(all.sce))

繼續分亞羣的一個好處是:我們可以基於之前ADT的分羣結果有一個大體的預估,每個cluster大體是什麼性質的。假如我們知道了ADT的一個clusterX中包含T細胞,那麼就沒有必須再對X.1、X.2之類的亞羣再進行判斷是不是T細胞了。而是可以把更多的精力放在findMarkers()後續分析上。比如已經發現cluster12中包含CD8+ T細胞,而且它又分成了兩個亞羣,就可以繼續根據顆粒酶表達量判斷亞羣

of.interest <- "12"
# 左邊是GZMH基因,右邊是GZHK
plotExpression(all.sce[[of.interest]], x="subcluster",
    features=c("ENSG00000100450", "ENSG00000113088"))
注意

我們這裏的亞羣都是根據之前的分羣結果繼續向下走的,如果說之前的某個分羣不對怎麼辦?那不就誤導了後續的分析?

因此可以補充一些檢查,看看每個亞羣是不是和它們的上一級分羣結果有相似的蛋白丰度

of.interest <- "12"
sce.cd8 <- all.sce[[of.interest]]
plotExpression(altExp(sce.cd8), x=I(sce.cd8$subcluster),
    features=c("CD3", "CD8a"))

4.2 利用轉錄組的分羣找蛋白相關的marker基因

我們選用ADT看蛋白丰度的目的應該不是爲了看細胞類型,因爲這個事情使用轉錄組也能完成,並且完成的效果還不錯。那麼爲什麼使用蛋白數據呢?因爲蛋白可以更直接地反映生物過程活性,可以減少後續手動註釋細胞類型的麻煩和不準確性。因此,結合轉錄組的分羣結果,這樣會不會更快地幫助判斷某羣細胞的生物功能呢?

對轉錄組數據進行快速分羣
sce <- logNormCounts(sce)
dec <- modelGeneVar(sce)
top <- getTopHVGs(dec, prop=0.1)

set.seed(1001010)
sce <- runPCA(sce, subset_row=top, ncomponents=25)

g <- buildSNNGraph(sce, use.dimred="PCA")
clusters <- igraph::cluster_walktrap(g)$membership
colLabels(sce) <- factor(clusters)

set.seed(1000010)
sce <- runTSNE(sce, dimred="PCA")
plotTSNE(sce, colour_by="label", text_by="label")
ADT數據+ 分羣結果 =》找marker基因
markers <- findMarkers(altExp(sce), colLabels(sce))
of.interest <- markers[[16]]
pheatmap(getMarkerEffects(of.interest), breaks=seq(-3, 3, length.out=101))

圖中可以看到,cluster16中的PD-1表達量升高,如果再把它和某個感興趣的表型結合起來(例如T細胞衰竭),就可以對數據多一些認識

用於標記衰竭程度的一個分子標記是PD-1,以“阻斷T細胞殺傷能力”而聞名。PD-1的沉默會通過抑制T細胞的增殖而損害其抗腫瘤功能。

來自:PD-1免疫療法的那些事兒
Naive T細胞在TCR信號通路激活的時候,會飛快地表達PD-1。而當T細胞被連續不斷地刺激時,T細胞會變成exhausted T,會持續性地表達很高的PD-1

整個過程不需要任何的預判,也不需要對ADT有任何的瞭解,所有的結果都是基於分羣+表達量差異。既然後面是按照ADT的數據推斷的marker基因,那麼如果有直接針對ADT數據的聚類分羣,結果就會更準確,只不過目前數據量還達不到像轉錄組一樣的水平,做降維聚類效果不太好。


歡迎關注我們的公衆號~_~  
我們是兩個農轉生信的小碩,打造生信星球,想讓它成爲一個不拽術語、通俗易懂的生信知識平臺。需要幫助或提出意見請後臺留言或發送郵件到[email protected]

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