easyqiafan

Update @2022-01-15

为了极致偷懒,因此将关于TCGA分析的这些脚本统合在一起,TCGA数据本身非常规整,各种信息的列名这些都是固定的,因此实际上可以用固定的分析流程来做。主要是想减少复制粘贴代码,有些时候实在太麻烦了,改到最后都不知道改的是哪个脚本了。

这次更新聚类篇,使用ConsensusClusterPlus和NMF来进行一步式分析,直接得到分析结果和分组表,实际上非常简单就是了。

file:getClusterAll.R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
doconsensus <- function(exprset,genelist,maxk = 10){
library(ConsensusClusterPlus)
path = "./consensusgroup/"
method = "pam"
distance = "pearson"
dir.create(path)

#以防万一需要重构group_list
group_list <- tinyarray::make_tcga_group(exprset)
index <- which(group_list == "tumor")
cluster_exprset <- exprset[,index]

cluster_exprset <- cluster_exprset %>% filter(.,row.names(.) %in% all_of(genelist))
write.csv(cluster_exprset,file = paste(path,"cluster_exprset",".csv",sep = ""))

#做一下中心化
d = as.matrix(exprset)
d = sweep(d,1, apply(d,1,median))

res <- ConsensusClusterPlus(d = d,
maxK = maxk,
pItem = 0.8,
pFeature = 1,
clusterAlg = method,
title = paste(path,method,distance,sep = ""),
distance = distance,
seed = 20010320,
plot = "pdf",
writeTable = TRUE,
verbose = FALSE)

return(res)
detach(package:ConsensusClusterPlus)

lapply(2:maxk, function(x){
grouped <- as.data.frame(res[[x]]$consensusClass)
names(grouped) <- "subgroup"
write.csv(pameucgrouped,file = paste(path,method,distance,x,".csv",sep = ""))
})

print("ConsensusCluster Complete! Group file saved!")

}


donmf <- function(exprset,genelist,maxk = 10){
path = "./nmfgroups/"
dir.create(path)

#以防万一需要重构group_list
group_list <- tinyarray::make_tcga_group(exprset)
index <- which(group_list == "tumor")
cluster_exprset <- exprset[,index]
cluster_exprset <- cluster_exprset %>% filter(.,row.names(.) %in% all_of(genelist))
write.csv(cluster_exprset,file = paste(path,"cluster_exprset",".csv",sep = ""))

if(max(cluster_exprset)>100){cluster_exprset <- log2(cluster_exprset + 1)}
#nmf用归一化,防止负数
normalize <- function(x){return((x-min(x))/(max(x)-min(x)))}
cluster_exprset <- normalize(cluster_exprset)

estimate <- NMF::nmf(cluster_exprset,rank=2:maxk,method="brunet",nrun=10,seed=20010320)

pdf('NMF-rank.pdf',width = 12,height = 12)
plot(estimate)
dev.off()

pdf('nmfconsensusmap.pdf',width = 20,height = 20,onefile = T)
NMF::consensusmap(estimate,annRow = NA,annCol = NA,main = "Consensus matrix",info = FALSE)
dev.off()

lapply(2:maxk,function(x){
estimate <- NMF::nmf(cluster_exprset,rank = x,seed = 20010320,method = "brunet")

nmfgroup <- predict(estimate)
nmfgroup <- as.data.frame(nmfgroup)
nmfgroup$nmfgroup <- paste0('Cluster',nmfgroup$nmfgroup)
nmfgroup$sample <- rownames(nmfgroup)
nmfgroup<- nmfgroup[order(nmfgroup$nmfgroup),]
write.csv(nmfgroup,file = paste(path,x,"groups","csv",sep = ""))
})

print("NMF Complete! Group file saved!")
}

这样只要输入TCGA/GTEx的表达矩阵,和感兴趣的基因列表,以及预期的聚类数,就能直接获得聚类的结果和全部分群的文件啦。而且还支持自定义方法类型,只需要自行修改想要的方法就行了。

Update @2022-01-19

说好的第二更,此次更新关于差异分析,这是一个最最最常见的需求,无论是使用三大差异分析包(limma-voom,DESeq2,edgeR)还是大家手写统计学检验,都是一个非常常见的需求,这次更新DESeq2的整合分析方案,主要输出差异分析的最核心结果+图,包括PCA图,Enhance火山图和基因表达热图,差异分析结果表格以及分组表。DESeq2要求输入为count,而且得是整数和非负,将一些预处理整合进去了。

由于一些显而易见的原因,这个脚本只支持TCGA和GTEx的分析,倒不是GEO和自定义数据没有脚本,纯粹是我懒得更新那个版本。

分析中使用了tinyarray的一些函数功能,特此感谢~,希望大家都去github上star一下这个包,很好用。

顺便我发现stun的hexo主题居然不支持默认数学公式….统计检验里的数学公式写了个寂寞…找时间我研究一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#' 将DESeq2差异分析收归至一个函数中,改为类似cibersort一样的用法
#' 这样的话只需要输入处理好的表达矩阵和metadata就能够直接得到PCA图+DEG结果+火山图+想看的基因的热图
#' 写法也参考了cibersort的脚本,目前只整合了DESeq2的方法,后续找时间整合edgeR和limma
#' 很多函数借用了tinyarray,特此感谢!(tinyarray:https://github.com/xjsun1221/tinyarray)
#' @Author:韩重印
#' @param exprset 表达矩阵,data.frame,样式必须是基因为行名,样本为列名,而且应当提前去掉NA
#' @param metadata data.frame,样式必须为行名为样本名(暂时不需要)
#' @param genelist 准备在火山图和热图中展示的基因列表,一行vector即可
#' @param degtype 准备使用的差异方法,参数为deseq2,limma和edger,注意适用范围

getDEGs <- function(exprset,genelist,degtype = "deseq2"){
#No check packages
library(tidyverse)
library(tinyarray)

if(!(degtype %in% c("deseq2","limma","dger"))){
stop("请输入正确的差异分析方法")
}

#如果出现负数,得处理掉
exprset[exprset<0] <- NA
exprset <- na.omit(exprset)

#创建group_list
group_list <- tinyarray::make_tcga_group(exprset)

#PCA画图要在反log之前画,因为PCA需要标准化的数据,反log之后差异太大了
#pca plot
pca.plot <- draw_pca(exprset,group_list)
pca.plot <- pca.plot + theme_classic()
Cairo::CairoPDF(file = "pcaplot.pdf",width = 10,height = 10)
print(pca.plot)
dev.off()

if(max(exprset)<50){exprset <- ceiling(2^exprset - 1)}

#过滤掉表达量较低的基因,以25% 0值为阈值,这在bulk已经是一个比较严格的标准了。
valid_gene <- apply(exprset,1, function(x){sum(x==0) <= 0.25*ncol(exprset)})
exprset <- exprset[valid_gene,]

colData <- data.frame(row.names = colnames(exprset),condition = group_list)
valid_genes <- dplyr::intersect(genelist,row.names(exprset))

if(!length(valid_genes)){stop("你的基因在表达矩阵里全都不存在")}

if(degtype=="deseq2"){
#使用DESeq2

library(DESeq2)
dds <- DESeqDataSetFromMatrix(
countData = exprset,
colData = colData,
design = ~condition)

dds <- DESeq(dds)
res <- results(dds, contrast = c("condition",rev(levels(group_list))))
resOrdered <- res[order(res$pvalue),]
DEG <- as.data.frame(resOrdered)
DEG <- na.omit(DEG)

detach(package:DESeq2)

logFC_cutoff <- 2

DEG$change = as.factor(
ifelse(DEG$pvalue < 0.05 & abs(DEG$log2FoldChange) > logFC_cutoff,
ifelse(DEG$log2FoldChange > logFC_cutoff ,'UP','DOWN'),'NOT'))
}

genelist_up <- DEG %>% filter(.,change == "UP") %>% filter(.,rownames(.) %in% all_of(genelist))
genelist_down <- DEG %>% filter(.,change == "DOWN") %>% filter(.,rownames(.) %in% all_of(genelist))

print(paste(("总上调表达基因有"),nrow(DEG[DEG$change=="UP",]),sep = ""))
print(paste(("总下调表达基因有"),nrow(DEG[DEG$change =="DOWN",]),sep = ""))
print(paste(("上调表达的重要基因有"),nrow(genelist_up),sep = ""))
print(paste(("下调表达的重要基因有"),nrow(genelist_down),sep = ""))

#数据持久化出来,顺便画几张图
write.csv(DEG,"DESeq2_DEG.csv")
write.csv(group_list,"group_list.csv")
write.csv(colData,"colData.csv")
write.csv(genelist_up,"genelist_up.csv")
write.csv(genelist_down,"genelist_down.csv")

#高级火山图
library(EnhancedVolcano)
E1 <- EnhancedVolcano(DEG,
lab = row.names(DEG),
selectLab = genelist,
x = 'log2FoldChange',
xlab = bquote(~Log[2]~ 'Fold Change'),
y = 'padj',
#ylim = c(0,10),
title = 'Tumor versus Normal',
pCutoff = 10e-2,
FCcutoff = 2,
pointSize = 2.0,
labSize = 2.0,
legendPosition = 'right',
legendLabSize = 12,
drawConnectors = TRUE,
widthConnectors = 0.5,
col = c('grey', 'green', 'darkgreen', 'red'),
colAlpha = 0.5,
colConnectors = 'black')

Cairo::CairoPDF(file = "DEGVolcano.pdf",width = 10,height = 10)
print(E1)
dev.off()
detach(package:EnhancedVolcano)

#指定基因的heatmap
colData <- colData %>%
mutate(colData,id = row.names(colData)) %>%
arrange(colData,desc(condition))

dat <- exprset %>% select(.,all_of(colData$id),everything())
dat_group_list <- colData$condition

#取差异表达的核心基因
cg1 <- rownames(DEG)[DEG$change !="NOT"]
cg1 <- dplyr::intersect(cg1,genelist)

if(length(cg1)==0){
print("你的基因列表里没有差异表达基因,但还是给你画个热图。")
h1 = draw_heatmap(dat[genelist,],
dat_group_list,
cluster_cols = FALSE,
legend = TRUE,
show_rownames = TRUE,
split_column = FALSE,
annotation_legend = TRUE,
n_cutoff = 2)

Cairo::CairoPDF(file = "DEGHeatmap.pdf",width = 10,height = 10)
print(h1)
dev.off()
}

if(length(cg1)>10){
cg1 <- cg1[1:10]
h1 = draw_heatmap(dat[cg1,],
dat_group_list,
cluster_cols = FALSE,
legend = TRUE,
show_rownames = TRUE,
split_column = FALSE,
annotation_legend = TRUE,
n_cutoff = 2)
Cairo::CairoPDF(file = "DEGHeatmap.pdf",width = 10,height = 10)
print(h1)
dev.off()
}else{
h1 = draw_heatmap(dat[cg1,],
dat_group_list,
cluster_cols = FALSE,
legend = TRUE,
show_rownames = TRUE,
split_column = FALSE,
annotation_legend = TRUE,
n_cutoff = 2)
Cairo::CairoPDF(file = "DEGHeatmap.pdf",width = 10,height = 10)
print(h1)
dev.off()
}
TNcolData <- colData
return(TNcolData)

print("File Saved. Unnecessary Packages detached. Proceed To Next Step.")
}

只需要把这个函数写写进.R文件里,然后source就行了,函数输入需要表达矩阵,和一个你准备研究的基因列表,如果不输入基因列表,可以自行去函数里加一个取差异表的排名靠前的基因就行,这里自己写就行了,顺便,你tm连感兴趣的群集都没有还研究个屁。

顺便为啥不用tidybulk?这个框架比较久之前我就看过了,但是跟技能树健明大佬说的一样,这个包其一对DESeq2的支持不佳,其二太多写死的参数。其三,我认为这个包本身是违反tidyverse精神的,形象地说,tidyverse本身就是只给了螺丝,铁块,而工具需要自己组,但是tidybulk的做法本质上和这种脚本化没区别,反正都是写死的,那为啥不直接自己写tidy的分析流程就行了(比如我的这个脚本也有tidy版本的),非要用一个封装得这么死的框架。所以我觉得称其为tidy-like-bulk更好就是了。尽管如此,这个包的工作结果当然很优秀,也有可借鉴性,比如它对数据的一些预处理和检验的部分,很值得我吸收,后面会考虑将部分数据检查的流程加进去~

下一次更新什么呢?

更新一个简单好用的森林图绘制?

还是更新一下全数据获取和预处理?

算了,开摆!

Update @2022-02-18

更新一波:关于如何构建基线表并做基本统计分析。以便迅速找到值得研究的临床特征,使用tableone构建临床样本的基线表+统计信息,提供自定的变量选择与统计分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#使用STAD的临床特征数据
library(UCSCXenaTools)
library(tidyverse)
library(tableone)

survset<-XenaGenerate(subset = XenaCohorts == "GDC TCGA Stomach Cancer (STAD)") %>%
XenaFilter(filterDatasets = "TCGA-STAD.survival.tsv") %>%
XenaQuery() %>%
XenaDownload() %>%
XenaPrepare()

metaset<-XenaGenerate(subset = XenaCohorts == "GDC TCGA Stomach Cancer (STAD)") %>%
XenaFilter(filterDatasets = "TCGA-STAD.GDC_phenotype.tsv") %>%
XenaQuery() %>%
    XenaDownload() %>%
    XenaPrepare()

survset <- as.data.frame(survset)
metaset <- as.data.frame(metaset)

names(metaset)[1] <- "sample"

metaset <- metaset %>% left_join(.,survset,by = "sample")

#理论上实际上这样就可以直接做表,但是应该进行一下筛选,按照低比例筛选留下来的特征

delnabyratiol <- function(df,ratiol = 0.1){
index <- apply(df,2,function(x){ifelse(sum(is.na(x))>ratiol*length(x),FALSE,TRUE)})
tmp <- df[,index]
clean <- tmp[which(complete.cases(tmp)),]
return(clean)
}
cleanmeta = delnabyratiol(metaset,ratiol = 0.1)

#TCGA的meta都是一致的,因此可以共同筛选,如果需要自定义的话要在这里改动。
selectmeta <- c("sample","age_at_initial_pathologic_diagnosis",
"neoplasm_histologic_grade","pathologic_M","pathologic_N","pathologic_T","gender.demographic",
"tumor_stage.diagnoses","sample_type.samples","OS","OS.time")
metadata <- cleanmeta %>% select(.,all_of(selectmeta))

names(metadata) <- c("sample","age","neoplasm_histologic_grade","M","N","T","gender",
"tumor_stage","sample_type","OS","OS.time")

#如果有必要需要设置单独的因子型变量,比如数值变量和因子变量混合的时候。
allVars <- names(metadata)[c(-1,-9)]
catVars <- c("neoplasm_histologic_grade","M","N","T","gender","tumor_stage","OS")
getclintable <-

tab <- CreateTableOne(vars = allVars,
strata = "sample_type",
data = metadata,
factorVars = catVars)
print(tab,formatOptions = list(big.mark = ","))
tab2csv <- print(tab, quote = FALSE, noSpaces = TRUE, printToggle = FALSE)
write.csv(tab2csv, file = "metadata_statics.csv")

#但有时候只想进行描述性统计:
tab2 <- CreateTableOne(vars = allVars, data = metadata, factorVars = catVars)
print(tab2,formatOptions = list(big.mark = ","))
tab22csv <- print(tab2, quote = FALSE, noSpaces = TRUE, printToggle = FALSE)
write.csv(tab22csv, file = "metadata_desc.csv")

原本还打算用 MOVICS的compClinvar来尝试一下,结果贼不好用,目前看来MOVICS对于moic.res这个对象耦合太严重了,如果想调用的话每次都得重新构建这个对象,虽然听上去很理想,一开始就构建一个对象,但是实际操作起来很难搞。

下一次更新从UCSCXenaTools下载数据并组合成moic.res对象,这样方便用MOVICS里面的工具,而且也算是一种简单的数据管理的方式。

另外,Rdata的数据密度比csv大多了,建议以后存储数据都用专门的数据存储格式,少存csv。