StringTie + DESeq2 进行 RNA-seq 差异基因分析

按:RNA-seq 流程非常多样化,每一个步骤可选工具都非常多。我的选择是 StringTie 进行组装取得 read counts 数值,DESeq2 分析差异基因。这里把我方法跟大家共享。一来给新手一个指引,如果你毫无相关经验可以照着我这个出结果;二来抛砖引玉,如果你有相关经验欢迎一起交流进步。

总体流程

如流程图所示,总体上有三大步骤。第一,用 stringtie 组装取得每个样本结果 gtf 文件;第二,用 StringTie 提供的 prepDE.py 脚本从所有(需要)样本提取 read counts 数值并以矩阵格式存储到一个 csv 文件;第三,用 DESeq2 进行差异基因分析。


总体流程

StringTie

StringTie 的输入文件是排序好的 BAM 格式文件,所以比对得到 sam 文件后用 samtools 进行 sam->bam 格式转换,并对 bam 进行排序和建立索引文件。组装需要一个基因组注释文件,推荐去 genecode 下载。网址 GENCODE - Human Release 33。如下图所示下载 gtf/gff3 格式。

下载GTF/GFF3基因组注释文件

示例 stringtie 命令如下:

stringtie /Example/Bam/PC3_NC_1_sorted.bam -G /Example/GRCh38_hg38/gencode.v32.chr_patch_hapl_scaff.annotation.gff3 -o /Example/GTF/PC3_NC_1/PC3_NC_1.gtf -A /Example/GTF/PC3_NC_1/PC3_NC_1_abundance.txt -p 4 -B -e

其中 -G 参数指定基因组注释文件, -o 输出的 gtf 路径, -e 参数限定分析基因组注释文件转录本,也就是说只分析已知转录本,不分析新转录本。 -B 是产生文件给下游 Ballgown 使用,我们不使用 Ballgown 的话可以不需要指定。得到 gtf 后用 prepDE.py 转换到 read counts 矩阵。 prepDE.py 输入是目录路径,或者提供一个文件,文件里是你需要的 gtf 文件路径。像我当前目录包含了需要的 gtf 文件。那么输入当前目录路径给 prepDE.py 即可。

$find . -name "*.gtf"
./PC3_si730_2/PC3_si730_2.gtf
./PC3_si730_1/PC3_si730_1.gtf
./PC3_NC_2/PC3_NC_2.gtf
./PC3_NC_1/PC3_NC_1.gtf
./PC3_NC_3/PC3_NC_3.gtf
./PC3_si730_3/PC3_si730_3.gtf

假设就在当前目录运行,示例命令如下:

python prepDE.py -i . -l 150

其中 -l 是设置平均 reads 长度。 prepDE.py 计算 read counts 代码如下,read_len 就是 -l 参数。可见这个参数需要按实际实验去设置,默认值是75.

int(ceil(coverage*transcript_len/read_len)

运行后得到 gene_count_matrix.csv 和 transcript_count_matrix.csv 两个文件,其中 gene_count_matrix.csv 就是需要的基因 read counts 矩阵。查看一下。

gene_id,PC3_NC_1,PC3_NC_2,PC3_NC_3,PC3_si730_1,PC3_si730_2,PC3_si730_3
ENSG00000255427.1|LINC02707,0,0,0,0,0,0
ENSG00000227189.2|AC092535.2,0,2,0,5,0,0
ENSG00000244171.4|PBX2P1,1782,1921,1717,1320,1829,1704
ENSG00000122188.13|LAX1,3,3,0,3,0,0
ENSG00000225415.2|ACKR4P1,0,0,0,0,0,0
ENSG00000141736.13|ERBB2,4397,4340,3813,3544,4978,4346
ENSG00000284348.1|AC245128.33,0,0,0,0,0,0
ENSG00000273728.1|Metazoa_SRP,0,0,0,0,0,0

可见其 gene_id 是混合了 ensembl 和其他基因名,用 | 分隔,后续 DESeq2 流程我们需要根据这个进行相应处理。关于 ensembl id 可以查看我之前文章《Ensembl基因ID格式》了解。

DESeq2

DESeq2 是一个 R 包,每个人 R 代码习惯不同,所以下面每一步我都会展示结果/数据, 你不需要跟着我代码写,只要能做到相同数据结构就行。 DESeq2 是非常复杂强大的包,建议去官网把教程至少看一遍。

第一步先导入需要的 R 包。其中 biomaRt 是用于基因ID转换,包使用方法参见我以前文章《批量转换基因ID》。

library(tidyverse, quietly = TRUE)
library(DESeq2, quietly = TRUE)
library(biomaRt, quietly = TRUE)

DESeq2 的输入是 read counts 矩阵和样品信息表,样品信息主要是样品分组信息。在我这里是个简单的敲低实验,其中3个对照组3个实验组,分析要求是取得 实验组 vs 对照组 差异基因。我的样品信息就存在这样文件里:

$head SampleGroup.tsv
    Group
PC3_NC_1    Control
PC3_NC_2    Control
PC3_NC_3    Control
PC3_si730_1 Treatment
PC3_si730_2 Treatment
PC3_si730_3 Treatment

使用下面代码将表格读取进 R:

sampleGroup <- read.table("/Example/SampleGroup.tsv", header = TRUE)
sampleGroup$Group <- factor(sampleGroup$Group, levels = c("Control", "Treatment"))
print(sampleGroup)

这里我特意把分组列转换到因子,并且设定第一个因子水平为 Control. 这是用 R 的好习惯,在 R 语言里如果涉及到排序,往往默认按因子水平排。如果你的列表不是因子,会自动 as.factor 进行转换,采用默认的因子水平。返回 DESeq2 这里,把 Control 设为第一个因子,那么后面的结果就是 其他组 vs 对照组。在我这个例子里就是 Treatment vs Control 。假如说你有2组 A 和 C , C 是你的对照组,你希望是 A vs C 。如果你不设定 C 为第一个因子,那 DESeq2 默认 A 为第一个因子,结果就是 C vs A 。

使用下面代码读取 read counts 矩阵。

readCount <- read_csv("/Example/GTF/gene_count_matrix.csv") %>% tidyr::separate(col = gene_id, into = c("ensembl_gene_id", "gene_name"), sep = "\\|", remove = TRUE) %>% dplyr::select(ensembl_gene_id, rownames(sampleGroup)) %>% dplyr::mutate(ensembl_gene_id = map_chr(ensembl_gene_id, geneID)) %>% as.data.frame()
rownames(readCount) <- readCount$ensembl_gene_id
readCount$ensembl_gene_id <- NULL
print(head(readCount, n = 3))

这里 StringTie 给的结果基因ID是 ENSG00000214428.3|NPM1P10 这样的格式,其中竖线右边基因名是杂乱的,为了方便处理我把这部分移除了,只保留 Ensembl ID: ENSG00000214428 。后面再用 biomaRt 转换到 hgnc_symbol 。总而言之,处理后得到下面的数据框,这就是 DESeq2 需要的输入格式。要注意列名顺序跟样品信息 sampleGroup 的行名顺序是一致的。

                PC3_NC_1 PC3_NC_2 PC3_NC_3 PC3_si730_1 PC3_si730_2 PC3_si730_3
ENSG00000255427        0        0        0           0           0           0
ENSG00000227189        0        2        0           5           0           0
ENSG00000244171     1663     1793     1602        1232        1707        1591

刚刚说了要用 biomaRt 进行基因ID转换,下面是代码和结果示例,详情参见《批量转换基因ID》文章。

# 其中 is.na(hgnc_symbol) 或许应该替换为 hgnc_symbol == ""
ensembl <- useMart("ensembl", dataset="hsapiens_gene_ensembl")
geneMap <- getBM(attributes = c("ensembl_gene_id", "hgnc_symbol", "entrezgene_id"), filters = "ensembl_gene_id", values = rownames(readCount), mart = ensembl) %>% dplyr::filter(!(is.na(hgnc_symbol) & is.na(entrezgene_id))) %>% dplyr::distinct(ensembl_gene_id, .keep_all = TRUE)
print(head(geneMap, n = 3))

# 得到如下表格
  ensembl_gene_id hgnc_symbol entrezgene_id
1 ENSG00000010404         IDS          3423
2 ENSG00000013293     SLC7A14         57709
3 ENSG00000029534        ANK1           286

使用下面代码将数据导入 DESeq2 。

dds <- DESeqDataSetFromMatrix(countData = readCount, colData = sampleGroup, design = ~ Group)
keep <- rowSums(counts(dds)) > 12
dds <- dds[keep, ]
dds <- DESeq(dds)
print(resultsNames(dds))

# 打印输出
[1] "Intercept"                  "Group_Treatment_vs_Control"

函数 DESeqDataSetFromMatrix 将 read counts 矩阵和样品信息导入到 DESeq2 。其中 design 参数设定实验设计,也即那一列信息用于分组比较(也包括批次效应或其他因素,在我这例子里没有)。后面的 keep 是移除低 read counts 基因,这里也没有一个数值标准,看自己想法。我这里是要求每个基因平均 read counts > 2 ,我有6样本所以要求总 read counts > 12 。只保留符合条件的基因。像 edgeR 的话是推荐按照 cpm(count-per-million) 进行筛选而不是直接对 read counts 筛选,比如 keep <- rowSums(cpm(dds)>1) >= 2 也可以参考一下这个方法。

results 函数取的差异基因结果,这个结果包含所有基因。

res <- results(dds, name = "Group_Treatment_vs_Control") %>% as_tibble(rownames = "ensembl_gene_id") %>% dplyr::left_join(geneMap, by = "ensembl_gene_id") %>% dplyr::select(ensembl_gene_id, hgnc_symbol, entrezgene_id, everything())
print(head(res, n = 3))

name 参数选择需要的比较组,也可以用 contrast 参数设置比较组。results 函数有个 lfcThreshold 参数跟P值计算直接相关,默认是0所以检验的是基因表达有无差异,如果你想检验基因表达是否差2倍以上,可以设为1。同时我们把基因ID匹配上去。得到差异基因结果如下:

# A tibble: 3 x 9
  ensembl_gene_id hgnc_symbol entrezgene_id baseMean log2FoldChange lfcSE   stat
  <chr>           <chr>               <int>    <dbl>          <dbl> <dbl>  <dbl>
1 ENSG00000244171 PBX2P1                 NA   1583.         -0.123  0.118 -1.04 
2 ENSG00000141736 ERBB2                2064   3916.          0.0734 0.105  0.702
3 ENSG00000268324 LRRC2-AS1              NA     15.8         1.20   0.860  1.40 
# … with 2 more variables: pvalue <dbl>, padj <dbl>

得到结果后可以根据自己需要设置条件筛选,比如:

degFilter <- dplyr::filter(res, abs(log2FoldChange) >= 1 & padj < 0.05)

DESeq2 提供了log2差异倍数(log2 fold change)收缩方法 lfcShrink ,跟原始的 results 取的结果相比会把差异倍数进行一定缩小。 lfcShrink 官方文档如下:

Beginning with the first row, all shrinkage methods provided by DESeq2 are good for ranking genes by “effect size”, that is the log2 fold change (LFC) across groups, or associated with an interaction term. It is useful to contrast ranking by effect size with ranking by a p-value or adjusted p-value associated with a null hypothesis: while increasing the number of samples will tend to decrease the associated p-value for a gene that is differentially expressed, the estimated effect size or LFC becomes more precise. Also, a gene can have a small p-value although the change in expression is not great, as long as the standard error associated with the estimated LFC is small.

总体而言缩放数据更适合去可视化以及进行排序等,像我们后面需要进行GSEA分析,那么可以采用这个数据。有3种缩放方法,官方给出下面总结:


3方法比较

示例代码:

deg <- lfcShrink(dds, coef = "Group_Treatment_vs_Control", type = "apeglm") %>% as_tibble(rownames = "ensembl_gene_id") %>% dplyr::left_join(geneMap, by = "ensembl_gene_id") %>% dplyr::select(ensembl_gene_id, hgnc_symbol, entrezgene_id, everything())
print(head(deg, n = 3))

# 打印输出
# A tibble: 3 x 8
  ensembl_gene_id hgnc_symbol entrezgene_id baseMean log2FoldChange   lfcSE
  <chr>           <chr>               <int>    <dbl>          <dbl>   <dbl>
1 ENSG00000244171 PBX2P1                 NA   1583.     -0.0000311  0.00144
2 ENSG00000141736 ERBB2                2064   3916.     -0.00000352 0.00144
3 ENSG00000268324 LRRC2-AS1              NA     15.8     0.00000157 0.00144
# … with 2 more variables: pvalue <dbl>, padj <dbl>

除了差异基因数据,往往我们还需要表达数据用于其他分析。 DESeq2 提供 rlog 函数将原始 read counts 转换为 log2 后值并且进行调整。得到的矩阵适合后续机器学习相关分析,比如聚类、热图展示等。如果你样本数很多,建议使用 vst 函数,会快非常多。代码示例。

rld <- rlog(dds)
rldt <- assay(rld) %>% as_tibble(rownames = "ensembl_gene_id") %>% dplyr::left_join(geneMap, by = "ensembl_gene_id") %>% dplyr::select(ensembl_gene_id, hgnc_symbol, entrezgene_id, everything())
print(head(rldt, n = 3))

# 输出
# A tibble: 3 x 9
  ensembl_gene_id hgnc_symbol entrezgene_id PC3_NC_1 PC3_NC_2 PC3_NC_3
  <chr>           <chr>               <int>    <dbl>    <dbl>    <dbl>
1 ENSG00000244171 PBX2P1                 NA    10.6     10.7     10.7 
2 ENSG00000141736 ERBB2                2064    11.9     11.9     11.9 
3 ENSG00000268324 LRRC2-AS1              NA     3.84     3.84     3.87
# … with 3 more variables: PC3_si730_1 <dbl>, PC3_si730_2 <dbl>,
#   PC3_si730_3 <dbl>

使用 counts 函数取的 normalized 后 read counts 数据,记得参数 normalized = TRUE .

nReadCount <- counts(dds, normalized = TRUE) %>% as_tibble(rownames = "ensembl_gene_id") %>% dplyr::left_join(geneMap, by = "ensembl_gene_id") %>% dplyr::select(ensembl_gene_id, hgnc_symbol, entrezgene_id, everything())
print(head(nReadCount, n = 3))

# 输出
# A tibble: 3 x 9
  ensembl_gene_id hgnc_symbol entrezgene_id PC3_NC_1 PC3_NC_2 PC3_NC_3
  <chr>           <chr>               <int>    <dbl>    <dbl>    <dbl>
1 ENSG00000244171 PBX2P1                 NA  1575.    1678.     1698. 
2 ENSG00000141736 ERBB2                2064  3888.    3789.     3772. 
3 ENSG00000268324 LRRC2-AS1              NA     8.53     8.42     11.7
# … with 3 more variables: PC3_si730_1 <dbl>, PC3_si730_2 <dbl>,
#   PC3_si730_3 <dbl>

参考

StringTie
Analyzing RNA-seq data with DESeq2

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

推荐阅读更多精彩内容