细胞间通讯教程|| cellphonedb 及其可视化

细胞配受体通识以及常见细胞分泌信号通路
CellChat:细胞间相互作用分析利器
CellChat:细胞间相互作用在线内容
Cell-Cell Interaction Database|| 单细胞配受体库你还在文章的附录里找吗?
celltalker:单细胞转录组配受体分析
Network在单细胞转录组数据分析中的应用

在单细胞数据分析过程中,上游的拆库定量,中游的分群注释,目前的门槛是很低的了。但是解析细胞类型异质性不应止于这些,还可以看细胞群之间的通讯。当然,这方面我们介绍过CellChat:细胞间相互作用分析利器。CellChat是以信号通路为单位来计算细胞间交流状态的,很多同学用cellphonedb来做基于配受体对的细胞间交流。今天,我们就来用经典的pbmc3k数据跑一下cellphonedb,并尝试可视化。

先放一张想象图:


来自文章: Single-cell transcriptomics reveals regulators underlying immune cell diversity and immune subtypes associated with prognosis in nasopharyngeal carcinoma

照例,请出我们的pbmc3k数据:

library(Seurat)
library(SeuratData)
pbmc3k

An object of class Seurat 
13714 features across 2700 samples within 1 assay 
Active assay: RNA (13714 features, 0 variable features)

head(pbmc3k@meta.data)
               orig.ident nCount_RNA nFeature_RNA seurat_annotations
AAACATACAACCAC     pbmc3k       2419          779       Memory CD4 T
AAACATTGAGCTAC     pbmc3k       4903         1352                  B
AAACATTGATCAGC     pbmc3k       3147         1129       Memory CD4 T
AAACCGTGCTTCCG     pbmc3k       2639          960         CD14+ Mono
AAACCGTGTATGCG     pbmc3k        980          521                 NK
AAACGCACTGGTAC     pbmc3k       2163          781       Memory CD4 T

可以看出已经注释好了的,接下来我们写出跑cellphonedb的文件:表达谱和细胞分组文件。

 write.table(as.matrix(pbmc3k@assays$RNA@data), 'cellphonedb_count.txt', sep='\t', quote=F)
 meta_data <- cbind(rownames(pbmc3k@meta.data), pbmc3k@meta.data[,'seurat_annotations', drop=F])  
meta_data <- as.matrix(meta_data)
  meta_dat[is.na(meta_data)] = "Unkown" #  细胞类型中不能有NA

 write.table(meta_data, 'cellphonedb_meta.txt', sep='\t', quote=F, row.names=F)

假设诸君已经安装好cellphonedb ,并加入环境变量了,我们可以这样跑:

cellphonedb method statistical_analysis  cellphonedb_meta.txt  cellphonedb_count.txt      --counts-data=gene_name  # 如果是直接写出基因名的加这个参数,转化为基因ID的话不用加。

#cellphonedb 自己的绘图
cellphonedb plot dot_plot 
cellphonedb plot heatmap_plot cellphonedb_meta.txt   

cellphonedb 结果文件需要挨个打开看看,不要只看名字。更多细节可以看官网的介绍,记住,官网对你是开放的。

https://www.cellphonedb.org/documentation

ree out/
out/
├── count_network.txt  # 绘制网络图文件  ; 注意 这个文件是在cellphonedb plot  中生成的,所以上面两个plot 不要漏掉了。
├── deconvoluted.txt
├── heatmap_count.pdf
├── heatmap_log_count.pdf
├── interaction_count.txt
├── means.txt # 绘制点图文件
├── plot.pdf
├── pvalues.txt  # 绘制点图文件 
└── significant_means.txt

下面在R中对结果进行可视化演练,主要是网络图和点图。点图我们知道ggplot的同学都不陌生了,就是网络图一看就让人头大,好在之前你们家运来小哥哥抄完了网络数据的统计分析:R语言实战一书,算是知道些皮毛,可以在这里用上啦。

pbmc='D:\\SingleCell\\out\\' ##  outs 文件放在这里了。

library(psych)
library(qgraph)
library(igraph)
library(tidyverse)


netf<- "count_network.txt"
mynet <- read.delim(paste0(pbmc,"count_network.txt"), check.names = FALSE)
table(mynet$count)
mynet %>% filter(count>0) -> mynet  # 有零会报错
head(mynet)
        SOURCE       TARGET count
1 Memory CD4 T Memory CD4 T     3
2 Memory CD4 T            B    10
3 Memory CD4 T   CD14+ Mono    14
4 Memory CD4 T           NK    15
5 Memory CD4 T        CD8 T     6
6 Memory CD4 T  Naive CD4 T     3

net<- graph_from_data_frame(mynet)

plot(net)

读入网络数据,构建网络,并绘制最简单的一张网络图:

为了给这个网络图的边点mapping上不同的属性我们引入一串颜色。

allcolour=c("#DC143C","#0000FF","#20B2AA","#FFA500","#9370DB",
            "#98FB98","#F08080","#1E90FF","#7CFC00","#FFFF00",
            "#808000","#FF00FF","#FA8072","#7B68EE","#9400D3",
            "#800080","#A0522D","#D2B48C","#D2691E","#87CEEB",
            "#40E0D0","#5F9EA0","#FF1493",
            "#FFE4B5","#8A2BE2","#228B22","#E9967A","#4682B4",
            "#32CD32","#F0E68C","#FFFFE0","#EE82EE","#FF6347",
            "#6A5ACD","#9932CC","#8B008B","#8B4513","#DEB887")


karate_groups <- cluster_optimal(net)
coords <- layout_in_circle(net, order =
                             order(membership(karate_groups)))  # 设置网络布局

E(net)$width  <- E(net)$count/10  # 边点权重(粗细)
plot(net, edge.arrow.size=.1, 
     edge.curved=0,
     vertex.color=allcolour,
     vertex.frame.color="#555555",
     vertex.label.color="black",
     layout = coords,
     vertex.label.cex=.7) 

经过以上一番修饰后,我们得到的网络图如下:

但是边的颜色和点的颜色还是对应不上,我们来修改一番边的属性。

net2 <- net  # 复制一份备用

for (i in 1: length(unique(mynet$SOURCE)) ){
  E(net)[map(unique(mynet$SOURCE),function(x) {
    get.edge.ids(net,vp = c(unique(mynet$SOURCE)[i],x))
  })%>% unlist()]$color <- allcolour[i]
}  # 这波操作谁有更好的解决方案? 

plot(net, edge.arrow.size=.1, 
     edge.curved=0,
     vertex.color=allcolour,
     vertex.frame.color="#555555",
     vertex.label.color="black",
     layout = coords,
     vertex.label.cex=.7) 

至此,文章开头的第一张图就复现出来了。我们观察到由于箭头是双向的,所以两个细胞类型之间的线条会被后来画上去的覆盖掉(开头那张网络图也有这个问题吗?),这里我们把线条做成曲线:

plot(net, edge.arrow.size=.1, 
     edge.curved=0.2, # 只是调了这个参数
     vertex.color=allcolour,
     vertex.frame.color="#555555",
     vertex.label.color="black",
     layout = coords,
     vertex.label.cex=.7) 

这下清晰很多了。

接下来,我们来绘制第二组类型贝壳一样的网络图。

dev.off()
length(unique(mynet$SOURCE)) # 查看需要绘制多少张图,以方便布局
par(mfrow=c(2,5), mar=c(.3,.3,.3,.3))

for (i in 1: length(unique(mynet$SOURCE)) ){
  net1<-net2
  E(net1)[map(unique(mynet$SOURCE),function(x) {
    get.edge.ids(net,vp = c(unique(mynet$SOURCE)[i],x))
  })%>% unlist()]$color <- allcolour[i]
  
  plot(net1, edge.arrow.size=.1, 
       edge.curved=0.4,
       vertex.color=allcolour,
       vertex.frame.color="#555555",
       vertex.label.color="black",
       layout = coords,
       vertex.label.cex=1) 
  
}

这样是不是清晰了许多呢?

但是,细胞间通讯的频数(count)并没有绘制在图上,略显不足,这就补上吧。

dev.off()
length(unique(mynet$SOURCE))
par(mfrow=c(2,5), mar=c(.3,.3,.3,.3))

for (i in 1: length(unique(mynet$SOURCE)) ){
  net1<-net2
  
  E(net1)$count <- ""
  E(net1)[map(unique(mynet$SOURCE),function(x) {
    get.edge.ids(net,vp = c(unique(mynet$SOURCE)[i],x))
  })%>% unlist()]$count  <- E(net2)[map(unique(mynet$SOURCE),function(x) {
    get.edge.ids(net,vp = c(unique(mynet$SOURCE)[i],x))
  })%>% unlist()]$count  # 故技重施
  
  E(net1)[map(unique(mynet$SOURCE),function(x) {
    get.edge.ids(net,vp = c(unique(mynet$SOURCE)[i],x))
  })%>% unlist()]$color <- allcolour[i]
  
  plot(net1, edge.arrow.size=.1, 
       edge.curved=0.4,
       edge.label = E(net1)$count, # 绘制边的权重
       vertex.color=allcolour,
       vertex.frame.color="#555555",
       vertex.label.color="black",
       layout = coords,
       vertex.label.cex=1
  ) 
  
}

需要再做一些调整就可以啦。

找两条边验证一下对应关系。

mynet %>% filter(SOURCE  == 'B',TARGET == 'DC')
  SOURCE TARGET count
1      B     DC    18

 mynet %>% filter(SOURCE  == 'Memory CD4 T',TARGET == 'B')
        SOURCE TARGET count
1 Memory CD4 T      B    10

用ggplot2 绘制点图,关键是把两张表合并到一起。

mypvals <- read.delim(paste0(pbmc,"pvalues.txt"), check.names = FALSE)
mymeans <- read.delim(paste0(pbmc,"means.txt"), check.names = FALSE)



# 这些基因list很有意思啊,建议保存
chemokines <- grep("^CXC|CCL|CCR|CX3|XCL|XCR", mymeans$interacting_pair,value = T)
chemokines <- grep("^CXC|CCL|CCR|CX3|XCL|XCR", mymeans$interacting_pair,value = T)
th1 <- grep("IL2|IL12|IL18|IL27|IFNG|IL10|TNF$|TNF |LTA|LTB|STAT1|CCR5|CXCR3|IL12RB1|IFNGR1|TBX21|STAT4", 
            mymeans$interacting_pair,value = T)
th2 <- grep("IL4|IL5|IL25|IL10|IL13|AREG|STAT6|GATA3|IL4R", 
            mymeans$interacting_pair,value = T)
th17 <- grep("IL21|IL22|IL24|IL26|IL17A|IL17A|IL17F|IL17RA|IL10|RORC|RORA|STAT3|CCR4|CCR6|IL23RA|TGFB", 
             mymeans$interacting_pair,value = T)
treg <- grep("IL35|IL10|FOXP3|IL2RA|TGFB", mymeans$interacting_pair,value = T)
costimulatory <- grep("CD86|CD80|CD48|LILRB2|LILRB4|TNF|CD2|ICAM|SLAM|LT[AB]|NECTIN2|CD40|CD70|CD27|CD28|CD58|TSLP|PVR|CD44|CD55|CD[1-9]", 
                      mymeans$interacting_pair,value = T)
coinhibitory <- grep("SIRP|CD47|ICOS|TIGIT|CTLA4|PDCD1|CD274|LAG3|HAVCR|VSIR", 
                     mymeans$interacting_pair,value = T)
niche <- grep("CSF", mymeans$interacting_pair,value = T)


mymeans %>% dplyr::filter(interacting_pair %in% costimulatory)%>%
  dplyr::select("interacting_pair",starts_with("NK"),ends_with("NK"))  %>%  
  reshape2::melt() -> meansdf

colnames(meansdf)<- c("interacting_pair","CC","means")

mypvals %>% dplyr::filter(interacting_pair %in% costimulatory)%>%
  dplyr::select("interacting_pair",starts_with("NK"),ends_with("NK"))%>%  
  reshape2::melt()-> pvalsdf

colnames(pvalsdf)<- c("interacting_pair","CC","pvals")
pvalsdf$joinlab<- paste0(pvalsdf$interacting_pair,"_",pvalsdf$CC)
meansdf$joinlab<- paste0(meansdf$interacting_pair,"_",meansdf$CC)
pldf <- merge(pvalsdf,meansdf,by = "joinlab")

summary((filter(pldf,means >1))$means)

pldf%>% filter(means >1) %>% 
  ggplot(aes(CC.x,interacting_pair.x) )+ 
  geom_point(aes(color=means,size=-log10(pvals+0.0001)) ) +
  scale_size_continuous(range = c(1,3))+
  scale_color_gradient2(high="red",mid = "yellow",low ="darkblue",midpoint = 25  )+ theme_bw()+ 
  theme(axis.text.x = element_text(angle = -45,hjust = -0.1,vjust = 0.8)) 

这部分写的略显臃肿,勉强可用吧。


番外篇:在不能用--counts-data=gene_name时,我们可以如何转化基因ID呢。这里提供一种思路:用Seurat读两次数据,一次是基因SYMBOL,一次是ENTREZID(cellranger提供的便利)。方法是从10X的输出中直接读取带有ENTREZID的矩阵,如下从这个矩阵中匹配出目标矩阵。

读入数据的时候gene.column = 1默认为2(SYMBOL)。

Read10X(
  data.dir = NULL,
  gene.column = 1,
  unique.features = TRUE,
  strip.suffix = FALSE
)
seurat2cellphonedb<- function(seosym,seo,groups,group1,celltype){
  Idents(seo) <- groups
  myseogroup <- subset(seo,idents =group1)
  myseogroup1 <-  subset(seosym,cells=colnames(myseogroup ))
  count_raw <- myseogroup1@assays$RNA@counts
  count_norm <- apply(count_raw, 2, function(x) (x/sum(x))*10000)
  write.table(count_norm, paste0(group1,"_cellphonedb_count.txt"), sep='\t', quote=F)
  meta_data <- cbind(rownames(myseogroup@meta.data),myseogroup@meta.data[,celltype, drop=F])
  write.table(meta_data, paste0(group1,'_cellphonedb_meta.txt'), sep='\t', quote=F, row.names=F)
}

$cellphonedb method statistical_analysis   cseocellphonedb_meta.txt  C_cellphonedb_count.txt

$cellphonedb plot dot_plot
$cellphonedb plot heatmap_plot yourmeta.txt

https://www.cellphonedb.org/faq-and-troubleshooting
https://github.com/Teichlab/cellphonedb/blob/master/cellphonedb/src/plotters/R/plot_heatmaps.R
https://github.com/zktuong/ktplots/

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

推荐阅读更多精彩内容