R-生物信息学秘籍-全-
R 生物信息学秘籍(全)
原文:
annas-archive.org/md5/38cc3d9f06ec3561cf365e2fe73c5d5a
译者:飞龙
前言
在《R 生物信息学宝典》中,您将通过实际的案例遇到生物信息学领域中常见的以及不太常见的挑战。
本书将采用基于配方的方法,帮助您使用 R 在计算生物学领域进行实际的研究和分析。您将通过分析 Bioconductor
、ggplot
和 tidyverse
库,深入理解您的数据,学习如何在 RNAseq、系统发育学、基因组学和序列分析等领域进行分析。您还将了解如何在生物信息学领域运用机器学习技术。您将发展关键的计算技能,例如在 R Markdown 中开发工作流,并设计您自己的包以便高效且可复现地重用代码。
到本书结束时,您将对生物信息学分析中最重要和最广泛使用的技术有扎实的理解,并掌握处理真实生物数据所需的工具。
本书适合的人群
本书适合数据科学家、生物信息学分析师、研究人员和 R 开发人员,他们希望通过基于配方的方法解决中级到高级的生物学和生物信息学问题。具备 R 编程语言的工作知识以及对生物信息学的基本了解是必须的。
如何最大程度地利用本书
本书要求具备良好的 R 语言和其各类库的知识。
下载示例代码文件
您可以从您的账户下载本书的示例代码文件,网址为 www.packt.com。如果您是从其他地方购买本书,可以访问 www.packtpub.com/support,并注册以便将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
-
登录或在 www.packt.com 注册。
-
选择支持选项卡。
-
点击代码下载。
-
在搜索框中输入书名并按照屏幕上的指示操作。
下载文件后,请确保使用最新版本的以下工具解压文件:
-
适用于 Windows 的 WinRAR/7-Zip
-
适用于 Mac 的 Zipeg/iZip/UnRarX
-
适用于 Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,地址为 github.com/PacktPublishing/R-Bioinformatics-Cookbook
。如果代码有更新,将会在现有的 GitHub 仓库中进行更新。
我们还提供了来自丰富书籍和视频目录的其他代码包,您可以访问 github.com/PacktPublishing/
。快去看看吧!
下载彩色图像
我们还提供了一份 PDF 文件,包含本书中使用的截图/图表的彩色图像。您可以在此下载: www.packtpub.com/sites/default/files/downloads/9781789950694_ColorImages.pdf
。
使用的规范 packtpub.com/…/9781789950694_ColorImages.pdf
本书中使用了多种文本规范。
CodeInText
:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。例如:“我们将讨论如何使用 ape
和 treeio
包将树形数据导入和导出 R。”
代码块如下设置:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
粗体:表示一个新术语、一个重要的词汇,或是您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中会这样显示。示例:“一些依赖项依赖于封装的 Java 代码,因此您需要为您的系统安装一个Java 运行时环境(JRE)。”
警告或重要说明将以这样的形式出现。
小贴士和技巧将以这样的形式出现。
章节
本书中有几个常见的标题(准备工作、如何操作…、它是如何工作的…、还有更多…、另见)。
为了清晰地说明如何完成一个食谱,请按照以下方式使用这些章节:
准备工作
本节告诉您在食谱中需要做的工作,并描述如何设置所需的任何软件或前期设置。
如何操作…
本节包含遵循食谱所需的步骤。
它是如何工作的…
本节通常包含对上一节内容的详细解释。
还有更多…
本节提供有关食谱的附加信息,以帮助您更好地理解食谱。
另见
本节提供指向其他有用信息的链接,帮助您了解食谱。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中注明书名,并通过 customercare@packtpub.com
与我们联系。
勘误:虽然我们已经尽一切努力确保内容的准确性,但错误确实会发生。如果您在本书中发现错误,我们将非常感激您能向我们报告。请访问 www.packtpub.com/support/errata,选择您的书籍,点击“勘误提交表单”链接并输入相关详情。
盗版:如果您在互联网上发现我们作品的非法复制版本,我们将非常感激您能提供该位置地址或网站名称。请通过 copyright@packt.com
与我们联系,并提供相关材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专长,并且有兴趣撰写或贡献书籍,请访问 authors.packtpub.com。
评审
请留下评论。在阅读并使用本书后,为什么不在您购买书籍的网站上留下评论呢?潜在读者可以看到并参考您的公正意见做出购买决策,我们在 Packt 可以了解您对我们产品的看法,我们的作者也能看到您对他们书籍的反馈。谢谢!
欲了解更多关于 Packt 的信息,请访问 packt.com。
第一章:执行定量 RNAseq
RNAseq 技术彻底改变了转录本丰度的研究,带来了高灵敏度检测和高通量分析。使用 RNAseq 数据的生物信息学分析管道通常从读取质量控制步骤开始,接着是将序列比对到参考基因组或将序列读取组装成更长的转录本de novo。之后,通过读取计数和统计模型来估算转录本丰度,并评估样本间的差异表达。自然,整个管道的各个步骤都有多种技术可供选择。质量控制和读取比对步骤通常会在 R 之外进行,因此在 R 中的分析将从转录本或基因注释文件(如 GFF 和 BED 文件)和比对后的读取文件(如 BAM 文件)开始。
R 中的分析工具功能强大且灵活。它们中的许多是 Bioconductor 套件的一部分,因此它们可以很好地集成在一起。研究人员希望通过 RNAseq 回答的核心问题通常是:哪些转录本有差异表达?在本章中,我们将探讨一些标准情况下的分析方法,假设我们已经知道感兴趣基因的基因组位置,并且在需要找到未注释转录本的情况下进行分析。我们还将探讨其他重要的分析方法,帮助回答问题多少重复实验足够?以及哪个等位基因表达更多?
在本章中,我们将涵盖以下分析方法:
-
使用 edgeR 估算差异表达
-
使用 DESeq2 估算差异表达
-
使用 powsimR 进行功效分析
-
使用 GRanges 对象查找未注释的转录区域
-
使用 bumphunter 查找初始显示高表达的区域
-
差异峰值分析
-
使用 SVA 估算批次效应
-
使用 AllelicImbalance 查找等位基因特异性表达
-
绘制和展示 RNAseq 数据
技术要求
你需要的示例数据可以从本书的 GitHub 仓库获取:github.com/PacktPublishing/R-Bioinformatics_Cookbook
.如果你希望按照书中的代码示例使用数据,那么你需要确保这些数据在你工作目录的子目录中。
这里是你需要的 R 包。大多数可以通过install.packages()
安装;其他一些则稍微复杂一些:
-
Bioconductor
-
AllelicImbalance
-
bumphunter
-
csaw
-
DESeq
-
edgeR
-
IRanges
-
Rsamtools
-
rtracklayer
-
sva
-
SummarizedExperiment
-
VariantAnnotation
-
-
dplyr
-
extRemes
-
forcats
-
magrittr
-
powsimR
-
readr
Bioconductor
非常庞大,并拥有自己的安装管理器。你可以通过以下代码安装它:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
更多信息可访问www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接按名称使用其中的函数。这在交互式会话中很有用,但当加载了许多包时,它可能会导致混淆。为了澄清我在某个时刻使用的是哪个包和函数,我偶尔会使用packageName::functionName()
这种约定。
有时,在一个代码片段的中间,我会暂停代码执行,以便你能看到一些中间输出或一个对象的结构,这是理解时很重要的内容。每当这种情况发生时,你会看到一个代码块,其中每一行都以##
(双井号)开头。请看以下命令:
letters[1:5]
这将给我们以下输出:
## a b c d e
请注意,输出行前缀是##
。
使用 edgeR 估计差异表达
edgeR 是一个广泛使用且功能强大的包,它实现了负二项模型,适用于稀疏计数数据(例如 RNAseq 数据),并且是一般线性模型框架中描述和理解计数关系以及多组实验的精确检验的强大工具。它使用了一种加权风格的标准化方法,称为 TMM,这是样本与对照之间对数比值的加权平均值,在去除具有高计数和异常对数比值的基因之后。TMM 值应该接近 1,但它可以作为一个修正因子,应用到整体文库大小的调整中。
在这个食谱中,我们将查看一些从准备注释区域的读取计数到识别基因组中差异表达特征的选项。通常,有一个上游步骤要求我们将高通量序列读取对齐到参考序列,并生成描述这些对齐的文件,例如.bam
文件。准备好这些文件后,我们将启动 R 并开始分析。为了集中精力处理差异表达分析部分,我们将使用一个已经准备好的数据集,其中所有数据都已准备就绪。第八章,与数据库和远程数据源的工作,展示了如果你想了解如何做这一步,可以从原始数据开始到达这一阶段的方法。
由于有许多不同的工具和方法来获取这些读取的对齐信息,我们将从两种常见的输入对象类型开始这个过程。我们将使用一个计数表,就像我们从文本文件加载时会用到的那样,并且我们将使用一个ExpressionSet
(eset
)对象,这是 Bioconductor 中常用的对象类型。
我们准备好的数据集将是来自 NHGRI DNA 元素百科全书项目的modencodefly
数据集,研究的模式生物是果蝇(Drosophila melanogaster)。你可以在www.modencode.org上阅读关于这个项目的更多信息。该数据集包含 147 个D. melanogaster的不同样本,果蝇的基因组约为 110 Mbp,注释了大约 15,000 个基因特征。
准备就绪
数据提供了计数矩阵和 ExpressionSet 对象,你可以查看本书末尾的附录,了解更多关于这些对象类型的信息。数据存储在本书的代码和数据仓库中,网址是 github.com/PacktPublishing/R_Bioinformatics_Cookbook
,位于 datasets/ch1/modencodefly_eset.RData
、datasets/ch1/modencodefly_count_table.txt
和 datasets/ch1/modencodelfy_phenodata.txt
。我们还将使用 edgeR
(来自 Bioconductor)、readr
和 magrittr
库。
如何操作...
我们将展示使用 edgeR 估计差异表达的两种方法。
从计数表中使用 edgeR
对于从计数表(例如文本文件)使用 edgeR 估计差异表达,我们将使用以下步骤:
- 加载计数数据:
count_dataframe <- readr::read_tsv(file.path(getwd(), "datasets", "ch1", "modencodefly_count_table.txt" ))
genes <- count_dataframe[['gene']]
count_dataframe[['gene']] <- NULL
count_matrix <- as.matrix(count_dataframe)
rownames(count_matrix) <- genes
pheno_data <- readr::read_table2(file.path(getwd(), "datasets", "ch1", "modencodefly_phenodata.txt"))
- 指定感兴趣的实验:
experiments_of_interest <- c("L1Larvae", "L2Larvae")
columns_of_interest <- which( pheno_data[['stage']] %in% experiments_of_interest )
- 构建分组因子:
library(magrittr)
grouping <- pheno_data[['stage']][columns_of_interest] %>%
forcats::as_factor()
- 构建计数数据的子集:
counts_of_interest <- count_matrix[,columns_of_interest]
- 创建 DGE 对象:
library(edgeR)
count_dge <- edgeR::DGEList(counts = counts_of_interest, group = grouping)
- 执行差异表达分析:
design <- model.matrix(~ grouping)
eset_dge <- edgeR::estimateDisp(eset_dge, design)
fit <- edgeR::glmQLFit(eset_dge, design)
result <- edgeR::glmQLFTest(fit, coef=2)
topTags(result)
从 ExpressionSet 对象中使用 edgeR
使用 eset
对象通过 edgeR 进行估计可以按照以下步骤完成:
- 加载
eset
数据:
load(file.path(getwd(), "datasets/ch1/modencodefly_eset.RData"))
- 指定感兴趣的实验:
experiments_of_interest <- c("L1Larvae", "L2Larvae")
columns_of_interest <- which( phenoData(modencodefly.eset)[['stage']] %in% experiments_of_interest )
- 构建分组因子:
grouping <- droplevels(phenoData(modencodefly.eset)[['stage']][columns_of_interest] )
- 构建计数数据的子集:
counts_of_interest <- exprs(modencodefly.eset)[, columns_of_interest]
- 创建 DGE 对象:
eset_dge <- edgeR::DGEList(
counts = counts_of_interest,
group = grouping
)
- 执行差异表达分析:
design <- model.matrix(~ grouping)
eset_dge <- edgeR::estimateDisp(eset_dge, design)
fit <- edgeR::glmQLFit(eset_dge, design)
result <- edgeR::glmQLFTest(fit, coef=2)
topTags(result)
如何工作...
我们展示了两种使用 edgeR 估计差异表达的方法。在本教程的前半部分,我们从文本文件中的数据开始,使用了 edgeR。
从计数表中使用 edgeR
在步骤 1中,我们使用 read_tsv()
函数(来自 readr
包)将制表符分隔的计数文本文件加载到一个名为 count_dataframe
的数据框中。然后,从中提取 'gene'
列到一个新变量 genes
,并通过将其赋值为 NULL
从 count_dataframe
中删除。这一切的目的是为了便于使用基础的 as.matrix()
函数将其转换为 count_matrix
矩阵,并将基因信息作为 rownames
添加回来。最后,我们使用 readr read_table2()
函数从文件中加载所需的表型数据。
步骤 2 主要是确定在 count_matrix
中我们想要使用的列。我们定义了一个变量 experiments_of_interest
,它包含了我们想要的列名,然后使用 %in%
运算符和 which()
函数创建一个与列数匹配的二进制向量。如果例如 columns_of_interest
向量的第三列为 TRUE
,则表示该名称在 experiments_of_interest
变量中。
步骤 3 从加载 magrittr
包开始,以获取 %>%
操作符,这将允许管道传输。接着,我们使用 R 索引和二进制 columns_of_interest
因子来选择我们想要的列名,并将其传递给 forcats as_factor()
函数,得到一个因子对象作为我们的分组变量。样本分组信息本质上是一个因子,告诉我们哪些样本是同一样本的重复,这对于实验设计的描述非常重要。我们需要创建一个分组向量,其中每个索引都对应于计数表中的一列。因此,在下面的示例中,数据中的前三列将是同一样本的重复,计数表中的第二组三列将是另一组重复,以此类推。我们可以在分组向量中使用任何符号来表示组。分组向量越复杂,实验设计就可以越复杂。在这里的示例中,我们将使用一个简单的测试/对照设计:
numeric_groups <- c(1,1,1,2,2,2)
letter_groups <- c("A","A","A", "B","B","B")
像这样的简单向量就足够了,但你也可以使用一个因子对象。因子是 R 的分类数据类型,它作为一个整数向量实现,并且有相关联的名称标签,称为水平。当显示因子时,使用的是名称标签,而不是整数。因子对象有某种“记忆”,即使只使用了某些水平,所有可能使用的水平都会被保留,这样在例如将水平作为类别使用时,空的水平仍然可以显示出来。
在 步骤 4 中,我们使用索引提取我们实际想要分析的数据列。
到了 步骤 5,我们的准备工作完成,可以构建我们需要进行差异分析的 DGEList
对象。首先,我们加载 edgeR
库,并对 counts_of_interest
和我们的分组对象使用 DGEList()
函数。
在 步骤 6 中,通过 DGEList
,我们可以进行 edgeR
过程。首先,我们使用基础的 model.matrix()
函数创建实验设计描述符设计对象。模型设计是必要的,它告诉函数如何比较样本;这是 R 中常见的操作,因此有一个基础函数。我们使用我们创建的 grouping
变量。我们必须使用 estimateDisp()
函数估算每个基因的离散度,然后可以在测试中使用这个变异性度量。最后,拟合一个广义线性模型,并使用两次 glmQLFTest()
应用准似然 F 检验,第一次使用离散度估算,eset_dge
,第二次使用结果的 fit
对象。
我们可以使用 topTags()
函数查看差异表达基因的详细信息。我们得到以下输出:
## Coefficient: groupingL2Larvae
## logFC logCPM F PValue FDR
## FBgn0027527 6.318665 11.14876 42854.72 1.132951e-41 1.684584e-37
## [ reached 'max' / getOption("max.print") -- omitted 9 rows ]
列中显示了基因名、基因的 logFC
值、F 值、P 值和 假阳性率 (FDR)。通常,我们想要从中做出统计结论的列是 FDR。
使用 edgeR
从 ExpressionSet 对象中
在步骤 1中,我们将使用从准备好的eset
对象中的edgeR
。我们首先使用基础 R 函数加载该对象,因为它是以标准的 Rdata 格式文件存储的。
在步骤 2中,我们准备感兴趣实验的向量。与步骤 2中类似,唯一不同的是,我们不需要查看从文件创建的pheno_data
对象;相反,我们可以使用eset
函数phenoData()
直接从eset
对象中提取表型数据(请注意,这是eset
与计数矩阵之间的主要区别之一——请参阅本书的附录以获取更多信息)。
在步骤 3中,我们创建分组因子。与之前一样,这可以通过使用phenoData()
提取函数完成,但由于它返回的是一个因子,我们需要使用droplevels()
函数删除未被选中的级别(有关因子对象的简要讨论,请参见前述方法中估计差异表达部分的步骤 3)。
在步骤 4中,我们将提取我们感兴趣的列的数据到标准的矩阵对象中。同样,我们有一个专用函数exprs()
,用于从eset
中提取表达值,并且我们可以使用column_names
进行列索引来对子集进行操作。
在步骤 5中,我们使用DGEList()
构造函数构建edgeR
的数据结构,在步骤 6中,执行分析。这个步骤与第一种方法的步骤 6完全相同。
使用 DESeq2 估计差异表达
DESeq2
包是用于计数数据差异分析的方法,因此它非常适合 RNAseq(以及其他计数类型数据,如ChIPSeq
)。它使用离散度估计和相对表达变化来加强估计和建模,重点提高结果表中的基因排序。DESeq2
与edgeR
的不同之处在于,它使用几何风格的标准化方法,其中每条通道的缩放因子是通过基因计数与其几何均值比率的比值的中位数来计算的,而edgeR
使用加权的标准化因子。两种标准化策略并不互相排斥,并且它们对数据有不同的假设。像任何RNAseq
或大规模实验一样,永远没有“现成的”最佳答案。你将需要测试这些方法,甚至其他方法,并仔细检查控制基因和交叉验证实验的结果,以查看哪种方法表现最佳。性能将很大程度上取决于具体的数据集,因此我们在这里学习的灵活方法将为你如何自行测试不同的解决方案提供一个良好的思路。
我们将在本食谱中讨论的过程与之前食谱 1中的edgeR
过程有些相似。我们可以将 ExpressionSets 和计数表作为输入提供给DESeq2
,当我们准备好这些数据时,我们将有一组不同的函数来将数据转换为DESeqDataSet
,而不是像edgeR
那样使用DGEList
。
准备工作
如同食谱 1中所示,数据以计数矩阵和ExpressionSet
对象的形式提供,你可以在本书的附录部分找到有关这些对象类型的更多信息。数据位于本书的代码和数据仓库:github.com/PacktPublishing/R_Bioinformatics_Cookbook
,路径为datasets/ch1/modencodefly_eset.RData
,datasets/ch1/modencodefly_count_table.txt
,以及datasets/ch1/modencodelfy_phenodata.txt
。再次提醒,我们将使用readr
和magrittr
,以及 Bioconductor 中的SummarizedExperiement
和 DESeq2。
如何操作……
使用 DESeq2 估计差异表达可以通过两种方式进行,如下所示。
从计数矩阵中使用 DESeq2
从计数表(例如,文本文件)中使用 DESeq2 估计差异表达,我们将使用以下步骤:
- 加载计数数据:
count_dataframe <- readr::read_tsv(file.path(getwd(), "datasets", "ch1", "modencodefly_count_table.txt" ))
genes <- count_dataframe[['gene']]
count_dataframe[['gene']] <- NULL
count_matrix <- as.matrix(count_dataframe)
rownames(count_matrix) <- genes
pheno_data <- readr::read_table2(file.path(getwd(), "datasets", "ch1", "modencodefly_phenodata.txt"))
- 指定感兴趣的实验:
experiments_of_interest <- c("L1Larvae", "L2Larvae")
columns_of_interest <- which( pheno_data[['stage']] %in% experiments_of_interest )
- 形成分组因子:
library(magrittr)
grouping <- pheno_data[['stage']][columns_of_interest] %>%
forcats::as_factor()
- 形成计数数据的子集:
counts_of_interest <- count_matrix[,columns_of_interest]
- 构建 DESeq 对象:
library("DESeq2")
dds <- DESeqDataSetFromMatrix(countData = counts_of_interest,
colData = grouping,
design = ~ stage)
- 执行分析:
dds <- DESeq(dds)
- 提取结果:
res <- results(dds, contrast=c("stage","L2Larvae","L1Larvae"))
从 ExpressionSet 对象中使用 DESeq2
要从 ExpressionSet 对象中使用 DESeq2 估计差异表达,我们将使用以下步骤:
- 加载
eset
数据并转换为DESeqDataSet()
:
library(SummarizedExperiment)
load(file.path(getwd(), "datasets/ch1/modencodefly_eset.RData"))
summ_exp <- makeSummarizedExperimentFromExpressionSet(modencodefly.eset)
ddsSE <- DESeqDataSet(summ_exp, design= ~ stage)
- 执行分析并提取结果:
ddsSE <- DESeq(ddsSE)
resSE <- results(ddsSE, contrast=c("stage","L2Larvae","L1Larvae"))
它是如何工作的……
在本食谱的第一部分,我们使用 DESeq1 从文本文件中的数据开始;正如你会注意到的,步骤 1到步骤 4与前一部分完全相同。
从计数矩阵中使用 DESeq2
在步骤 1中,我们使用readr
包中的read_tsv()
函数加载带有制表符分隔的计数文本文件到名为count_dataframe
的数据框中。然后,我们从中提取'gene'
列到一个新变量genes
,并通过赋值NULL
将其从count_dataframe
中删除。这些操作的目的是为了方便我们使用基础的as.matrix()
函数将其转换为count_matrix
矩阵,并将基因信息作为rownames
重新添加进去。最后,我们使用readr
的read_table2()
函数从文件中加载所需的表型数据。
步骤 2的任务是确定我们想要在count_matrix
中使用哪些列。我们定义一个变量experiments_of_interest
,保存我们需要的列名,然后使用%in%
操作符和which()
函数创建一个与列数匹配的二进制向量。如果比如columns_of_interest
向量的第三列为'TRUE'
,这表示该列名存在于experiments_of_interest
变量中。
第 3 步 从加载 magrittr
包开始,以获取 %>%
操作符,这将允许管道操作。然后我们使用 R 索引与二进制 columns_of_interest
因子选择我们需要的列名,并将其传递给 forcats
包中的 as_factor()
函数,以获得分组变量的因子对象。样本分组信息基本上是一个因子,告诉我们哪些样本是相同事物的重复,这对于实验设计描述非常重要。你可以在 步骤 3 中的 食谱 1 中看到对这些分组/因子对象的扩展描述。
在 第 4 步 中,我们使用索引提取我们实际需要分析的数据列。
到了 第 5 步,我们进入了实际的分析部分。首先,我们将计数矩阵转换为 DESeqDataSet
对象;这可以通过转换函数 DESeqDataSetFromMatrix()
来完成,传入计数、分组和设计。设计采用 R 公式的形式,因此使用了 ~ stage
注解。
在 第 6 步 中,我们使用 DESeq()
函数对 dds DESeqDataSet
对象进行实际分析,在 第 7 步 中,使用 results()
函数将结果存储到 res
变量中。输出有以下六列:
baseMean log2FoldChange lfcSE stat pvalue padj
这显示了基因的平均计数、样本之间的 log2
fold change、log2
fold change 的标准误差、Wald 统计量,以及原始和调整后的 P 值。padj
列表示调整后的 P 值,通常用于得出显著性的结论。
使用来自 ExpressionSet 对象的 DESeq2
第 1 步 和 第 2 步 显示了如何从 eset
对象开始进行相同的操作。只需两步,因为 DESeq2 在处理 Bioconductor 对象时比 edgeR 更加高效。在 第 8 步 中,我们使用 load()
函数加载 eset
数据。然后我们使用来自 SummarizedExperiment
Bioconductor 包的 makeSummarizedExperimentFromExpressionSet()
函数,将 eset
转换为 SummarizedExperiment
,这可以直接用于 第 9 步 中的 DESeq()
函数。此步骤与 第 6 步 和 第 7 步 完全相同。
使用 powsimR 进行功效分析
任何实验的一个重要前提是评估实验设计的功效,以优化统计敏感性。从本质上讲,功效分析可以告诉我们,在给定实验变异性下,为确定特定效应大小所需的样本重复次数。
我们将使用 powsimR
包,它不属于 Bioconductor,用来进行两种类型的功效分析。这两种分析都使用一个小的真实数据集,但首先,我们将用两种处理方法——测试组和对照组——进行分析,然后,只用一个处理组进行分析。在每个分析中,我们将估计需要多少个重复实验才能发现基因表达的特定差异——如果这些差异存在的话。powsimR
采用基于仿真的方法,实际生成多个数据集并评估每个数据集中的检测功效,最终形成一个功效分布。因此,第一步是为这些仿真估计一些参数——为此,我们需要一些样本数据或初步数据。之后,我们可以运行仿真并评估功效。
准备工作
这个配方的数据集将是来自 Arabidopsis 的一个测试或对照 RNAseq 实验,每组有三个重复。这些数据可以在本书的数据仓库中的 datasets/ch1/arabidopsis.RDS
中找到,作为一个预处理过的计数矩阵。在本节中,我们将使用一个来自 Arabidopsis thaliana 的简单测试或对照实验的计数数据集。该矩阵有六列(三个 mock
处理组和三个 hrcc
处理组),共 26,222 行,每行代表一个基因特征。我们需要 dplyr
、extRemes
和 powsimR
包来运行这段代码。
我们感兴趣的包 powsimR
不在 CRAN 上,它作为源代码托管在 GitHub 上,网址为 github.com/bvieth/powsimR
。你需要使用 devtools
来安装它,以下代码可以完成安装:
install.packages("devtools")
devtools::install_github("bvieth/powsimR")
如果你这样做,仍然有可能会安装失败。它有很多依赖项,你可能需要手动安装这些依赖;在该包的 GitHub 仓库上有更多信息,建议你查看最新的内容。写这篇文档时,你需要做以下两个步骤。首先,创建这里描述的 ipak
函数,然后使用 ipak
函数运行三个不同的包安装步骤:
ipak <- function(pkg, repository = c("CRAN", "Bioconductor", "github")) {
new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])]
# new.pkg <- pkg
if (length(new.pkg)) {
if (repository == "CRAN") {
install.packages(new.pkg, dependencies = TRUE)
}
if (repository == "Bioconductor") {
if (strsplit(version[["version.string"]], " ")[[1]][3] > "3.5.0") {
if (!requireNamespace("BiocManager")) {
install.packages("BiocManager")
}
BiocManager::install(new.pkg, dependencies = TRUE, ask = FALSE)
}
if (strsplit(version[["version.string"]], " ")[[1]][3] < "3.5.0") {
source("https://bioconductor.org/biocLite.R")
biocLite(new.pkg, dependencies = TRUE, ask = FALSE)
}
}
if (repository == "github") {
devtools::install_github(new.pkg, build_vignettes = FALSE, force = FALSE,
dependencies = TRUE)
}
}
}
# CRAN PACKAGES
cranpackages <- c("broom", "cobs", "cowplot", "data.table", "devtools", "doParallel",
"dplyr", "drc", "DrImpute", "fastICA", "fitdistrplus", "foreach", "gamlss.dist",
"ggExtra", "ggplot2", "ggthemes", "grDevices", "glmnet", "grid", "gtools",
"Hmisc", "kernlab", "MASS", "MBESS", "matrixStats", "mclust", "methods",
"minpack.lm", "moments", "msir", "NBPSeq", "nonnest2", "parallel", "penalized",
"plyr", "pscl", "reshape2", "Rmagic", "rsvd", "Rtsne", "scales", "Seurat",
"snow", "stats", "tibble", "tidyr", "VGAM", "ZIM")
ipak(cranpackages, repository = "CRAN")
# BIOCONDUCTOR
biocpackages <- c("AnnotationDbi", "bayNorm", "baySeq", "Biobase", "BiocGenerics",
"BiocParallel", "DEDS", "DESeq2", "EBSeq", "edgeR", "IHW", "iCOBRA", "limma",
"Linnorm", "MAST", "monocle", "NOISeq", "qvalue", "ROTS", "RUVSeq", "S4Vectors",
"scater", "scDD", "scde", "scone", "scran", "SCnorm", "SingleCellExperiment",
"SummarizedExperiment", "zinbwave")
ipak(biocpackages, repository = "Bioconductor")
# GITHUB
githubpackages <- c("nghiavtr/BPSC", "cz-ye/DECENT", "mohuangx/SAVER", "statOmics/zingeR")
ipak(githubpackages, repository = "github")
完成这些步骤后,你应该能够用以下代码安装我们需要的包:
devtools::install_github("bvieth/powsimR", build_vignettes = TRUE, dependencies = FALSE)
library("powsimR")
目前,为了使其正常工作,你还需要手动加载 dplyr
。
如何做...
我们将通过以下步骤进行功效分析:
- 估计仿真参数值:
arab_data <- readRDS(file.path(getwd(), "datasets", "ch1", "arabidopsis.RDS" ))
means_mock <- rowMeans(arab_data[, c("mock1", "mock2", "mock3")])
means_hrcc <- rowMeans(arab_data[, c("hrcc1", "hrcc2", "hrcc3")])
log2fc <- log2(means_hrcc / means_mock)
prop_de <- sum(abs(log2fc) > 2) / length(log2fc)
- 检查
log2
变动比率的分布:
finite_log2fc <-log2fc[is.finite(log2fc)]
plot(density(finite_log2fc))
extRemes::qqnorm(finite_log2fc )
- 设置仿真运行的参数值:
library(powsimR)
library(dplyr)
params <- estimateParam(
countData = arab_data,
Distribution = "NB",
RNAseq = "bulk",
normalization = "TMM" # edgeR method, can be others
)
de_opts <- DESetup(ngenes=1000,
nsims=25,
p.DE = prop_de,
pLFC= finite_log2fc,
sim.seed = 58673
)
sim_opts <- SimSetup(
desetup = de_opts,
params = params
)
num_replicates <- c(2, 3, 5, 8, 12,15)
- 运行仿真:
simDE <- simulateDE(n1 = num_replicates,
n2 = num_replicates,
sim.settings = sim_opts,
DEmethod = "edgeR-LRT",
normalization = "TMM",
verbose = FALSE)
- 运行仿真评估:
evalDE <- evaluateDE(simRes = simDE,
alpha.type = 'adjusted',
MTC = 'BH',
alpha.nominal = 0.1,
stratify.by = 'mean',
filter.by = 'none',
strata.filtered = 1,
target.by = 'lfc',
delta = 0)
- 绘制评估图:
plotEvalDE(evalRes = evalDE,
rate='marginal',
quick=FALSE, annot=TRUE)
它是如何工作的...
powsimR
中的功效分析要求我们做一些预分析,以便估算一些重要参数。为了执行基于仿真的功效分析,我们需要估计处理组之间的 log fold change 分布以及差异表达特征的比例。
在步骤 1中,我们将获得每个特征在两种处理条件下的平均计数。加载表达数据后,我们使用readRDS()
函数,然后在某些列上使用rowMeans()
函数,获得mock
和hrcc1
两种处理下每个基因的平均表达计数。接着,我们可以获得这些值的 log2 比率(只需将这两个向量相除,并在最后一行使用标准算术运算符计算出那些 log2 折叠变化大于 2 的值)。检查最终的prop_de
变量时,会得到以下输出:
prop_de
## [1] 0.2001754
因此,大约 0.2 的特征在计数上变化了 log2 两倍。
步骤 2 查看基因表达比率的分布。我们首先从log2fc
变量中移除非有限值。我们必须这样做,因为在计算比率时,会在 R 中生成Inf
值;这种情况发生在分母(对照样本)的均值为零时。我们可以通过使用来自is.finite()
函数的二进制向量对向量进行索引来移除这些值。移除Inf
值后,我们可以进行绘图。首先,我们使用density()
函数做一个正态密度图,展示比率的分布。然后,我们使用extRemes
包中的qqnorm()
函数,该函数将数据与来自理想正态分布、均值相同的采样数据进行对比。强烈的线性相关性表示原始数据符合正态分布。我们可以在以下截图中看到输出:
它们看起来呈对数正态分布,因此我们可以假设它们符合对数正态分布。
这里最长的步骤,步骤 3,实际上只有四行代码。我们基本上是在为模拟设置参数,这需要我们指定很多值。第一组params
是通过estimateParam()
函数创建的,需要提供数据源(countData
)、要使用的分布(我们设置Distribution = "NB"
,选择负二项分布);RNAseq 实验的类型——我们的实验是批量 RNAseq 实验(RNAseq = "bulk"
),以及归一化策略——我们使用 edgeR 风格的 TMM(normalization = "TMM"
)。第二组desetup
是通过DESetup()
函数创建的;在这里,我们选择与模拟差异表达基因数量相关的参数。我们设置总共模拟 1,000 个基因(ngenes
)和 25 次模拟运行(nsims
)。我们将差异表达的比例设置为步骤 1中估算的值,存储在prop_de
中。我们使用finite_log2fc
的向量作为pLFC
参数的输入。设置sim.seed
不是必须的,但它可以确保不同运行之间的可重复性。第三行使用SimSetup()
函数将params
和desetup
合并为一个单一对象sim_opts
。最后,我们创建一个num_replicates
向量,指定要模拟的生物学重复数(RNA 样本数)。
步骤 4相对简单:我们使用前几步创建的sim_opts
参数运行差异表达仿真,选择"edgeR-LRT"
作为差异表达方法,选择"TMM"
作为标准化方法。仿真数据存储在simDE
变量中。
在步骤 5中,我们创建了一个仿真评估——它分析并提取了各种统计数据。我们将simDE
仿真数据传递给evaluateDE()
函数,并附带与分组、过滤和显著性相关的值。
最后,在步骤 6中,我们可以绘制来自步骤 5的evalDE
对象,并查看仿真结果。我们得到以下图表,其中可以看到不同重复次数下的不同功效。请注意,x轴表示使用的重复 RNA 样本数量,指标包括 FDR、假阴性/假阳性率 (FNR/FPR),以及真阴性/真阳性率 (TNR/TPR):
还有更多...
当我们只有一个样本(或者可能只有一个重复)时,我们很难估算 log2 倍数变化分布以及差异表达基因的数量。我们可以使用回调函数代替估算来生成所需的数字。该函数的主体只需返回一个指定分布中的数字,具体分布由你决定。在这里,我们将构建一个返回均值为 0,标准差为 2 的正态分布数字的函数。这反映了我们认为 log 倍数变化分布是正态分布,且具有这些参数。构建完函数后,它将在DESetup()
函数中替代 log2 倍数变化的向量。至于差异表达基因的比例,我们只需要猜测或从我们已知的实验系统中获取估算值:
log2fc_func <- function(x){ rnorm(x, 0, 2)}
prop_de = 0.1
de_opts <- DESetup(ngenes=1000,
nsims=25,
p.DE = prop_de,
pLFC= log2fc_func,
sim.seed = 58673
)
寻找未注释的转录区域
一个常见的挑战是找到并计算对齐在注释区域之外的读取值。在 RNAseq 实验中,这些读取值可能代表未注释的基因和新型转录本。实际上,我们有一些已知的基因,并且可以看到它们被转录,因为它们有对齐的读取覆盖,但其他转录区域并未包含在任何注释中,我们希望知道这些读取代表的对齐位置。在这个配方中,我们将探讨一种看似简单的技术来寻找这些区域。
准备就绪
我们的数据集将是一个合成数据集,包含一个小的 6,000 bp 基因组区域和两个具有读取值的基因特征,以及一个未注释的区域,具有对齐的读取值,如下图所示:
我们将需要 Bioconductor 的csaw
、IRanges
、SummarizedExperiment
和rtracklayer
库,以及其他一些来自 Bioconductor 基本包的函数。数据位于本书的数据仓库中的datasets/ch1/windows.bam
和datasets/ch1/genes.gff
。
如何操作...
使用powsimR
进行功效分析可以按照以下步骤进行:
- 设置加载函数:
get_annotated_regions_from_gff <- function(file_name) {
gff <- rtracklayer::import.gff(file_name)
as(gff, "GRanges")
}
- 获取整个基因组的窗口计数:
whole_genome <- csaw::windowCounts(
file.path(getwd(), "datasets", "ch1", "windows.bam"),
bin = TRUE,
filter = 0,
width = 500,
param = csaw::readParam(
minq = 20,
dedup = TRUE,
pe = "both"
)
)
colnames(whole_genome) <- c("small_data")
annotated_regions <- get_annotated_regions_from_gff(file.path(getwd(), "datasets", "ch1", "genes.gff"))
- 查找注释与窗口之间的重叠,并对子集进行操作:
library(IRanges)
library(SummarizedExperiment)
windows_in_genes <-IRanges::overlapsAny( SummarizedExperiment::rowRanges(whole_genome), annotated_regions )
- 将窗口子集分为注释区域和非注释区域:
annotated_window_counts <- whole_genome[windows_in_genes,]
non_annotated_window_counts <- whole_genome[ ! windows_in_genes,]
- 将数据提取到计数矩阵中:
assay(non_annotated_window_counts)
它是如何工作的...
在步骤 1中,我们创建一个函数,加载 GFF 文件中的基因区域信息(请参见本书的附录以了解 GFF 的描述),并使用rtracklayer
包将其转换为 Bioconductor 的GRanges
对象。此方法有效,因为GRanges
对象可以像普通的 R 矩阵或数据框一样进行子集选择。它们在这方面表现得像一个“矩阵”,尽管GRanges
比矩阵复杂得多,但它的行为却非常相似。这使得一些操作和提取变得非常简单。我们在本食谱中广泛使用GRanges
以及相关的类RangedSummarizedExperiment
。
在步骤 2中,我们使用csaw windowCounts()
函数在 500 bp 的窗口中获取整个基因组的计数。width
参数定义了窗口大小,param
参数决定了什么是通过的读数;在这里,我们将最小读数质量(minq
)设置为 PHRED 评分 20,去除 PCR 重复(dedup = TRUE
),并要求每个读数的两个配对都对齐(pe="both"
)。返回的whole_genome
对象是RangedSummarizedExperiment
。我们将whole_genome
中单一数据列的名称设置为small_data
。最后,我们使用自定义函数get_annotated_regions_from_gff()
,根据 GFF 文件中的基因信息创建GRanges
对象annotated_regions
。
在步骤 3中,我们使用IRanges overlapsAny()
函数检查窗口位置是否与基因区域有重叠。此函数需要GRanges
对象,因此我们使用SummarizedExperiment
的rowRanges()
函数从whole_genome
变量中提取,并将该对象与现有的GRanges
对象的annotated_regions
一起传递给overlapsAny()
。此函数返回一个二进制向量,我们可以用它来进行子集选择。
在步骤 4中,我们只需使用二进制向量windows_in_genes
来对子集whole_genome
对象,从而提取注释窗口(存入annotated_window_counts
)作为GRanges
对象。然后,我们可以使用相同的代码,通过逻辑反转二进制向量(使用!
操作符)来获取非注释窗口,从而得到non_annotated_window_counts
。
最后,在步骤 5中,我们可以使用assay()
函数从GRanges
对象中提取实际计数。
还有更多...
我们可能需要从 GFF 以外的其他文件格式获取注释区域。rtracklayer
支持多种格式——这里是处理 BED 文件的函数:
get_annotated_regions_from_bed <- function(file_name){
bed <- rtracklayer::import.bed(file_name)
as(bed, "GRanges")
}
使用 bumphunter 方法发现高表达区域的初步步骤
查找所有来自同一、可能未注释的基因组特征的读数比对区域是一项常见任务。这里的目标是将读数比对区域分组,以便标记出具有显著覆盖度的区域,并随后对样本之间的表达水平差异进行比较。
准备工作...
我们将使用相同的windows
数据集,其中有一个实验包含三个峰值,并将其传入我们在食谱 4中使用的函数——这样我们就知道我们要查找三个突起。数据存储在本书的数据仓库中的datasets/ch1/windows.bam
文件下。我们需要Rsamtools
和bumphunter
库。
如何进行操作...
- 加载数据并获取每位置的覆盖度:
library(Rsamtools)
library(bumphunter)
pileup_df <- Rsamtools::pileup(file.path(getwd(), "datasets", "ch1", "windows.bam"))
- 查找初步簇:
clusters <- bumphunter::clusterMaker(pileup_df$seqnames, pileup_df$pos, maxGap = 100)
- 查找具有最小阈值的突起:
bumphunter::regionFinder(pileup_df$count, pileup_df$seqnames, pileup_df$pos, clusters, cutoff=1)
它是如何工作的...
在步骤 1中,我们使用 Rsamtools 的pileup()
函数并使用默认设置,获取每碱基的覆盖度数据框。每一行代表参考中的单个核苷酸,计数列给出该点的覆盖深度。结果存储在pileup_df
数据框中。
在步骤 2中,我们使用bumphunter
的clusterMaker()
函数在pileup_df
上进行操作,简单地将距离较近的读数分组到一起形成簇。我们传入序列名称、位置和最大距离参数(maxGap
)。该函数返回一个与数据框长度相等的簇编号向量,指示每一行在数据框中的簇归属。如果我们用table
进行统计,就能看到每个簇中包含的行数(簇大小):
table(clusters)
## clusters
## 1 2 3
## 1486 1552 1520
在步骤 3中,我们改进了方法;我们使用regionFinder()
,它应用了一个读深度阈值,以确保簇的最低读深度。我们传入与步骤 2类似的数据,添加簇归属向量 clusters 以及最小读数阈值——在这个非常小的数据集中,我们设置为 1。步骤 3的结果是被聚集在一起的区域,并以有用的表格形式呈现:
## chr start end value area cluster indexStart indexEnd L
## 3 Chr1 4503 5500 10.401974 15811 3 3039 4558 1520
## 1 Chr1 502 1500 9.985868 14839 1 1 1486 1486
## 2 Chr1 2501 3500 8.657216 13436 2 1487 3038 1552
在这些区域预测中,我们可以清楚地看到这三个位于数据中的区域,读数大致会有几个核苷酸的偏差。
还有更多...
如果你有多个实验需要分析,可以尝试bumphunter()
函数。它会在矩阵中的多个数据列上操作,并进行线性建模,以评估来自重复样本的位点和存在的不确定性;它的操作方式与regionFinder()
非常相似。
差异峰值分析
当你发现未注释的转录本时,可能想要看看它们在不同实验之间是否有差异表达。我们已经了解了如何使用edgeR和DESeq来进行这一分析,但一个问题是如何将一个如RangedSummarizedExperiment
的对象(它由数据和描述峰值区域的GRanges
对象组成)转换为内部的DESeq对象。在这个食谱中,我们将探讨如何将这些对象中的数据汇总并将其转换为正确的格式。
准备工作
对于这个食谱,你需要本书仓库中的datasets/ch1/arabidopsis_rse.RDS
文件,这里面包含了拟南芥 RNAseq 的RangedSummarizedExperiment
版本。我们还将继续使用之前使用过的DESeq和SummarizedExperiment
Bioconductor 包。
如何做...
- 加载数据并设置一个创建区域标签的函数:
library(SummarizedExperiment)
arab_rse <- readRDS(file.path(getwd(), "datasets", "ch1", "arabidopsis_rse.RDS") )
make_tag <- function(grange_obj){
paste0(
grange_obj@seqnames,
":",
grange_obj@ranges@start,
"-",
(grange_obj@ranges@start + grange_obj@ranges@width)
)
}
- 提取数据并注释行:
counts <- assay(arab_rse)
if ( ! is.null(names(rowRanges(arab_rse))) ){
rownames(counts) <- names(rowRanges(arab_rse))
} else {
rownames(counts) <- make_tag(rowRanges(arab_rse))
}
它是如何工作的...
步骤 1开始时加载我们预先准备好的RangedSummarized
实验数据;请注意,GRanges
对象中的names
槽没有填充。接下来,我们创建一个自定义函数make_tag()
,它通过拼接seqnames
、starts
和通过传递的GRanges
对象计算出的结束位置(start
+ width
)来工作。请注意@
符号语法:这是因为GRange
是一个 S4 对象,槽是通过@
而不是熟悉的$
来访问的。
在步骤 2中,代码通过assay()
函数从RangedSummarizedExperiment
中提取实际数据。返回的矩阵没有行名,这样不太有用,因此我们使用if
语句来检查names
槽——如果它可用,我们将其作为行名;如果不可用,我们使用GRanges
对象中的位置信息,在我们创建的make_tag()
函数中生成行名标签。这样就会得到以下输出——一个计数矩阵,其中位置标签作为行名,可以在本章的食谱 1和食谱 2中描述的 DESeq 和 edgeR 中使用:
head(counts)
## mock1 mock2 mock3 hrcc1 hrcc2 hrcc3
## Chr1:3631-5900 35 77 40 46 64 60
## Chr1:5928-8738 43 45 32 43 39 49
## Chr1:11649-13715 16 24 26 27 35 20
## Chr1:23146-31228 72 43 64 66 25 90
## Chr1:31170-33154 49 78 90 67 45 60
## Chr1:33379-37872 0 15 2 0 21 8
使用 SVA 估计批次效应
高通量数据,如 RNAseq,常常受到实验设计中未明确建模的技术误差的影响,这些误差可能会混淆差异表达的检测。来自不同日期、不同地点或不同机器上运行的样本的计数差异就是一个常见的技术误差示例,这种误差通常存在,我们应该尽量在实验设计中进行建模。解决这个问题的一种方法是将代理变量纳入我们的实验设计中,解释批次效应,并在建模和差异表达分析阶段考虑它。我们将使用SVA包来实现这一点。
准备工作
我们需要 Bioconductor 中的 SVA 包和我们的拟南芥计数数据,位于datasets/ch1/arabidopsis.RDS
。
如何做...
使用 SVA 估计批次效应,请执行以下步骤:
- 加载库和数据:
library(sva)
arab <- readRDS(file.path(getwd(), "datasets", "ch1", "arabidopsis.RDS"))
- 在某些实验中过滤掉计数过少的行:
keep <- apply(arab, 1, function(x) { length(x[x>3])>=2 } )
arab_filtered <- arab[keep,]
- 创建初步设计:
groups <- as.factor(rep(c("mock", "hrcc"), each=3))
- 设置测试模型和空模型并运行 SVA:
test_model <- model.matrix(~groups)
null_model <- test_model[,1]
svar <- svaseq(arab_filtered, test_model, null_model, n.sv=1)
- 提取代理变量以用于后续的设计:
design <- cbind(test_model, svar$sv)
它是如何工作的...
在步骤 1中,脚本首先加载拟南芥 RNAseq 数据的计数矩阵,你会记得这是一个简单的实验,包含三次mock
处理和三次hrcc
处理的重复。
在步骤 2中,我们创建了一个包含我们希望保留的行索引的向量,这通过测试每一行是否至少有两列计数大于 3 来完成——这是通过使用apply()
和匿名函数在计数矩阵的行上进行操作实现的。
在步骤 3中,我们创建了一个groups
因子,描述了实验样本的类型。
步骤 4是实际操作的部分;我们在model.matrix()
中使用groups
因子来创建我们想要使用的模型设计。我们还需要一个空模型,在此实验设计中,它等同于第一列,因此我们从test_model
设计对象中提取它。然后,我们可以使用关键的svaseq()
函数来估计需要添加到设计中的代理变量。我们将test_model
和null_model
结合,并使用n.sv
来选择使用的代理变量数量,对于像这样的简单设计,通常设置为 1。
最后一步,步骤 5,是将代理变量添加到设计模型中,我们通过将test_model
和svar
中的sv
列(svsar$sv
)结合在一起完成此操作。最终的设计对象可以像其他任何设计对象一样用于edgeR和DESeq2等包,且这些方法将使用代理变量来处理批次效应。
使用 AllelicImbalance 寻找等位基因特异性表达
等位基因特异性表达是指在转录本的不同等位基因变异之间存在差异丰度的情况。RNAseq 有助于为具有转录变异的基因提供等位基因特异性表达的定量估计——即转录本中可能导致不同蛋白质的变异。在本教程中,我们将探讨一种方法,用于确定转录本的哪些变异可能在不同样本中表现出偏向性表达。这些读取数据将来自不同的.bam
文件,且变异必须已经被识别。这意味着你已经完成了读取比对和变异调用步骤,并且拥有每个样本的.bam
和.vcf
文件。我们将在此教程中使用AllelicImbalance
和VariantAnnotation
包。
准备工作
你需要从 Bioconductor 中安装AllelicImbalance
和VariantAnnotation
。AllelicImbalance
包提供了一个小但信息丰富的数据集,包含人类基因组 hg19 版本上 17 号染色体的三个 SNP。文件已被提取到本书的数据仓库中的datasets/ch1/allele_expression
目录下。
如何操作...
- 加载库并设置导入文件夹:
library(AllelicImbalance)
library(VariantAnnotation)
region_of_interest <- GRanges(seqnames = c("17"), ranges = IRanges(79478301, 79478361))
bam_folder <- file.path(getwd(), "datasets", "ch1", "allele_expression")
- 加载目标区域的读取数据和变异信息:
reads <- impBamGAL(bam_folder, region_of_interest, verbose = FALSE)
vcf_file <-file.path( getwd(), "datasets", "ch1", "allele_expression","ERP000101.vcf" )
variant_positions <- granges(VariantAnnotation::readVcf(vcf_file), "hg19" )
allele_counts <- getAlleleCounts(reads, variant_positions, verbose=FALSE)
- 构建 ASEset 对象:
ase.vcf <- ASEsetFromCountList(rowRanges = variant_positions, allele_counts)
reference_sequence <- file.path(getwd(), "datasets", "ch1", "allele_expression", "hg19.chr17.subset.fa")
ref(ase.vcf) <- refAllele(ase.vcf,fasta=reference_sequence)
alt(ase.vcf) <- inferAltAllele(ase.vcf)
- 对所有变异进行测试:
binom.test(ase.vcf, n="*")
它是如何工作的...
在步骤 1中,脚本首先创建了一个熟悉的GRanges
对象,描述了我们感兴趣的区域以及包含.bam
文件的文件夹。
在步骤 2中,impBamGAL()
函数加载了目标区域的读取数据。变异信息被加载到variant_positions
中——另一个GRanges
对象,读取数据和变异用于使用getAlleleCounts()
生成等位基因计数。*
完成这一部分后,在第三步,我们可以构建ASESet对象ase.vcf
(一个继承自RangedSummarizedExperiment
的类),使用构造函数ASEsetFromCountList()
;然后我们使用设置函数ref()
和alt()
,应用参考和替代碱基身份。
最后,在第四步,我们可以应用测试。binom.test()
对每个位置每个样本(.bam
文件)进行二项分布检验,测试参考和替代等位基因的计数是否有偏差。n参数告诉测试考虑哪个链——在这个例子中,我们没有设置每条链的信息,因此使用"*"
来忽略链特性。
这将给出以下输出:
## chr17_79478287 chr17_79478331 chr17_79478334
## ERR009113.bam 0.500 1.000000e+00 1.000000e+00
## ERR009115.bam 0.125 6.103516e-05 3.051758e-05
还有更多...
如果需要,前面的分析可以扩展为每条链和每种表型的测试。脚本需要进行修改,以在ASESet
对象构造步骤中引入链信息。通常,这需要 RNAseq 实验和对齐步骤在考虑链特性的前提下执行,并且生物信息学流程需按此配置。可以在构造步骤中使用colData
参数和一个表型或样本类型的向量来添加表型信息,这些信息对应ASESet
对象中的列。
绘制和展示 RNAseq 数据
绘制 RNAseq 数据总览或单个基因或特征的图表是质量控制和理解的重要步骤。在这个步骤中,我们将看到如何绘制样本中基因计数图、如何创建一个 MA 图(它绘制计数与倍数变化,并帮助我们发现与表达相关的样本偏差),以及如何创建一个火山图(它绘制显著性与倍数变化,并帮助我们轻松发现最有意义的变化)。
准备工作
在这个步骤中,我们将使用DESeq2
包、ggplot2
包、magrittr
和dplyr
。我们将使用为modencodefly
数据创建的DESeqDataSet
对象,第 2 步配方中保存的版本位于本书数据仓库中的datasets/ch1/modencode_dds.RDS
文件中。
如何做到这一点...
- 加载库并创建 RNAseq 结果的数据框:
library(DESeq2)
library(magrittr)
library(ggplot2)
dds <- readRDS("~/Desktop/r_book/datasets/ch1/modencode_dds.RDS")
- 创建一个单一基因的箱线图,基于"
stage
"进行条件化:
plotCounts(dds, gene="FBgn0000014", intgroup = "stage", returnData = TRUE) %>%
ggplot() + aes(stage, count) + geom_boxplot(aes(fill=stage)) + scale_y_log10() + theme_bw()
- 创建一个基于显著性条件的 MA 图:
result_df <- results(dds, contrast=c("stage","L2Larvae","L1Larvae"), tidy= TRUE) %>%
dplyr::mutate(is_significant=padj<0.05)
ggplot(result_df) + aes(baseMean, log2FoldChange) + geom_point(aes(colour=is_significant)) + scale_x_log10() + theme_bw()
- 创建一个基于显著性条件的火山图:
ggplot(result_df) + aes(log2FoldChange, -1 * log10(pvalue)) + geom_point(aes(colour=is_significant)) + theme_bw()
它是如何工作的...
第一步是简要加载我们需要的数据集和库。
在第二步,我们利用了DESeq2中plotCounts()
和results()
函数中的一些有用参数。plotCounts()
中的returnData
标志将可选地返回给定条件下某个基因的计数信息的整洁数据框,因此我们可以将数据传递给ggplot()
来制作单个基因的箱线图。magrittr的%>%
操作符允许我们将plotCounts()
的返回值直接传递给ggplot()
的第一个位置参数,而无需先保存到中间变量中。
在步骤 3中,我们使用 DESeq2 中的results()
函数来获取results
数据框,并将其传递给dplyr的mutate()
函数,以添加一个名为is_significant
的新列,如果padj
列的值小于 0.05,则该列值为TRUE
。然后,我们在ggplot()
命令中使用返回的result_df
数据框,绘制baseMean
(计数)与 log2 折叠变化之间的散点图,点的颜色由is_significant
变量决定,实际上是根据 P 值是否小于 0.05 来着色。
在步骤 4中,我们使用相同的result_df
数据框,绘制 log2 折叠变化与'pvalue'
的负 log10 值的关系,得到一个'火山图'
,展示 P 值与差异表达水平之间的关系:
前面三个图是这三个`ggplot``()命令的合成结果输出。
第二章:使用 HTS 数据查找遗传变异
高通量测序(HTS)使得在短时间内发现遗传变异并进行全基因组基因分型和单倍型分析成为可能。这项技术所释放的数据洪流为生物信息学家和计算机科学家提供了一些独特的机会,创造了很多创新的新数据存储和分析流程。变异调用的基本流程从 HTS 读取的质量控制开始,然后是将这些读取比对到参考基因组。这些步骤通常会在 R 分析之前进行,并通常会生成一个包含读取比对的 BAM 文件或包含变异位置的 VCF 文件(有关这些文件格式的简要讨论,请参见本书的附录),我们将在 R 代码中对其进行处理。
由于变异调用和分析是生物信息学中的基本技术,Bioconductor 配备了构建软件和进行分析所需的工具。研究人员想要问的关键问题可能从 我的基因组上遗传变异的位置在哪里? 到 有多少个变异? 再到 如何对它们进行分类? 我们将查看一些解决这些问题的方案,并且还将探讨一些重要的通用技术,这些技术使我们能够在基因组上可视化变异和标记,并评估变异与基因型的关联。我们还将了解遗传变异的其他定义,并探索如何评估单个位点的拷贝数。
本章将涵盖以下方案:
-
使用 VariantTools 查找序列数据中的 SNP 和 indel
-
预测长参考序列中的开放阅读框
-
使用 karyoploteR 在遗传图谱上绘制特征
-
寻找替代转录本同种型
-
使用 VariantAnnotation 选择和分类变异
-
提取感兴趣基因组区域的信息
-
查找表型和基因型与 GWAS 的关联
-
估计感兴趣位点的拷贝数
技术要求
以下是你将需要的 R 包。一些可以通过 install.packages()
安装。列在 Bioconductor
下的包需要通过专用安装器安装。相关说明请参见此处。如果你需要进行其他操作,包的安装将在使用这些包的方案中进行描述:
-
Bioconductor
:以下是这些包:-
Biostrings
-
GenomicRanges
-
gmapR
-
karyoploteR
-
rtracklayer
-
systemPipeR
-
SummarizedExperiment
-
VariantAnnotation
-
VariantTools
-
-
rrBLUP
Bioconductor 非常庞大,并且拥有自己的安装管理器。你可以通过以下代码安装这些包(更多信息请参考 www.bioconductor.org/install/
):
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
通常,在 R 中,用户会加载一个库并直接通过名称使用其中的函数。这在交互式会话中非常方便,但当加载多个包时,可能会导致混淆。为了明确当前使用的是哪个包和函数,我有时会使用packageName::functionName()
的约定。
有时,在一个过程的中途,我会暂停代码,以便你可以看到一些中间输出或理解某个对象的结构。每当发生这种情况时,你会看到一个代码块,每行的开头都会有双井号(##
)符号,如下所示:
letters[1:5]
## a b c d e
本章所有代码和数据都可以在本书的 GitHub 仓库找到:github.com/danmaclean/R_Bioinformatics_Cookbook
。
使用 VariantTools 从序列数据中找到 SNP 和插入缺失变异(indels)
一个关键的生物信息学任务是处理高通量序列读取的比对结果,通常存储在 BAM 文件中,并计算变异位置列表。当然,这个过程可以通过许多外部命令行程序和工具来完成,通常会生成一个 VCF 格式的变异文件,但 Bioconductor 中有一些非常强大的包能够完成整个流程,并且以快速高效的方式,通过利用 BiocParallel 的并行计算功能——这一功能旨在加速处理 Bioconductor 对象中的大规模数据集——实现这一目标。使用 Bioconductor 工具可以让我们将所有处理步骤都保持在 R 环境中,在这一部分中,我们将使用纯粹的 R 代码和一些 Bioconductor 包,逐步完成一个从读取数据到变异基因列表的完整流程。
准备工作
在本节中,我们将使用一组合成的读取数据,覆盖人类基因组 17 号染色体前 83KB 左右的区域。这些读取数据是使用samtools
中的wgsim
工具生成的——一个外部命令行程序。它们包含 64 个由wgsim
引入的 SNP,这些 SNP 可以在datasets/ch2/snp_positions.txt
中的样本数据中看到。你会看到,随着程序的进展,默认参数会找到比实际更多的 SNP——你需要仔细调节参数,以精细调整 SNP 发现过程。
如何操作...
使用VariantTools
从序列数据中找到 SNP 和插入缺失变异(indels)可以通过以下步骤完成:
- 导入所需的库:
library(GenomicRanges)
library(gmapR)
library(rtracklayer)
library(VariantAnnotation)
library(VariantTools)
- 然后,加载数据集:
bam_folder <- file.path(getwd(), "datasets", "ch2")
bam_folder_contents <- list.files(file.path(getwd(), "datasets", "ch2" ) )
bam <- file.path( bam_folder, "hg17_snps.bam")
fasta_file <- file.path(bam_folder,"chr17.83k.fa")
- 设置基因组对象和参数对象:
fa <- rtracklayer::FastaFile(fasta_file)
genome <- gmapR::GmapGenome(fa, create=TRUE)
qual_params <- TallyVariantsParam(
genome = genome,
minimum_mapq = 20)
var_params <- VariantCallingFilters(read.count = 19,
p.lower = 0.01
)
- 调用变异:
called_variants <- callVariants(bam, qual_params,
calling.filters = var_params
)
head(called_variants)
- 现在,我们进入注释阶段并加载来自
.gff
或.bed
文件的特征位置信息:
get_annotated_regions_from_gff <- function(file_name) {
gff <- rtracklayer::import.gff(file_name)
as(gff, "GRanges")
}
get_annotated_regions_from_bed <- function(file_name){
bed <- rtracklayer::import.bed(file_name)
as(bed, "GRanges")
}
genes <- get_annotated_regions_from_gff(file.path( bam_folder, "chr17.83k.gff3"))
- 现在我们计算哪些变异与哪些基因重叠:
overlaps <- GenomicRanges::findOverlaps(called_variants, genes)
overlaps
- 最后,我们通过重叠列表来筛选基因。
genes[subjectHits(overlaps)]
它是如何工作的...
这是一个复杂且涉及多个步骤的管道。加载库后,前四行设置了我们需要的来自数据集目录的文件。请注意,我们需要一个.bam
文件和一个 fasta
文件。接下来,我们使用 gmapR::GmapGenome()
函数与 fasta
对象一起创建一个 GmapGenome
对象——这个对象描述了基因组以供后续的变异调用函数使用。接下来的两个函数,TallyVariantParams()
和 VariantCallingFilters()
,对于正确调用和筛选候选 SNP 至关重要。这些函数是你可以设置定义 SNP 或 indel 参数的地方。这里的选项故意设置得很少。正如从输出中看到的,虽然我们创建了 64 个 SNP,但只有 6 个被成功调用。
一旦参数定义完毕,我们使用 callVariants()
函数,将我们设置的所有信息作为输入,获得一个变异的 vranges
对象。
这将产生以下输出:
VRanges object with 6 ranges and 17 metadata columns:
## seqnames ranges strand ref alt
## <Rle> <IRanges> <Rle> <character> <characterOrRle>
## [1] NC_000017.10 64 * G T
## [2] NC_000017.10 69 * G T
## [3] NC_000017.10 70 * G T
## [4] NC_000017.10 73 * T A
## [5] NC_000017.10 77 * T A
## [6] NC_000017.10 78 * G T
然后,我们可以设置 GFF
文件注释的 GRanges
对象(我还提供了一个从 BED
文件获取注释的函数)。
这将产生以下输出:
## Hits object with 12684 hits and 0 metadata columns:
## queryHits subjectHits
## <integer> <integer>
## [1] 35176 1
## [2] 35176 2
## [3] 35176 3
## [4] 35177 1
最后的步骤是使用 XRanges
对象强大的重叠和子集功能。我们使用 GenomicRanges::findOverlaps()
查找实际的重叠——返回的 overlaps
对象实际上包含了每个输入对象中重叠对象的索引。
这将产生以下输出:
## GRanges object with 12684 ranges and 20 metadata columns:
## seqnames ranges strand | source type score
## <Rle> <IRanges> <Rle> | <factor> <factor> <numeric>
## [1] NC_000017.10 64099-76866 - | havana ncRNA_gene <NA>
## [2] NC_000017.10 64099-76866 - | havana lnc_RNA <NA>
## [3] NC_000017.10 64099-65736 - | havana exon <NA>
因此,我们可以使用 subjectHits(overlaps)
直接筛选出包含 SNP 的基因,并获得一个非常简洁的列表。
还有更多内容...
当我们对筛选条件和我们调用的变异集感到满意时,可以使用以下代码保存变异的 VCF 文件:
VariantAnnotation::sampleNames(called_variants) <- "sample_name"
vcf <- VariantAnnotation::asVCF(called_variants)
VariantAnnotation::writeVcf(vcf, "hg17.vcf")
另见
虽然我们的方案使步骤和代码清晰易懂,但我们需要更改的实际参数和数值不能像这样直接描述,因为这些值将极大依赖于数据集。VariantTools
文档包含了如何正确设置参数的详细讨论:bioconductor.org/packages/release/bioc/vignettes/VariantTools/inst/doc/VariantTools.pdf
。
在长参考序列中预测开放阅读框
先前未测序基因组的草图组装可以是一个丰富的生物学知识来源,但当基因组学资源如基因注释不可用时,继续进行可能会变得棘手。在这里,我们将看看一个第一阶段的流程,用来寻找潜在的基因和基因组位点,完全是de novo的,且没有超出序列的信息。我们将使用一组非常简单的规则来寻找开放阅读框——这些序列从起始密码子开始,并以终止密码子结束。实现这一点的工具被封装在Bioconductor
包中的一个函数systemPipeR
内。我们最终会得到另一个GRanges
对象,可以将其整合到下游的处理流程中,帮助我们交叉引用其他数据,例如 RNAseq,正如我们在第一章《执行定量 RNAseq》中的查找未注释的转录区食谱中看到的那样。最后一步,我们将看看如何使用基因组模拟来评估哪些开放阅读框实际上可能是有效的,而不是仅仅偶然出现的。
做好准备
在这个食谱中,我们只需要输入Arabidopsis
叶绿体基因组的短 DNA 序列;它位于datasets/ch2/arabidopsis_chloroplast.fa
.
我们还需要Bioconductor
包中的Biostrings
和systemPipeR
。
如何操作...
在长参考序列中预测开放阅读框可以通过以下步骤完成:
- 加载库并输入基因组:
library(Biostrings)
library(systemPipeR)
dna_object <- readDNAStringSet(file.path(getwd(), "datasets","ch2", "arabidopsis_chloroplast.fa"))
- 预测ORFs(开放阅读框):
predicted_orfs <- predORF(dna_object, n = 'all', type = 'gr', mode='ORF', strand = 'both', longest_disjoint = TRUE)
predicted_orfs
- 计算参考基因组的特性:
bases <- c("A", "C", "T", "G")
raw_seq_string <- strsplit(as.character(dna_object), "")
seq_length <- width(dna_object[1])
counts <- lapply(bases, function(x) {sum(grepl(x, raw_seq_string))} )
probs <- unlist(lapply(counts, function(base_count){signif(base_count / seq_length, 2) }))
- 创建一个函数,找到模拟基因组中最长的 ORF:
get_longest_orf_in_random_genome <- function(x,
length = 1000,
probs = c(0.25, 0.25, 0.25, 0.25),
bases = c("A","C","T","G")){
random_genome <- paste0(sample(bases, size = length, replace = TRUE, prob = probs), collapse = "")
random_dna_object <- DNAStringSet(random_genome)
names(random_dna_object) <- c("random_dna_string")
orfs <- predORF(random_dna_object, n = 1, type = 'gr', mode='ORF', strand = 'both', longest_disjoint = TRUE)
return(max(width(orfs)))
}
- 在 10 个模拟基因组上运行该函数:
random_lengths <- unlist(lapply(1:10, get_longest_orf_in_random_genome, length = seq_length, probs = probs, bases = bases))
- 获取最长随机 ORF 的长度:
longest_random_orf <- max(random_lengths)
- 只保留预测的长度大于最长随机 ORF 的 ORF:
keep <- width(predicted_orfs) > longest_random_orf
orfs_to_keep <- predicted_orfs[keep]
orfs_to_keep
它是如何工作的...
这个食谱的第一部分是我们实际预测 ORF 的地方。首先,我们使用Biostrings
包中的readDNAStringSet()
函数将 DNA 序列加载为DNAStringSet
对象。systemPipeR
包中的predORF()
函数使用这个对象作为输入,并根据设置的选项实际预测开放阅读框。在这里,我们返回两个链上的所有 ORF。
这将产生以下输出:
## GRanges object with 2501 ranges and 2 metadata columns:
## seqnames ranges strand | subject_id inframe2end
## <Rle> <IRanges> <Rle> | <integer> <numeric>
## 1 chloroplast 86762-93358 + | 1 2
## 1162 chloroplast 2056-2532 - | 1 3
## 2 chloroplast 72371-73897 + | 2 2
## 1163 chloroplast 77901-78362 - | 2 1
## 3 chloroplast 54937-56397 + | 3 3
我们返回一个GRanges
对象,其中描述了 2,501 个开放阅读框。这个数字太多了,所以我们需要过滤掉其中一些;特别是,我们可以找出哪些 ORF 是由序列中的偶然事件引起的。为此,我们需要做一点模拟,这也正是接下来代码部分要做的事情。
为了估计随机 ORF 的最大长度,我们将创建一系列长度与输入序列相等且具有相同碱基比例的随机基因组,并观察可以预测的最长 ORF 是什么。我们进行几次迭代,并大致了解由偶然事件引起的最长 ORF 的长度。这个长度将作为一个阈值,用于拒绝真实序列中预测的 ORF。
实现这一目标需要一些设置和自定义函数。首先,我们定义将作为简单字符向量使用的碱基。然后,我们通过分割dna_object
的as.character
版本来获取原始 DNA 序列的字符向量。我们利用这些信息计算输入序列中每个碱基的比例,首先计算每个碱基的数量(得到counts
),然后将其除以序列的长度,得到probs
。在这两个步骤中,我们使用lapply()
遍历bases
向量和counts
列表,应用匿名函数来计算这些变量的结果列表。unlist()
用于将最终列表简化为一个简单的向量。
一旦我们完成设置,就可以构建我们的get_longest_orf_in_random_genome()
模拟函数。该函数通过从bases
中的选择中按给定的probs
概率采样字符生成一个随机基因组。该向量通过paste0()
合并为一个字符串,然后转换为DNAStringSet
对象,以供predORF()
函数使用。这一次,我们只请求最长的 ORF,使用n = 1并返回其长度。
这将产生以下输出:
## GRanges object with 10 ranges and 2 metadata columns:
## seqnames ranges strand | subject_id inframe2end
## <Rle> <IRanges> <Rle> | <integer> <numeric>
## 1 chloroplast 86762-93358 + | 1 2
## 2 chloroplast 72371-73897 + | 2 2
## 3 chloroplast 54937-56397 + | 3 3
## 4 chloroplast 57147-58541 + | 4 1
现在,我们可以运行这个函数,我们使用lapply()
运行 10 次,并使用我们之前计算的length
、probs
和bases
信息。unlist()
将结果转换为一个简单的向量,我们使用max()
提取 10 次运行中的最长一个。我们可以通过子集化原始的predicted_orfs GRanges
对象,保留那些比随机生成的 ORFs 更长的 ORF。
还有更多...
一旦你获得了一组你满意的 ORF,你可能希望将它们保存到文件中。你可以使用BSgenome
包中的getSeq()
函数,通过传递原始序列对象——dna_object
——以及orfs_to_keep
中的范围来完成。然后,使用names()
为结果命名,最后,你可以使用writeXStringSet()
函数将它们保存到文件中:
extracted_orfs <- BSgenome::getSeq(dna_object, orfs_to_keep)
names(extracted_orfs) <- paste0("orf_", 1:length(orfs_to_keep))
writeXStringSet(extracted_orfs, "saved_orfs.fa")
使用 karyoploteR 绘制遗传图谱上的特征
我们能做的最有价值且富有洞察力的事情之一就是可视化数据。我们常常希望在染色体或遗传图谱上看到某些感兴趣的特征与其他特征的关系。这些图有时称为染色体图,有时称为示意图,在本节中,我们将看到如何使用karyoploteR
包创建这些图。该包将熟悉的GRanges
对象作为输入,并根据配置生成详细的图表。我们将快速浏览几种不同的图表样式,以及一些配置选项,以解决图表中的问题,比如标签溢出或重叠。
准备工作
对于这个配方,你需要安装karyoploteR
,但我们将使用的所有数据将在配方本身中生成。
如何进行操作...
使用karyoploteR
绘制遗传图谱上的特征,可以通过以下步骤完成:
- 首先,我们加载库:
library(karyoploteR)
library(GenomicRanges)
- 然后,设置基因组对象,这将作为我们核型图的基础:
genome_df <- data.frame(
chr = paste0("chr", 1:5),
start = rep(1, 5),
end = c(34964571, 22037565, 25499034, 20862711, 31270811)
)
genome_gr <- makeGRangesFromDataFrame(genome_df)
- 设置我们将绘制为标记的 SNP 位置:
snp_pos <- sample(1:1e7, 25)
snps <- data.frame(
chr = paste0("chr", sample(1:5,25, replace=TRUE)),
start = snp_pos,
end = snp_pos
)
snps_gr <- makeGRangesFromDataFrame(snps)
- 为标记创建一些标签:
snp_labels <- paste0("snp_", 1:25)
- 设置绘图边距:
plot.params <- getDefaultPlotParams(plot.type=1)
plot.params$data1outmargin <- 600
- 创建基础图表并添加轨道:
kp <- plotKaryotype(genome=genome_gr, plot.type = 1, plot.params = plot.params)
kpPlotMarkers(kp, snps_gr, labels = snp_labels)
它是如何工作的...
代码首先加载我们需要的库,然后我们构建一个data.frame
,描述我们要绘制的基因组,并相应地设置名称和长度。然后,使用makeGRangesFromDataFrame()
转换函数将data.frame
转换为genome_gr
——一个GRanges
对象。接着,我们使用sample()
函数生成一个包含 25 个随机 SNP 的data.frame
,并选择一个位置和染色体。同样,这些数据也被转换为GRanges
。现在我们可以设置我们的绘图。首先,我们通过getDefaultPlotParams()
从包内部获取默认的绘图参数对象。我们可以修改此对象,以便对绘图的默认设置进行更改。
注意,我们选择了plot.type = 1
——这是一个简单的绘图,具有直接位于每个染色体区域上方的数据轨道。我们需要调整数据轨道的边距高度,以防止标记标签溢出到顶部——这可以通过plot.params$data1outmargin <- 600
来完成。最后,我们可以绘制我们的图表;我们通过调用plotKaryotype()
并传入genome_gr
对象、plot.type
以及在修改后的plot.params
对象中的参数来创建基本的绘图对象kp
。
这将产生以下输出:
我们的标记是使用kpPlotMarkers()
函数绘制的,传入新的kp
绘图对象、snps_gr
数据和 SNP 标签。
还有更多...
我们可以通过karyoploteR
将多种类型的数字数据添加到数据轨道中。以下示例演示了如何将一些数字数据绘制成简单的线条。第一步是准备我们的数据。在这里,我们创建了一个包含 100 个随机数的data.frame
,这些随机数映射到染色体 4 的 100 个窗口,并且像之前一样,我们创建了一个GRanges
对象。这次,我们将在染色体的上方和下方分别添加一个数据轨道——一个用于 SNP 标记,另一个用于新数据(注意,这里的plot.type = 2
)。接下来,我们需要设置绘图的参数——特别是边距,以防标签和数据重叠;但是之后,绘图的调用与之前相同,这次加入了kpLines()
的调用。这里的关键参数是y
,它描述了数据在每个绘图点的y
值(请注意,这来自我们的numeric_data
对象中的单列数据)。现在我们得到了一个包含数字数据轨道的绘图,沿着染色体 4 进行显示。以下是此示例所需执行的步骤:
- 创建一些数字数据:
numeric_data <- data.frame(
y = rnorm(100,mean = 1,sd = 0.5 ),
chr = rep("chr4", 100),
start = seq(1,20862711, 20862711/100),
end = seq(1,20862711, 20862711/100)
)
numeric_data_gr <- makeGRangesFromDataFrame(numeric_data)
- 设置绘图边距:
plot.params <- getDefaultPlotParams(plot.type=2)
plot.params$data1outmargin <- 800
plot.params$data2outmargin <- 800
plot.params$topmargin <- 800
- 创建一个绘图并添加轨道:
kp <- plotKaryotype(genome=genome_gr, plot.type = 2, plot.params = plot.params)
kpPlotMarkers(kp, snps_gr, labels = snp_labels)
kpLines(kp, numeric_data_gr, y = numeric_data$y, data.panel=2)
这将产生以下输出:
参见:
这里有许多未涵盖的轨道类型和绘图布局。请尝试查看 karyoploteR 手册,以获取完整列表:bioconductor.org/packages/release/bioc/vignettes/karyoploteR/inst/doc/karyoploteR.html
。
karyoploteR
的一个特点是它仅绘制水平的染色体图。对于垂直图,Bioconductor 中还有chromPlot
包。
使用 VariantAnnotation 选择和分类变异
在我们调用变异的管道中,我们通常希望进行后续分析步骤,这些步骤需要根据单个变异的特征(例如,备选等位基因的覆盖深度)进一步进行过滤或分类。这最好从 VCF 文件中进行,常见的做法是保存一个包含所有变异的 VCF 文件,之后再进行过滤。在这一部分中,我们将讨论如何获取输入 VCF 并进行过滤,以保留那些备选等位基因在样本中为主要等位基因的变异。
准备工作
我们需要一个tabix
索引的 VCF 文件;我提供了一个在datasets/ch2/sample.vcf.gz
文件中。我们还需要 Bioconductor 包VariantAnnotation
。
如何操作...
使用VariantAnnotation
选择和分类变异可以通过以下步骤完成:
- 创建一个预过滤函数:
is_not_microsat <- function(x){ !grepl("microsat", x, fixed = TRUE)}
- 将预过滤函数加载到
FilterRules
对象中:
prefilters <- FilterRules(list(microsat = is_not_microsat) )
- 创建一个过滤函数,以保留那些参考等位基因出现在少于一半读取中的变异:
major_alt <- function(x){
af <- info(x)$AF
result <- unlist(lapply(af, function(x){x[1] < 0.5}))
return(result)
}
- 将过滤函数加载到一个
FilterRules
对象中:
filters <- FilterRules(list(alt_is_major = major_alt))
- 加载输入的 VCF 文件并应用过滤:
vcf_file <- file.path(getwd(), "datasets", "ch2", "sample.vcf.gz")
filterVcf(vcf_file, "hg17", "filtered.vcf", prefilters = prefilters, filters = filters)
它是如何工作的...
在这个非常简短的脚本中,实际上有大量的内容。总体思路是我们需要定义两组过滤规则——prefilter
和filter
。这通过定义函数来实现,函数接受解析后的 VCF 记录并返回TRUE
,如果该记录通过了过滤。预过滤通常是基于未解析的 VCF 记录行的文本过滤——即记录的原始文本。我们的第一行代码定义了一个is_not_microsat()
函数,该函数接收一个字符串,并使用grepl()
函数判断该行是否包含单词microsat
,如果不包含,则返回TRUE
。预过滤函数被打包到我们称之为prefilters
的FilterRules
对象中。
过滤器更为复杂。这些过滤器作用于解析后的 VCF 记录(作为VCF类对象)。我们的major_alt()
函数使用info()
VCF访问器函数提取 VCF 记录中的info
数据。它返回一个数据框,其中每一列都是info
部分的一个独立部分。我们提取AF
列,它返回一个列表,其中每个 VCF 都有一个元素。为了遍历这些元素,我们使用lapply()
函数应用一个匿名函数,如果参考等位基因的比例低于 0.5(即替代等位基因是主要等位基因),则返回TRUE
。然后我们使用unlist()
将结果转换为向量。major_alt()
函数然后被打包到一个名为filters
的FilterRules
对象中。
最后,完成所有设置后,我们可以加载输入 VCF 文件并使用filterVCF()
函数进行过滤。此函数需要FilterRules
对象和输出的过滤后的 VCF 文件名。我们使用filtered.vcf
作为要写入的文件。
另请参见
在过滤函数中,我们可以利用其他访问器函数来获取 VCF 记录的不同部分。有geno()
和fixed()
函数,它们将返回描述这些 VCF 记录部分的数据结构。你可以像使用info()
一样使用它们来创建过滤器。
提取感兴趣的基因组区域信息
很多时候,你可能需要更详细地查看落在特定基因组区域内的数据,无论是基因中的 SNP 和变异,还是特定基因座中的基因。这个极其常见的任务可以通过功能强大的GRanges
和SummarizedExperiment
对象来很好地处理,尽管这些对象的设置可能有些繁琐,但它们具有非常灵活的子集操作,付出的努力是值得的。我们将探讨几种设置这些对象的方法以及几种操作它们以获取有趣信息的方法。
准备工作
在这个示例中,我们需要GenomicRanges
、SummarizedExperiment
和rtracklayer
Bioconductor 包。我们还需要两个输入数据文件:Arabidopsis
染色体 4 的 GFF 文件,位于datasets/ch2/arabidopsis_chr4.gff
文件中,以及同一染色体的基因仅特征的较小文本版本,位于datasets/ch2/arabidopsis_chr4.txt
。
如何实现……
提取感兴趣的基因组区域信息可以通过以下步骤完成:
- 加载包并定义一些从常见文件创建
GRanges
的函数:
library(GenomicRanges)
library(rtracklayer)
library(SummarizedExperiment)
get_granges_from_gff <- function(file_name) {
gff <- rtracklayer::import.gff(file_name)
as(gff, "GRanges")
}
get_granges_from_bed <- function(file_name){
bed <- rtracklayer::import.bed(file_name)
as(bed, "GRanges")
}
get_granges_from_text <- function(file_name){
df <- readr::read_tsv(file_name, col_names = TRUE )
GenomicRanges::makeGRangesFromDataFrame(df, keep.extra.columns = TRUE)
}
- 实际上使用这些函数创建一些
GRanges
对象:
gr_from_gff <- get_annotated_regions_from_gff(file.path(getwd(), "datasets", "ch2", "arabidopsis_chr4.gff"))
gr_from_txt <- get_granges_from_text(file.path(getwd(), "datasets", "ch2", "arabidopsis_chr4.txt"))
- 通过对属性进行筛选来提取区域;在此案例中——
seqnames
和metadata
列:
genes_on_chr4 <- gr_from_gff[ gr_from_gff$type == "gene" & seqnames(gr_from_gff) %in% c("Chr4") ]
- 手动创建一个感兴趣的区域:
region_of_interest_gr <- GRanges(
seqnames = c("Chr4"),
IRanges(c(10000), width= c(1000))
)
- 使用感兴趣的区域对子集进行筛选:
overlap_hits <- findOverlaps(region_of_interest_gr, gr_from_gff)
features_in_region <- gr_from_gff[subjectHits(overlap_hits) ]
features_in_region
它是如何工作的……
这里的第一步是创建一个GRanges
对象,该对象描述了你感兴趣的基因组特征。我们创建的三个函数分别加载来自不同文件类型的信息,即.gff
、.bed
和一个制表符分隔的.txt
文件,并返回所需的GRanges
对象。在第 2 步中,我们利用 GFF 和文本函数创建了两个GRanges
对象:gr_from_gff
和gr_from_txt
。这些对象随后用于子集操作。首先,在第 3 步中,我们对特征属性进行了子集操作。代码在 4 号染色体上找到了基因类型的特征。请注意,在Chr4
中寻找基因和特征的语法区别。GRanges
对象中的基本列—即seqnames
、width
和start
—都有访问器函数来返回向量。
因此,我们在条件的第二部分使用了它。所有其他列—在GRanges
术语中称为元数据—可以使用标准的$
语法访问,因此我们在条件的第一部分使用了它。
在第 4 步中,我们在自定义的最小GRanges
对象中创建了一个特定的区域。这个对象只包含一个区域,但可以通过在手动指定的向量中添加更多的seqnames
、start
和width
来添加更多区域。最后,在第 5 步中,我们使用findOverlaps()
函数获取在gr_from_gff
对象中与手动创建的region_of_interest
重叠的特征的索引,并使用这些索引来子集较大的gr_from_gff
对象。
这将产生以下输出:
## GRanges object with 1 range and 10 metadata columns:
## seqnames ranges strand | source type score phase
## <Rle> <IRanges> <Rle> | <factor> <factor> <numeric> <integer>
## [1] Chr4 2895-10455 - | TAIR10 gene <NA> <NA>
## ID Name Note Parent
## <character> <character> <CharacterList> <CharacterList>
## [1] AT4G00020 AT4G00020 protein_coding_gene <NA>
## Index Derives_from
## <character> <character>
## [1] <NA> <NA>
## -------
## seqinfo: 1 sequence from an unspecified genome; no seqlengths
请注意,我们需要使用subjectHits()
访问器来提取 subject hits 列。
还有更多...
通过利用GRanges
,也可以以相同的方式提取数据框或矩阵的子集,GRanges
是其他对象的一部分。在以下示例中,我们创建了一个随机数据矩阵,并用它构建了一个SummarizedExperiment
对象,该对象使用GRanges
对象来描述其行:
set.seed(4321)
experiment_counts <- matrix( runif(4308 * 6, 1, 100), 4308)
sample_names <- c(rep("ctrl",3), rep("test",3) )
se <- SummarizedExperiment::SummarizedExperiment(rowRanges = gr_from_txt, assays = list(experiment_counts), colData = sample_names)
然后,我们可以像之前一样进行子集操作,并返回数据的子集以及范围的子集。assay()
函数返回实际的数据矩阵:
overlap_hits <- findOverlaps(region_of_interest_gr, se)
data_in_region <- se[subjectHits(overlap_hits) ]
assay(data_in_region)
这将给出结果输出:
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 69.45349 90.44524 88.33501 60.87932 86.24007 45.64919
通过 GWAS 寻找表型和基因型关联
使用高通量测序技术在许多样本中找到数千种基因变异的强大应用之一是全基因组关联研究(GWAS)——基因型和表型的关系。GWAS 是一项基因组分析,研究不同个体或遗传群体中的遗传变异,查看是否某个特定变异与某一性状相关。进行这种研究的方法有很多,但都依赖于收集特定样本中的变异数据,并在某种方式下与表型进行交叉参考。在本例中,我们将研究 2006 年 Yu et al 提出的复杂混合线性模型(Nature Genetics,38:203-208)。描述统一混合线性模型的工作原理超出了本例的范围,但它是一个适用于样本量大且具有广泛等位基因多样性的数据的模型,且可用于植物和动物数据。
准备工作
在本例中,我们将构建运行分析所需的数据结构,这些数据结构来自输入的 VCF 文件。我们将使用 GWAS()
函数,该函数位于 rrBLUP
包中。我们的样本数据文件包含三个 SNP——出于教学目的,这有助于我们的编程任务,但对于 GWAS 研究来说,数量显然过于微小。虽然代码可以运行,但结果在生物学上没有实际意义。
我们需要 rrBLUP
,它不是 Bioconductor 的一部分,因此需要通过 install.packages()
安装,同时还需要 VariantAnnotation
和 datasets/ch2/small_sample.vcf
文件。
如何实现...
通过 GWAS 查找表型和基因型关联可以通过以下步骤完成:
- 加载库并获取 VCF 文件:
library(VariantAnnotation)
library(rrBLUP)
set.seed(1234)
vcf_file <- file.path(getwd(), "datasets", "ch2", "small_sample.vcf")
vcf <- readVcf(vcf_file, "hg19")
- 提取基因型、样本和标记位置信息:
gts <- geno(vcf)$GT
samples <- samples(header(vcf))
markers <- rownames(gts)
chrom <- as.character(seqnames(rowRanges(vcf)))
pos <- as.numeric(start(rowRanges(vcf)))
- 创建一个自定义函数,将 VCF 基因型转换为 GWAS 函数使用的约定:
convert <- function(v){
v <- gsub("0/0", 1, v)
v <- gsub("0/1", 0, v)
v <- gsub("1/0", 0, v)
v <- gsub("1/1",-1, v)
return(v)
}
- 调用函数并将结果转换为数值矩阵:
gt_char<- apply(gts, convert, MARGIN = 2)
genotype_matrix <- matrix(as.numeric(gt_char), nrow(gt_char) )
colnames(genotype_matrix)<- samples
- 构建描述变异的数据框:
variant_info <- data.frame(marker = markers,
chrom = chrom,
pos = pos)
- 构建一个合并的变异/基因型数据框:
genotypes <- cbind(variant_info, as.data.frame(genotype_matrix))
genotypes
- 构建一个
phenotype
数据框:
phenotypes <- data.frame(
line = samples,
score = rnorm(length(samples))
)
phenotypes
- 运行
GWAS
:
GWAS(phenotypes, genotypes,plot=FALSE)
它是如何工作的...
本例中的大部分代码都是设置代码。在加载库并使用 set.seed()
固定随机数生成器以确保结果可重复之后,第一步是加载有用变异的 VCF 文件,第二步是提取一些有用的信息:我们获取一个基因型矩阵,使用 geno(vcf)$GT
调用,它返回一个矩阵,其中每一行是一个变异,每一列是一个样本,基因型记录在交点处。然后,我们使用一些访问器函数提取样本和标记名称,以及每个变异的参考序列(chrom
)和位置(pos
)。在步骤 3 中,我们定义一个转换函数(convert()
),将 VCF 风格的杂合和纯合标注映射到 GWAS()
使用的格式。简而言之,在 VCF 中,"0/0"
表示 AA(纯合),在 GWAS()
中编码为 1,"0/1"
和 "1/0"
表示杂合 Aa 或 0,而 "1/1"
表示纯合 aa
或 -1。
在步骤 4中,我们将convert()
应用于gts
矩阵。烦人的是,返回值是一个字符矩阵,必须转换为数值型并重新包装成矩阵,这就是步骤 4中最后几行的目的。
在步骤 5中,我们构建一个数据框,描述我们之前创建的样本、标记和序列信息中的变异信息,而在步骤 6中,我们实际上将变异信息与基因型编码结合起来。
这将产生以下输出:
## marker chrom pos NA00001 NA00002 NA00003
## 1 rs6054257 20 14370 1 0 -1
## 2 20:17330_T/A 20 17330 1 0 1
## 3 20:1230237_T/G 20 1230237 1 1 0
请注意,列的顺序很重要。GWAS()
函数期望我们按照这里指定的顺序提供这些信息。
在步骤 7中,我们构建了表型信息。第一列必须命名为line
,并包含与基因型矩阵列相同顺序的样本名称。其余列可以是表型分数,并具有固定效应。
这将产生类似以下的输出(如果你在脚本顶部省略了set.seed()
调用,由于随机化过程和示例数据中的小样本量,你的实际数字可能会有所不同):
## line score
## 1 NA00001 -1.2070657
## 2 NA00002 0.2774292
## 3 NA00003 1.0844412
最后,在步骤 8中,我们运行GWAS()
函数。
这将产生以下输出(同样,你的数字可能会有所不同):
## [1] "GWAS for trait: score"
## [1] "Variance components estimated. Testing markers."
## marker chrom pos score
## 1 rs6054257 20 14370 0.3010543
## 2 20:17330_T/A 20 17330 0.3010057
## 3 20:1230237_T/G 20 1230237 0.1655498
默认情况下,函数尝试生成一个图形。由于数据点太少,无法生成图形,因此我们在此通过plot = FALSE
关闭该功能。
估计一个感兴趣位点的拷贝数
通常,我们会关心一个序列在样本中的出现频率——也就是估计在你的特定样本中,某个位点是否发生了重复,或其拷贝数是否增加。该位点可以是 Kbp 尺度上的基因,也可以是 Mbp 尺度上的较大 DNA 片段。我们在这个食谱中的方法是,在比对后的 HTS 读取覆盖度基础上估算背景覆盖度,然后检查我们感兴趣区域的覆盖度。我们感兴趣区域的覆盖度与背景水平的比率将为我们提供该区域的拷贝数估计。这个食谱是第一步。我们使用的背景模型非常简单——仅计算一个全局均值,但稍后我们将讨论一些替代方法。此外,这个食谱不涉及倍性——即细胞中整个基因组的拷贝数。通过类似的数据(尤其是 SNP 的主要/次要等位基因频率)可以估算倍性,但这是一个非常复杂的流程。请查看另请参阅部分,了解关于用于该长期分析的包的建议。
准备工作
对于这个食谱,我们需要csaw
Bioconductor 包和样本hg17
人类基因组.bam
文件,文件位于datasets/ch2/hg17_snps.bam
。
如何实现...
估计一个感兴趣位点的拷贝数可以通过以下步骤完成:
- 加载库并获取基因组窗口中的计数:
library(csaw)
whole_genome <- csaw::windowCounts(
file.path(getwd(), "datasets", "ch2", "hg17_snps.bam"),
bin = TRUE,
filter = 0,
width = 100,
param = csaw::readParam( minq = 20, dedup = TRUE, pe = "both" )
)
colnames(whole_genome) <- c("h17")
- 从
SummarizedExperiment
中提取数据:
counts <- assay(whole_genome)[,1]
- 计算一个低计数阈值,并将计数较低的窗口设置为
NA
:
min_count <- quantile(counts, 0.1)[[1]]
counts[counts < min_count] <- NA
- 在中间的一组窗口中翻倍计数——这些将作为我们的高拷贝数区域:
n <- length(counts)
doubled_windows <- 10
left_pad <- floor( (n/2) - doubled_windows )
right_pad <- n - left_pad -doubled_windows
multiplier <- c(rep(1, left_pad ), rep(2,doubled_windows), rep(1, right_pad) )
counts <- counts * multiplier
- 计算每个窗口的平均覆盖度以及该窗口与平均覆盖度的比值,并通过绘图检查该比值向量:
mean_cov <- mean(counts, na.rm=TRUE)
ratio <- matrix(log2(counts / mean_cov), ncol = 1)
plot(ratio)
- 使用新数据和旧数据的行数据构建
SummarizedExperiment
:
se <- SummarizedExperiment(assays=list(ratio), rowRanges= rowRanges(whole_genome), colData = c("CoverageRatio"))
- 创建感兴趣区域并提取其中的覆盖数据:
region_of_interest <- GRanges(
seqnames = c("NC_000017.10"),
IRanges(c(40700), width = c(1500) )
)
overlap_hits <- findOverlaps(region_of_interest, se)
data_in_region <- se[subjectHits(overlap_hits)]
assay(data_in_region)
它是如何工作的...
在步骤 1中,本配方以熟悉的方式开始,使用csaw
包在我们的人类第 17 号染色体小段上获取 100 bp 窗口的读取计数。读取过滤选项在param
参数中设置。在步骤 2中,我们提取数据的第一列(也是唯一一列),使用assay()
函数和子集提取来获得一个简单的计数向量。接下来,在步骤 3中,我们使用quantile()
函数从counts
向量中获取低于 10^(百分位)的min_count
值。需要使用双括号子集提取,以便从quantile()
函数返回的命名向量中获得单一数字。min_count
值将作为切断点。counts
向量中低于该值的所有值都将被设置为NA
,以便从分析中移除——这充当了一个低覆盖阈值,并且可以根据需要在您自己修改的配方中调整使用的百分位数。
在步骤 4中,我们添加了一些覆盖度翻倍的区域——以便我们能检测到它们。我们选择了一些窗口,在其中翻倍计数,然后创建一个与计数长度相等的multiplier
向量,其中1表示我们不希望更改计数,2表示我们希望将其翻倍。然后我们应用乘法。步骤 4可能会在您的分析中省略,因为它是一个合成数据生成步骤。
在步骤 5中,我们实际计算背景覆盖度。我们这里的函数是一个简单的全局平均,保存在mean_cov
中——但您可以使用许多其他函数。有关此的讨论,请参见另见部分。我们还计算每个窗口计数与全局mean_cov
的比值的log2()
,并将其保存在一个名为ratio
的单列矩阵对象中——因为我们需要将结果保存为矩阵形式,才能用于最终的SummarizedExperiment
对象。我们快速使用plot()
来检查ratio
,并可以清晰地看到数据中间窗口计数翻倍的情况。
这将产生以下输出:
在步骤 6中,我们构建一个新的SummarizedExperiment
对象se
,用于保存窗口范围和新的比值数据。我们从window_counts
中提取GRanges
和colData
对象,并添加我们新的ratio
矩阵。现在我们可以开始对其进行子集提取,查看感兴趣区域的覆盖情况。
在第 7 步中,我们构造了一个手动的GRanges
对象,表示我们感兴趣的任意区域,称为region_of_interest
,并使用它通过findOverlaps()
找到se
对象中重叠的窗口。然后我们使用结果中的overlap_hits
向量对子集se
对象,并通过assay()
函数查看该兴趣区域的计数。
这将产生以下输出:
## [,1]
## [1,] 0.01725283
## [2,] 0.03128239
## [3,] -0.05748994
## [4,] 0.05893873
## [5,] 0.94251006
## [6,] 0.88186246
## [7,] 0.87927929
## [8,] 0.63780103
## [9,] 1.00308550
## [10,] 0.75515798
## [11,] 0.80228189
## [12,] 1.05207419
## [13,] 0.82393626
## [14,] NA
## [15,] NA
## [16,] -0.16269298
在输出中,我们可以看到该区域相对于背景大约有一个 log2 比例为 1(二倍)的覆盖度,我们可以将其解读为拷贝数为 2。
另见
该配方中的背景水平计算非常简单——这对于学习配方是很好的,但在你自己真实数据中可能很快就不够用了。你可以采用许多选项来修改计算背景水平的方式,以适应你自己的数据。查看zoo
包中的rollmeans()
和rollmedians()
函数——这些函数可以在任意步长的滚动窗口中给出均值和中位数,能为你提供一个移动窗口的背景平均值,可能会更加合适。
与拷贝数相关的分析之一是从 SNP 等位基因频率估计倍性。你可以查看vcfR
包中的freq_peaks()
函数,作为从BAM
文件中的变异信息估计倍性的起点。
第三章:搜索基因和蛋白质的结构域与模体
基因、蛋白质和整个基因组的序列包含了它们功能的线索。重复的子序列或相互之间高度相似的序列,可能是进化保守性或功能相关性的线索。因此,针对模体和结构域的序列分析是生物信息学的核心技术。Bioconductor 包含了许多有用的包,用于分析基因、蛋白质和基因组。在本章中,你将学习如何使用 Bioconductor 分析序列中具有功能意义的特征,例如从广泛使用的数据库中提取的 de novo DNA 模体和已知结构域。你将学习一些基于核函数的机器学习包,用于发现蛋白质序列特征。你还将学习一些大规模比对技术,用于处理非常多或非常长的序列。你将使用 Bioconductor 和其他统计学习包。
本章将涉及以下配方:
-
使用 universalmotif 查找 DNA 模体
-
使用 PFAM 和 bio3d 查找蛋白质结构域
-
查找 InterPro 结构域
-
执行基因或蛋白质的多重比对
-
使用 DECIPHER 对基因组长度序列进行比对
-
用于蛋白质新特征检测的机器学习
-
使用 bio3d 进行蛋白质的 3D 结构比对
技术要求
你需要的样本数据可以从本书的 GitHub 仓库获取:github.com/danmaclean/R_Bioinformatics_Cookbook
[.](https://github.com/danmaclean/R_Bioinformatics_Cookbook)如果你想直接使用书中的代码示例,则需要确保这些数据位于你工作目录的子目录中。
以下是你将需要的 R 包。大多数包可以通过 install.packages()
安装;其他包则稍微复杂一些:
-
ape
-
Bioconductor
:-
Biostrings
-
biomaRt
-
DECIPHER
-
EnsDb.Rnorvegicus.v79
-
kebabs
-
msa
-
org.At.tair.db
-
org.Eck12.db
-
org.Hs.eg.db
-
PFAM.db
-
universalmotif
-
-
bio3d
-
dplyr
-
e1071
-
seqinr
Bioconductor 是一个庞大的工具集,具有自己的安装管理器。你可以使用以下代码进行安装:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
更多信息请访问 www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接按名称使用函数。这在交互式会话中非常方便,但当加载了许多包时,可能会引起混淆。为了澄清我在某一时刻使用的是哪个包和函数,我会偶尔使用 packageName::functionName()
的约定。
有时,在一个过程的中途,我会中断代码,方便你查看一些中间输出或是需要理解的对象结构。每当发生这种情况时,你会看到一个代码块,其中每行前面都有 ##
双井号符号。请参考以下命令:
letters[1:5]
这将给我们如下的输出——请注意,输出行以##
为前缀:
## a b c d e
本章中我们要使用的一些软件包依赖于必须单独安装的第三方软件。在 Windows、Linux 或 macOS 上安装和管理生物信息学软件的一种极好的方式是使用 conda 包管理器,并结合 bioconda 包频道。你可以通过一些简单的命令安装大量软件。要安装这两者,首先阅读bioconda.github.io/
上的当前安装说明。
使用 universalmotif 查找 DNA motifs
在处理 DNA 序列时,一个非常常见的任务是查找 motif 实例——即在较长序列中定义的短序列。这些可能代表蛋白质- DNA 结合位点,例如基因启动子中的转录因子结合位点或增强区域。在此分析中有两种起点:要么你有一个 motif 数据库,想用它来扫描目标 DNA 序列并提取出现的 motif,要么你只有感兴趣的序列,想要查找其中是否有任何重复的 motif。在本食谱中,我们将讨论如何执行这两项操作。我们将在这两种情况下都使用universalmotif
包。
准备工作
本食谱中,我们需要datasets/ch3/simple_motif.txt
和datasets/ch3/promoters.fa
文件,一个描述简单 motif 的简单矩阵,格式为位置特异性权重矩阵(PSWM)(简要描述请参见附录),以及一组来自转录起始位点上游的序列。
本食谱还需要在你的系统中有一个有效的MEME
副本。MEME
是一个用于在序列集中过度表现的统计序列 motif 的程序。当用于启动子或基因上游区域时,这些 motif 可能代表转录因子结合位点。MEME
的网页地址为alternate.meme-suite.org/
,如果你已经安装了 conda,可以通过conda install -c bioconda meme
来安装。MEME
包无法在 Windows 系统上使用。如果你希望在 Windows 上运行它,可以考虑在 Cygwin(Linux 模拟层)下运行它:www.cygwin.com/
。你可能还需要在 Cygwin 下安装新的 R 版本。
如何操作...
使用universalmotif
查找 DNA motifs 可以按照以下步骤进行:
- 首先,加载所需的库和感兴趣的 motif:
library(universalmotif)
library(Biostrings)
motif <- read_matrix(file.path(getwd(), "datasets", "ch3","simple_motif.txt"))
- 然后,加载要用该 motif 扫描的序列:
sequences <- readDNAStringSet(file.path(getwd(), "datasets", "ch3", "promoters.fa"))
- 执行序列扫描:
motif_hits <- scan_sequences(motif, sequences = sequences)
motif_hits
请注意,motif_hits
包含关于 motif 在每个目标序列中位置的信息。
- 计算该结构是否在序列中富集:
motif_info <- enrich_motifs(motif, sequences, shuffle.k = 3, verbose = 0, progress = FALSE, RC = TRUE)
motif_info
请注意,motif 信息包含关于在一组序列中统计富集的信息。
- 运行
MEME
以查找新型 motif:
meme_path = "/Users/macleand/miniconda2/bin/meme"
meme_run <- run_meme(sequences, bin = meme_path, output = "meme_out", overwrite.dir = TRUE)
motifs <- read_meme("meme_out/meme.txt")
view_motifs(motifs)
工作原理...
这段代码真是太棒了!仅用几行代码,我们就完成了整个分析。我们首先加载了一个基序的矩阵描述和一些我们希望找到启动子的序列——这发生在步骤 1 和 2 中,结果我们得到了一个universalmotif
对象和一个DNAStringSet
对象。接下来的真正工作发生在步骤 3 和 4 中。scan_sequences()
函数会搜索每个序列并报告找到基序的位置——查看motif_hits
对象,看看它们在哪里。
这将产生以下输出:
## motif sequence start stop score max.score score.pct
## 1 YTTTYTTTTTYTTTY AT4G28150 73 87 7.531 22.45824 33.53335
## 2 YTTTYTTTTTYTTTY AT4G28150 75 89 10.949 22.45824 48.75270
在判断一个基序是否显著时,universalmotifs
包中的enrich_motifs()
函数会在第 4 步为我们完成这项工作,结果会产生以下输出:
## motif total.seq.hits num.seqs.hit num.seqs.total
## 1 YTTTYTTTTTYTTTY 916 50 50
## total.bkg.hits num.bkg.hit num.bkg.total Pval.hits Qval.hits
## 1 265 48 50 4.75389e-85 4.75389e-85
## Eval.hits
## 1 9.50778e-85
它会搜索序列,寻找可能出现基序的地方,并对其进行计数,执行 Fisher 精确检验,比较我们的序列集中的基序频率与自动生成的背景集中的基序频率。最终的motif_info
输出包含一个p值报告。为了找到新的基序,我们在第 5 步运行外部软件MEME。run_meme()
函数需要知道MEME包在你系统中的位置,因此我们需要在meme_path
变量中定义该路径。
请注意,系统中meme_path
的值与此处提到的值会有所不同——这是我系统中的一个示例。
我们将这些信息传递给函数,并提供包含序列的DNAStringSet
对象。该函数还需要一个输出目录,用于写入MEME
的结果,因为它不会返回任何有用的信息给 R。run_meme()
函数会在后台执行MEME
,一旦运行完成,我们可以通过read_meme()
函数和文件名从meme.txt
文件中加载结果。它会返回一个universalmotif
对象。最后,在这里,我们通过view_motifs()
函数快速查看motifs
对象:
这会给我们一个非常直观的基序可视化。
还有更多内容...
从现有的数据库(如 JASPAR 和 TRANSFAC)中加载基序非常简单,使用universalmotif
时,只需对read_matrix()
函数做直接替换。查看以下函数,它们可以从不同格式中加载基序:read_cisbp()
、read_homer()
、read_jaspar()
、read_matrix()
、read_meme()
、read_motifs()
和 read_uniprobe()
。
使用 PFAM 和 bio3d 查找蛋白质结构域
发现蛋白质序列的功能是一个关键任务。我们可以通过多种方式来完成这项任务,包括使用 BLAST 等工具对已知蛋白质数据库进行全序列相似性搜索。如果我们希望获得更具信息量和更精细的资料,可以选择在序列中查找个体功能域。Pfam
等数据库和hmmer
等工具使得这一过程成为可能。Pfam
将蛋白质结构域编码为配置文件隐马尔可夫模型,而hmmer
则使用这些模型扫描序列并报告任何可能存在的结构域。通常,基因组注释项目会为我们完成这些搜索,这意味着要在我们的序列中找到Pfam
结构域,关键就在于搜索数据库。Bioconductor 在这些特定数据库的数据包装方面做得非常出色,通常是以.db
为后缀的包。在本配方中,我们将展示如何判断一个包是否包含 Pfam 结构域信息,如何为特定基因提取这些信息,以及如果没有现成的信息时,如何自行运行 Pfam 搜索。
正在准备中
对于这个示例,我们需要一些 Bioconductor 的Annotationdbi
数据库包—具体来说,org.Hs.eg.db
、org.EcK12.eg.db
和org.At.tair.db
。
你还需要bio3d
包,当前版本—在写这篇文档时—只有在使用开发版时,bio3d
才会连接到 Pfam 服务器。你可以通过devtools
包从 BitBucket 安装该版本:
install.packages("devtools")
library(devtools)
install_bitbucket("Grantlab/bio3d", subdir = "ver_devel/bio3d/")
如何操作...
使用PFAM.db
和bio3d
查找蛋白质结构域,可以通过以下步骤完成:
- 加载数据库包并检查数据库中的键类型:
library(org.Hs.eg.db)
keytypes(org.Hs.eg.db)
注意这个输出中的ENSEMBL
键—我们可以用它来查询数据库。
- 使用
keys()
函数获取键的向量:
k <- head(keys(org.Hs.eg.db, keytype = "ENSEMBL"), n = 3 )
- 查询数据库:
result <- select(org.Hs.eg.db, keys = k, keytype="ENSEMBL", columns = c("PFAM"))
result
- 加载
PFAM
数据库以提取描述信息:
library(PFAM.db)
descriptions <- PFAMDE
- 从
PFAM
数据库中获取所有键:
all_ids <- mappedkeys(descriptions)
- 获取
PFAM
ID 的所有描述:
id_description_mapping <- as.data.frame(descriptions[all_ids])
- 将描述信息与
PFAM
关联:
dplyr::left_join(result, id_description_mapping, by = c("PFAM" = "ac") )
工作原理...
这种方法的关键在于找出我们使用的数据库是否确实包含 PFAM 域信息。这就是我们在步骤 1 中做的—我们使用keytypes()
函数列出可用的搜索键。PFAM 出现在结果中。一旦我们确认可以使用此数据库来获取我们需要的信息,就可以遵循一个相当标准的流程:
- 获取查询用的键列表—比如基因名。这里我们直接从数据库中提取它们,但它们也可以来自其他地方。这将产生以下输出:
## [1] "ACCNUM" "ALIAS" "ENSEMBL" "ENSEMBLPROT"
## [5] "ENSEMBLTRANS" "ENTREZID" "ENZYME" "EVIDENCE"
## [9] "EVIDENCEALL" "GENENAME" "GO" "GOALL"
## [13] "IPI" "MAP" "OMIM" "ONTOLOGY"
## [17] "ONTOLOGYALL" "PATH" "PFAM" "PMID"
## [21] "PROSITE" "REFSEQ" "SYMBOL" "UCSCKG"
## [25] "UNIGENE" "UNIPROT"
-
使用
select()
函数查询数据库,该函数根据提供的键拉取数据。columns
参数告诉它要拉取哪些数据。这里的表达式将获取我们感兴趣基因的 PFAM ID。 -
制作所有 PFAM ID 和描述的列表。我们加载
PFAM.db
包,并使用它提供的PFAMDE
对象来获取 ID 和描述之间的映射。这将产生以下输出。请注意,因为我们从外部数据库中提取数据,数据库的更改可能会在这里反映出来:
## ENSEMBL PFAM
## 1 ENSG00000121410 PF13895
## 2 ENSG00000175899 PF01835
## 3 ENSG00000175899 PF07678
## 4 ENSG00000175899 PF10569
## 5 ENSG00000175899 PF07703
## 6 ENSG00000175899 PF07677
## 7 ENSG00000175899 PF00207
## 8 ENSG00000256069 <NA>
-
然后,我们可以使用
mappedkeys()
函数获取实际的描述信息对象。 -
接下来,提取和转换
all_ids
对象的描述为数据框。 -
最后,我们将 PFAM 域的描述与之前获得的 PFAM ID 进行连接,使用具有共同数据的列——
PFAM
和ac
。这将产生以下输出:
## ENSEMBL PFAM de
## 1 ENSG00000121410 PF13895 Immunoglobulin domain
## 2 ENSG00000175899 PF01835 MG2 domain
## 3 ENSG00000175899 PF07678 A-macroglobulin TED domain
## 4 ENSG00000175899 PF10569 <NA>
## 5 ENSG00000175899 PF07703 Alpha-2-macroglobulin bait region domain
## 6 ENSG00000175899 PF07677 A-macroglobulin receptor binding domain
## 7 ENSG00000175899 PF00207 Alpha-2-macroglobulin family
## 8 ENSG00000256069 <NA> <NA>
还有更多...
我提到食谱的关键部分——特别是第 6 步的连接——是确保数据库包含正确的键,特别是 PFAM,以便继续进行。根据生物体和数据库的不同,PFAM 注释可能不存在。以下是如何检查你感兴趣的数据库中是否存在它,使用了两个示例数据库 org.At.tair.db
和 org.Eck12.eg.db
,一个拟南芥数据库:
library(org.At.tair.db)
columns(org.At.tair.db)
还需要一个 E.coli 数据库:
library(org.EcK12.eg.db)
columns(org.EcK12.eg.db)
只需使用 columns()
函数报告数据库中的数据列。如果 PFAM 出现,你可以按照以下步骤进行。如果未出现,则可以作为替代方案,在服务器上运行 PFAM 并自行进行注释。以下代码使用 bio3d
函数 hmmer()
在 EBI 服务器上对输入的蛋白质序列进行 PFAM 搜索。返回的对象在 hit.tbl
槽中包含 PFAM 输出的数据帧:
sequence <- read.fasta(file.path(getwd(), "datasets", "ch3", "ecoli_hsp.fa") )
# run pfamseq on protein
result <- hmmer(sequence, type="hmmscan", db="pfam")
result$hit.tbl
这将产生以下输出:
## name acc bias dcl desc evalue flags hindex ndom nincluded
## 1 GrpE PF01025.19 3.3 272 GrpE 1.4e-46 3 8846 1 1
## nregions nreported pvalue score taxid pdb.id bitscore mlog.evalue
## 1 1 1 -115.4076 158.2 0 PF01025.19 158.2 105.5824
查找 InterPro 域
InterPro 是由多个蛋白质数据库提供的预测模型或签名的数据库。InterPro 聚合来自多个来源的信息,以减少注释的冗余性并帮助解释性。在这个示例中,我们将扩展我们用于 PFAM 域的方法,查看感兴趣序列上的 InterPro 域的注释。我们将从 Ensembl 核心数据库开始。
准备工作
我们需要 ensembldb
、Ensdb.Rnorvegicus.v79
和 biomaRt
这几个 Bioconductor 包。
如何操作...
可以通过以下步骤找到 InterPro 蛋白质域:
- 加载库并仔细检查我们的数据库包是否携带所需的蛋白质数据:
library(ensembldb)
library(EnsDb.Rnorvegicus.v79)
hasProteinData(EnsDb.Rnorvegicus.v79)
- 构建要查询的基因列表——请注意,这里我需要的
keytype
是GENEID
:
e <- EnsDb.Rnorvegicus.v79
k <- head(keys(e, keytype = "GENEID"), n = 3 )
- 使用
select()
函数提取相关数据:
select(e, keys = GeneIdFilter(k),
columns = c("TXBIOTYPE", "UNIPROTID", "PROTEINID","INTERPROACCESSION"))
工作原理...
代码是通过相关包在特定的 Rattus norvegicus Ensembl 数据库中进行数据库查找。该过程与 PFAM 域搜索类似:
- 我们使用
EnsemblDB
包特定的hasProteinData()
函数来检查数据库是否包含我们需要的信息。如果输出是TRUE
,则很好:
## [1] TRUE
-
我们再次构建感兴趣的基因列表——这里我从数据库中提取列表,但这些 ID 可以来自任何地方。
-
最后,我们用感兴趣的基因作为关键字搜索数据库。 请注意,我们需要
GeneIdFilter()
函数包装器和columns
参数来选择要返回的数据。 这将导致一个包含以下信息的数据框架:
## TXBIOTYPE UNIPROTID PROTEINID INTERPROACCESSION GENEID
## 1 protein_coding Q32KJ7 ENSRNOP00000052495 IPR017850 ENSRNOG00000000001
## 2 protein_coding Q32KJ7 ENSRNOP00000052495 IPR000917 ENSRNOG00000000001
## 3 protein_coding C9E895 ENSRNOP00000000008 IPR015424 ENSRNOG00000000007
这里还有更多内容……
我们在这个配方中使用的方法非常适合 Ensembl 核心数据库,但还有其他非 Ensembl 核心数据库,我们可能想要搜索; 对此,有 biomaRt。 biomaRt 允许我们定义到我们可能知道的其他数据库的连接。 许多这些数据库公开了我们可以用来查询它们的 API。 为此,加载biomaRt
库并使用useMart()
函数来定义与适当主机和数据集的连接。 然后,使用连接和列以及基因 ID 使用getBM()
函数进行查询。 如果您的查询是interpro
,则会返回 InterPro 的搜索结果。 以下示例在plants.ensembl.org
上为两个拟南芥基因进行搜索:
library(biomaRt)
biomart_athal <- useMart(biomart = "plants_mart", host = "plants.ensembl.org", dataset = "athaliana_eg_gene")
getBM( c("tair_locus", "interpro"), filters=c("tair_locus"), values = c("AT5G40950", "AT2G40510"), mart = biomart_athal)
这将返回以下输出:
## tair_locus interpro
## 1 AT2G40510 IPR000892
## 2 AT2G40510 IPR038551
## 3 AT5G40950 IPR001684
## 4 AT5G40950 IPR018261
另请参阅……
如果您在确定 marts 和 columns 的名称时遇到问题,请尝试来自bioMart的listMarts()
和listDatasets()
函数,它们将提供当前可用 marts 和它们包含的数据列表。
执行基因或蛋白质的多重比对
在构建系统发育树之前或作为确定保守和分散区域的结束任务中,对序列进行对齐是生物信息学分析的重要组成部分,并且在 R 和 Bioconductor 中使用ape和DECIPHER包广泛涵盖了从序列到对齐的极为简单的程序。 在这个配方中,我们将看看如何实现这一过程。
请注意,不同的序列长度有不同的技术。 在这个第一个配方中,我们将看看如何对齐一些 Kbp 长度的序列,例如代表基因和蛋白质的序列。
准备就绪
此配方需要msa
包。 这是一个相当庞大的包,包括外部软件:Clustal、Clustal Omega 和 Muscle。 还需要ape
和seqinR
包。 作为测试数据集,我们将使用存储在书籍数据和代码存储库中的一些血红蛋白蛋白质序列,路径为datasets/ch3/hglobin.fa
。 您的系统中还需要 PDFLatex。 您可以在此处找到安装信息:www.latex-project.org/get/
。
如何做到这一点……
可以通过以下步骤执行基因或蛋白质的多重比对:
- 加载库和序列:
library(msa)
seqs <- readAAStringSet(file.path(getwd(), "datasets", "ch3", "hglobin.fa"))
seqs
- 执行多序列比对:
alignment <- msa(seqs, method = "ClustalOmega")
alignment
这将返回一个如下所示的对齐对象:
## ClustalOmega 1.2.0
##
## Call:
## msa(seqs, method = "ClustalOmega")
##
## MsaAAMultipleAlignment with 3 rows and 142 columns
## aln names
## [1] MVLSPADKTNVKAAWGKVGAHAG...PAVHASLDKFLASVSTVLTSKYR HBA_HUMAN
## [2] MVLSGEDKSNIKAAWGKIGGHGA...PAVHASLDKFLASVSTVLTSKYR HBA_MOUSE
## [3] MSLTRTERTIILSLWSKISTQAD...ADAHAAWDKFLSIVSGVLTEKYR HBAZ_CAPHI
## Con MVLS??DKTNIKAAWGKIG?HA?...PAVHASLDKFLASVSTVLTSKYR Consensus
- 使用以下代码查看结果:
msaPrettyPrint(alignment, output="pdf", showNames="left",
showLogo="none", askForOverwrite=FALSE, verbose=FALSE, file="whole_align.pdf")
- 使用以下代码查看放大区域:
msaPrettyPrint(alignment, c(10,30), output="pdf", showNames="left",
file = "zoomed_align.pdf", showLogo="top", askForOverwrite=FALSE, verbose=FALSE)
如何运行它……
这里的配方简单而明了——使用msa
执行 MSA 非常简单。 在步骤 1 中,我们使用常见的readAAStringSet()
函数加载包和序列,以给出seqs
——一个AAStringSet
对象,我们可以检查并获取以下输出:
## A AAStringSet instance of length 3
## width seq names
## [1] 142 MVLSPADKTNVKAAWGKVGAH...HASLDKFLASVSTVLTSKYR HBA_HUMAN
## [2] 142 MVLSGEDKSNIKAAWGKIGGH...HASLDKFLASVSTVLTSKYR HBA_MOUSE
## [3] 142 MSLTRTERTIILSLWSKISTQ...HAAWDKFLSIVSGVLTEKYR HBAZ_CAPHI
接下来,在步骤 2中,将msa()
函数传入seqs
对象和对齐方法的名称。这里我们使用ClustalOmega
(你可以选择ClustalOmega
、ClustalW
或Muscle
)。method
参数指定了用于实际对齐的外部程序的名称。对齐器运行后,你将获得一个MsaMultipleAlignment
对象——这是一个容器,包含了对齐后的序列,格式如下:
## ClustalOmega 1.2.0
##
## Call:
## msa(seqs, method = "ClustalOmega")
##
## MsaAAMultipleAlignment with 3 rows and 142 columns
## aln names
## [1] MVLSPADKTNVKAAWGKVGAHAG...PAVHASLDKFLASVSTVLTSKYR HBA_HUMAN
## [2] MVLSGEDKSNIKAAWGKIGGHGA...PAVHASLDKFLASVSTVLTSKYR HBA_MOUSE
## [3] MSLTRTERTIILSLWSKISTQAD...ADAHAAWDKFLSIVSGVLTEKYR HBAZ_CAPHI
## Con MVLS??DKTNIKAAWGKIG?HA?...PAVHASLDKFLASVSTVLTSKYR Consensus
在步骤 3 中,我们使用msaPrettyPrint()
函数将对齐的可视化结果写入 PDF 文件。该函数有多个参数,用于描述对齐图的布局。可视化结果必须写入文件,不能像普通的绘图对象那样发送到交互式会话的绘图窗口中。图片存放的文件由file
参数指定。生成的图片如下:
在步骤 4 中,我们使用第二个位置参数,通过c(10,30)
向量将视图限制在 10 到 30 之间。我们得到以下图片:
不幸的是,由于图片制作过程在后台使用了 Latex,因此我们无法将图片强制转换为比 PDF 更有用的格式,或像其他绘图对象一样进行渲染。
还有更多...
在这个阶段,树形可视化的序列相似性常常非常有用。我们可以使用ape
和seqinr
包生成这种可视化。我们可以将对齐对象转换为描述序列距离的seqinr distance
对象,并从中使用ape
创建一个简单的邻接树,然后进行绘制:
library(ape)
library(seqinr)
alignment_seqinr <- msaConvert(alignment, type="seqinr::alignment")
distances <- seqinr::dist.alignment(alignment_seqinr, "identity")
tree <- ape::nj(distances)
plot(tree, main="Phylogenetic Tree of HBA Sequences")
这将产生以下输出:
使用 DECIPHER 对齐基因组长度序列
对齐比基因和蛋白质更长的序列,例如来自组装项目的 contigs、染色体或整个基因组,是一个棘手的任务,并且需要不同于短序列的技术。序列越长,越难对齐。长序列的对齐尤其在计算时间上非常耗费,因为对于短序列有效的算法,随着序列长度的增加,其计算时间呈指数级增长。执行长序列的对齐通常从寻找短的锚对齐开始,然后从那里进行扩展。我们通常会得到同源块——在不同基因组对齐之间匹配较好的区域。
在这个教程中,我们将介绍DECIPHER
包用于基因组长度的对齐。我们将使用一些叶绿体基因组——约 150 Kbp 长的小型细胞器基因组,这些基因组在进化上相对保守,作为我们的目标序列。
准备就绪
确保你已经安装了DECIPHER
包。我们将使用datasets/ch3/plastid_genomes.fa
文件作为示例。
如何进行...
使用DECIPHER
对齐基因组长度序列可以按照以下步骤进行:
- 加载库和基因组序列:
library(DECIPHER)
long_seqs <- readDNAStringSet(file.path(getwd(), "datasets", "ch3", "plastid_genomes.fa"))
long_seqs
- 在本地数据库中准备序列:
Seqs2DB(long_seqs, "XStringSet", "long_db", names(long_seqs))
- 查找
synteny
区块:
synteny <- FindSynteny("long_db")
pairs(synteny)
这将创建一个同源区块的点图。
- 绘制
syntenic
区块:
plot(synteny)
- 现在,进行实际的比对:
alignment <- AlignSynteny(synteny, "long_db")
- 然后逐一保存配对比对结果:
blocks <- unlist(alignment[[1]])
writeXStringSet(blocks, "genome_blocks_out.fa")
它是如何工作的...
DECIPHER 包非常强大,因此在进行实际分析之前,需要进行一些设置。在步骤 1 中,我们加载库并将序列加载到 long_seqs
,一个 DNAStringSet 对象中;但在步骤 2 中,我们为后续步骤构建了一个额外的磁盘 SQLite 数据库。这是通过 Seqs2DB()
函数完成的,该函数接受 long_seqs
(输入类型为 XStringSet,DNAStringSet 的父类)、数据库的名称(long_db
)以及一个序列名称的向量,我们通过 names()
函数提取该向量。一旦数据库构建完成,我们就可以在以下工作流中使用它:
- 使用
FindSynteny()
函数在数据库中查找同源区块。此操作将产生以下输出:
## A DNAStringSet instance of length 5
## width seq names
## [1] 130584 GGCATAAGCTATCTTCCCAA...GATTCAAACATAAAAGTCCT NC_018523.1 Sacch...
## [2] 161592 ATGGGCGAACGACGGGAATT...AGAAAAAAAAATAGGAGTAA NC_022431.1 Ascle...
## [3] 117672 ATGAGTACAACTCGAAAGTC...GATTTCATCCACAAACGAAC NC_022259.1 Nanno...
## [4] 154731 TTATCCATTTGTAGATGGAA...TATACACTAAGACAAAAGTC NC_022417.1 Cocos...
## [5] 156618 GGGCGAACGACGGGAATTGA...TTTTGTAGCGAATCCGTTAT NC_022459.1 Camel...
- 使用同源区块作为种子,并通过
AlignSynteny()
函数执行实际的比对。
这些操作在步骤 3 和 5 中完成。FindSynteny()
需要数据库的名称;AlignSynteny()
需要 synteny
对象和数据库名称。
最后,我们可以输出结果。带有 synteny
对象的 pairs()
函数将创建一个同源区块的点图:
使用带有 synteny
对象的 plot()
函数,可以创建一个有用的热图,显示同源区块相对于第一个基因组的位置。跨基因组的相同颜色区域表示同源序列区域:
最后一步,步骤 6,是稍微复杂的保存过程。alignment
对象是一个 R 列表,每个成员表示一个比对——它本身也是一个列表。通过提取并对每个返回的元素使用 unlist()
,你将得到一个可以用 writeXStringSet()
保存为典型 FASTA 比对的对象(blocks
)。记住,你需要分别为 blocks
对象的每个成员执行此操作。
用于蛋白质中新特征检测的机器学习
有时,我们会有一个来自某些分析或实验的蛋白质序列列表,这些序列在某种程度上是生物学相关的——例如,它们可能都与同一个靶标结合——我们将希望确定这些蛋白质中负责该功能的部分。正如我们在前面配方中所做的,域和基序的寻找可以有所帮助,但前提是我们之前见过这些域,或者该序列特别保守,或者在统计学上被过度表示。另一种方法是尝试使用机器学习,在这种方法中,我们构建一个可以准确分类我们感兴趣的蛋白质的模型,然后利用该模型的特性来告诉我们哪些部分的蛋白质导致了这个分类。在这个配方中,我们将采用这种方法;具体来说,我们将训练一个支持向量机(SVM)。
准备工作
对于这个配方,我们需要kebabs
和Biostrings
、e1071
以及readr
库,并且两个输入数据文件。机器学习在有大量训练样本时效果最佳,但它们运行需要时间,因此我们有一个相对较小的输入,即 170 个大肠杆菌蛋白质,根据STRING
数据库(string-db.org/
)的数据显示,这些蛋白质与pfo蛋白有实验性互作证据。这些是正向训练样本。我们还需要负向训练样本——这又是 170 个与pfo蛋白没有互作证据的大肠杆菌蛋白,它们是随机选择的。所有蛋白质序列都存储在datasets/ch3/ecoli_proteins.fa
文件中。与该文件一起,还有一个文本文件记录了每个蛋白质的类别。datasets/ch3/ecoli_protein_classes.txt
是一个单列文本文件,描述了每个蛋白质的类别——“1”表示正向pfo互作,“-1”表示负向pfo互作。类别文件中的行索引与序列文件中的蛋白质索引相匹配。
如何实现...
蛋白质的新特征检测的机器学习可以通过以下步骤完成:
- 加载库和输入文件:
library(kebabs)
library(Biostrings)
seqs <- readAAStringSet(file.path(getwd(), "datasets", "ch3", "ecoli_proteins.fa"))
classes <- readr::read_csv(file.path(getwd(), "datasets", "ch3", "ecoli_protein_classes.txt"), col_names = TRUE)
classes <- classes$class
- 将数据划分为训练集和测试集:
num_seqs <- length(seqs)
training_proportion <- 0.75
training_set_indices <- sample(1:num_seqs, training_proportion * num_seqs)
test_set_indices <- c(1:num_seqs)[-training_set_indices]
- 使用训练集构建模型:
kernel <- gappyPairKernel(k=1, m=3)
model <- kbsvm(x=seqs[training_set_indices], y=classes[training_set_indices], kernel=kernel, pkg="e1071", svm="C-svc", cost=15)
- 使用模型预测测试集的类别:
predictions <- predict(model, seqs[test_set_indices])
evaluatePrediction(predictions, classes[test_set_indices], allLabels=c(1,-1) )
这将给出以下输出:
## 1 -1
## 1 36 23
## -1 10 19
##
## Accuracy: 62.500% (55 of 88)
## Balanced accuracy: 61.749% (36 of 46 and 19 of 42)
## Matthews CC: 0.250
##
## Sensitivity: 78.261% (36 of 46)
## Specificity: 45.238% (19 of 42)
## Precision: 61.017% (36 of 59)
- 检查序列的预测图谱:
seq_to_test <- seqs[[1]][1:10]
seq_to_test
这将给出以下输出:
## 10-letter "AAString" instance ## seq: MSFDIAKYPT
- 然后,使用以下代码绘制
prediction_profile
:
prediction_profile <-getPredictionProfile(seq_to_test, kernel, featureWeights(model), modelOffset(model) ) plot(prediction_profile)
它是如何工作的...
这里的第一步很简单:我们加载我们感兴趣的序列以及它们所属的类别。因为我们将ecoli_protein_classes.txt
文件加载到一个数据框中,当我们需要一个简单的向量时,我们使用$
子集操作符从数据框中提取classes
列。这样做会返回我们需要的那个单列向量对象。之后,工作流程很简单:
-
决定数据中有多少用于训练,多少用于测试:在第 1 步中,我们选择将数据的 75%作为训练集,在创建
training_proportion
变量时进行设置。它与num_seqs
一起用于sample()
函数,以随机选择序列的索引并将其放入训练集。training_set_indices
变量包含我们稍后将用来子集化数据的整数。最初,我们通过使用方括号[]
和否定运算符-
,创建一个互补的索引列表test_set_indices
。基本上,这种构造方法是创建一个包含所有不在training_set_indices
中的索引的向量的惯用方式。 -
构建和训练支持向量机模型:在第 2 步中,我们构建了分类模型。首先,我们选择一个内核,将输入数据映射到一个支持向量机可以学习的矩阵空间。在这里,它来自
gappyPairKernel()
函数——请注意,内核类型有很多种;这个内核非常适合序列数据。我们将kernel
传递给kbsvm()
函数,并将training_set_indices
子集的序列作为x
参数传入,training_set_indices
子集的类别作为y
参数传入。此函数中的其他参数决定了模型的具体类型、包和训练参数。这些选项非常多,而且它们对最终模型的效果有很大的影响。了解并进行科学实验,找到最适合你数据的设置是非常值得的。最终模型保存在model
变量中。 -
在未见数据上测试模型:现在我们有了一个模型,我们可以用它来预测未见蛋白质的类别。这个阶段将告诉我们模型的效果如何。在第 3 步中,我们使用
predict()
函数与模型以及我们没有用来训练的序列(即test_set_indices
中的序列)进行预测,并返回一个预测对象。通过将预测结果传递给evaluatePrediction()
函数,并结合类别向量中的实际类别和所有可能类别标签的向量(使用allLabels
参数),我们可以得到模型的准确率和其他指标的摘要。这里模型的准确率是 62%,这还算可以;比随机猜测要好。但我们数据集相对较小,且模型没有经过优化;如果做更多的工作,模型的表现可能会更好。请注意,如果你运行这段代码,可能会得到不同的结果。由于训练集序列的选择是随机的,模型的表现可能会略微变好或变差,取决于输入数据的具体内容。 -
估计序列的预测特征:为了实际找到在分类中重要的区域,可能也涉及蛋白质的功能,我们使用
getPredictionProfile()
函数来分析一个序列。在第四步中,我们用列表和双括号索引提取第一个序列的小片段(10 个氨基酸),并用单括号索引获取范围;例如,seqs[[1]][1:10]
。我们这样做是为了在最后一步中的可视化更清晰。你也可以直接使用完整的序列。getPredictionProfile()
函数需要kernel
和model
对象才能正常工作。它将输出以下结果:
## 1 -1
## 1 36 23
## -1 10 19
##
## Accuracy: 62.500% (55 of 88)
## Balanced accuracy: 61.749% (36 of 46 and 19 of 42)
## Matthews CC: 0.250
##
## Sensitivity: 78.261% (36 of 46)
## Specificity: 45.238% (19 of 42)
## Precision: 61.017% (36 of 59)
- 最后,我们可以
plot()
预测特征:该特征展示了每个氨基酸对整体决策的贡献,并增加了学习结果的可解释性。在这里,第四个残基D对该蛋白质的决策贡献非常大。通过在多个序列中检查这种贡献模式,可以阐明影响决策的模式。值得注意的是,你可能会看到与以下示例略有不同的图像——这是由于算法中的随机过程——这是你应纳入分析的一部分:确保任何显著差异不是由于运行代码时的随机选择造成的。在这个例子中,最强的贡献依然应来自于"D":
使用 bio3d 进行 3D 结构蛋白质比对
两个分子模型之间的三维结构比对可以揭示两种蛋白质共有或独特的结构特性。这些结构特性可能暗示着进化或功能上的共性。在本教程中,我们将展示如何在三维空间中对两个蛋白质序列进行比对,并在 3D 渲染软件中查看它们。
准备工作
对于本节内容,我们至少需要两个外部软件——PyMOL 和 MUSCLE——一个是 3D 结构渲染程序,另一个是序列比对工具。
可以通过 conda 安装 MUSCLE,命令如下:
conda install -c bioconda muscle
MUSCLE 的一个版本已经与msa
包一起安装,并且 bio3d 可以引用这个安装版本。我们将在本教程中使用msa版本。
PyMOL 对于可视化至关重要,可以通过 conda 安装,命令如下:
conda install -c schrodinger pymol
要查找该软件的安装路径,可以使用which pymol
命令。
除此之外,你还需要两个包含人类和果蝇硫氧还蛋白结构的文件来进行操作:datasets/ch3/1xwc.pdb
和datasets/ch3/3trx.pdb
。
如何操作...
使用 bio3d 进行 3D 结构蛋白质比对的步骤如下:
- 加载库和 PDB 结构文件:
library(bio3d)
a <- read.pdb(file.path(getwd(), "datasets", "ch3" ,"1xwc.pdb"))
b <- read.pdb(file.path(getwd(), "datasets", "ch3", "3trx.pdb"))
- 然后,进行结构比对:
pdbs <- pdbaln(list("1xwc"=a,"3trx"=b), fit=TRUE, exefile="msa")
- 在 PyMOL 中启动并渲染比对:
pymol_path = "/Users/macleand/miniconda2/bin/pymol"
pymol(pdbs, exefile = pymol_path, type = "launch", as="cartoon")
它是如何工作的...
和往常一样,第一步是加载库,然后加载输入数据。这里在第 1 步,我们使用 read.pdb()
函数加载两个独立的 PDB 文件。在第 2 步,我们使用 pdbaln()
函数进行对齐。所有我们想要对齐的 PDB 对象首先被放入一个适当命名的 list 对象中。fit
参数设置为 TRUE
,以便基于所有对齐的序列执行结构叠加(执行的是基于序列的叠加)。
exefile
参数告诉 pdbaln()
在哪里执行基于序列的对齐部分,如这里所示;"msa"
的值使用 msa 包中的对齐工具,但你也可以使用一个指向替代可执行文件的路径,或者将 exefile
替换为你有效的电子邮件地址,使用 web.args="your.email@something.org"
这种形式在 EBI 上通过网络进行对齐。
一旦我们有了一个 pdbs
中的对齐对象,就可以在 PyMOL 中可视化它。我们在 pymol_path
变量中设置 PyMOL 的路径,并将其与类型 "launch"
一起传递给 pymol()
函数,这将创建一个交互式的 PyMOL 会话。或者,省略 type
参数将生成一个 PyMOL 脚本,稍后可以使用。PyMol 应该会显示接下来的图片。截图显示了两种对齐蛋白质的渲染效果:人类版本为红色,果蝇版本为黄色。
还有更多...
pdbaln()
函数对于长度相似的结构效果很好。对于那些 PDB 长度不相等的结构,你可以尝试 struct.aln()
函数。
第四章:系统发育分析与可视化
比较序列以推断进化关系是生物信息学中的一项基本技术。这项技术在 R 中也有着悠久的历史。除了 Bioconductor 之外,还有许多用于进化分析的包。在本章的食谱中,我们将详细研究如何处理来自不同来源的树形格式。重点将放在如何操作树形结构,聚焦于特定部分,并使用基于新的ggplot
的树形可视化包进行可视化,这对于查看和注释大型树形结构特别有用。
本章将涵盖以下食谱:
-
使用 ape 和 treeio 读取和写入各种树形格式
-
使用 ggtree 快速可视化多基因树
-
使用 treespace 量化树之间的距离
-
使用 ape 提取和处理子树
-
为对齐可视化创建点图
-
使用 phangorn 从比对中重建树形
技术要求
你需要的示例数据可以从本书的 GitHub 仓库获得,链接是github.com/danmaclean/R_Bioinformatics_Cookbook
。如果你想按照代码示例直接使用这些数据,请确保这些数据位于你工作目录的一个子目录中。
这里是你需要的 R 包。大多数包可以通过install.packages()
进行安装;其他的则稍微复杂一些:
-
ape
-
adegraphics
-
Bioconductor:
-
Biostrings
-
ggtree
-
treeio
-
msa
-
-
devtools
-
dotplot
-
ggplot2
-
phangorn
-
treespace
Bioconductor
非常庞大,并且有自己的安装管理器。你可以使用以下代码进行安装:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
更多信息请参见www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接按名称使用其中的函数。这在交互式会话中非常方便,但当加载了许多包时,可能会造成混淆。为了明确在某个时刻我正在使用哪个包和函数,我会偶尔使用packageName::functionName()
的约定。
有时,在食谱的中途,我会中断代码,这样你就能看到一些中间输出或一个重要的对象结构,帮助理解。每当这种情况发生时,你会看到一个代码块,每行都以##
(双哈希)符号开头。请看以下命令:
letters[1:5]
这将给我们带来以下输出:
## a b c d e
注意,输出行的前面会有##
作为前缀。
使用 ape 和 treeio 读取和写入各种树形格式
系统发育分析是生物学和生物信息学的基石。相关程序种类繁多且复杂,计算耗时,数据集通常庞大。许多程序是独立运行的,且拥有专有的输入和输出格式。这形成了一个复杂的生态系统,我们在处理系统发育数据时必须应对,意味着通常最简单的策略是使用多种工具组合来加载、转换并保存分析结果,以便能够在不同的软件包中使用它们。在这个操作中,我们将探讨如何在 R 中处理系统发育树数据。迄今为止,R 对各种树格式的支持较为有限,但一些核心包具有足够标准化的对象,工作流可以集中在少数几种类型上,且转换为这些类型的过程已经简化。我们将使用ape
和treeio
包来导入和导出树数据。
准备工作
对于本节内容,我们需要从本书数据仓库中的datasets/ch4/
文件夹获取树和系统发育信息,特别是mammal_tree.nwk
和mammal_tree.nexus
文件,它们分别是哺乳动物系统发育树的 Newick 格式和 Nexus 格式(你可以在本书的附录中查看这些文件格式的简要描述)。我们还需要beast_mcc.tree
,这是 BEAST 运行的树文件,和RAxML_bipartitionsBranchLabels.H3
,它是 RAxML 的输出文件。这两个文件来自treeio
包提供的大量数据。我们还需要 Bioconductor 包treeio
和ape
包。
操作步骤...
使用ape
和treeio
读取和写入树格式的步骤如下:
- 加载
ape
库并加载树数据:
library(ape)
newick <-ape::read.tree(file.path(getwd(), "datasets", "ch4", "mammal_tree.nwk"))
nexus <-ape::read.nexus(file.path(getwd(), "datasets", "ch4", "mammal_tree.nexus"))
- 加载
treeio
库并加载 BEAST/RAxML 输出:
library(treeio)
beast <- read.beast(file.path(getwd(), "datasets", "ch4", "beast_mcc.tree"))
raxml <- read.raxml(file.path(getwd(), "datasets", "ch4", "RAxML_bipartitionsBranchLabels.H3"))
- 检查不同函数返回的对象类型:
class(newick)
class(nexus)
class(beast)
class(raxml)
- 将
tidytree
转换为phylo
,反之亦然:
beast_phylo <- treeio::as.phylo(beast)
newick_tidytree <- treeio::as.treedata(newick)
- 使用以下代码编写输出文件:
treeio::write.beast(newick_tidytree,file = "mammal_tree.beast")
ape::write.nexus(beast_phylo, file = "beast_mcc.nexus")
工作原理...
在步骤 1中,我们使用了ape
包中非常简单的加载函数——我们使用read.tree()
和read.nexus()
函数,它们能够读取通用格式的树。在步骤 2中,我们使用treeio
包的特定格式函数来加载 BEAST 和 RaXML 的输出。步骤 3只是确认这些函数返回的对象类型;注意,ape
返回phylo
对象,而treeio
返回treedata
对象。通过treeio
中的as.phylo()
和as.treedata()
,我们可以相互转换这两种对象类型。在步骤 4中,通过这种转换,我们可以将多种格式的输入导入到 R 中进行后续分析。最后,在步骤 5中,我们将文件写出。
参见
我们在步骤 2中使用的加载函数只是其中的几个。请参阅treeio
包的说明文档,了解完整的函数列表。
使用 ggtree 快速可视化多个基因的树
一旦你计算出了树,首先要做的就是查看它。虽然许多程序都能做到这一点,但 R 具有一种极其强大、灵活且快速的系统,形式为ggtree
包。在这个配方中,我们将学习如何将数据导入ggtree
,并仅用几条命令重新布局、突出显示和注释树图。
准备就绪
你需要ggplot2
、ggtree
和ape
包。此外,还需要从本书的仓库中的datasets/ch4
文件夹中获取itol.nwk
文件,它是来自Interactive Tree of Life在线工具的公共数据集的 Newick 格式树,包含 191 个物种。
如何实现...
使用ggtree
快速可视化多基因的树,可以按照以下步骤执行:
- 加载库并获取 Newick 树的
phylo
对象:
library(ggplot2)
library(ggtree)
itol <-ape::read.tree(file.path(getwd(), "datasets", "ch4", "itol.nwk"))
- 制作基础的树形图:
ggtree(itol)
- 制作圆形图:
ggtree(itol, layout = "circular")
- 旋转并反转树:
ggtree(itol) + coord_flip() + scale_x_reverse()
- 向树的末端添加标签:
ggtree(itol) + geom_tiplab( color = "blue", size = 2)
- 制作颜色条来注释特定的谱系:
ggtree(itol, layout = "circular") + geom_strip(13,14, color="red", barsize = 1)
- 制作颜色块来突出显示特定的谱系:
ggtree(itol, layout = "unrooted") + geom_hilight_encircle(node = 11, fill = "steelblue")
它是如何工作的...
这段代码能够非常快速地实现很多功能。它通过类似于ggplot
的层语法来实现这一点。以下是每个步骤的作用及其输出:
-
从文件加载一棵树。这里的树有 191 个叶子节点,所以非常大。恰好它是 Newick 格式的,因此我们使用
ape
的read.tree()
函数。请注意,在后续步骤中,我们不需要为ggtree
创建treedata
对象;从read.tree()
返回的phylo
对象完全可以传递给ggtree()
。 -
使用
ggtree()
创建一棵基本的树。这是一个封装了较长ggplot
样式语法的函数,具体来说是ggplot(itol) + aes(x,y) + geom_tree() + theme_tree()
。因此,可以将所有常见的ggplot
函数作为额外层叠加到图表中。此步骤中的代码将生成以下图:
- 更改图表的布局。将布局参数设置为圆形将得到一个圆形的树。通过此参数,还可以选择其他多种树形:
- 我们可以使用标准的
ggplot
函数coord_flip()
和scale_x_reverse()
将树的左右方向改为上下方向,图表将呈现如下效果:
- 我们可以使用
geom_tiplab()
在树的末端添加名称。size
参数设置文本的大小。以下代码将生成如下输出:
- 通过添加
geom_strip()
层,我们可以用一块颜色条注释树中的谱系。第一个参数(在本例中为13
)是树中的起始节点,第二个参数是树中颜色条的结束节点。barsize
参数设置颜色块的宽度。结果如下所示:
- 我们可以使用
geom_hilight_encircle()
几何图形在无根树中突出显示某些分支。我们需要为node
参数选择一个值,这告诉ggtree()
哪个节点是颜色的中心。这里的代码输出以下结果:
还有更多...
步骤 6和步骤 7依赖于我们知道要操作树中的哪些节点。因为节点是通过数字而非名称来标识的,这并不总是显而易见的。如果我们使用MRCA()
(最近共同祖先)函数,就可以找到我们想要的节点编号。只需传递一个节点名称的向量,它就会返回代表 MRCA 的节点 ID:
MRCA(itol, tip=c("Photorhabdus_luminescens", "Blochmannia_floridanus"))
这将输出以下结果:
## 206
使用 treespace 量化树之间的差异
比较树以区分或将它们分组,可以帮助研究人员观察进化模式。通过跨物种或菌株跟踪单一基因的多棵树,可以揭示该基因在不同物种中变化的差异。这些方法的核心是树之间距离的度量。在这个食谱中,我们将计算一个这样的度量,找到 15 个不同物种中 20 棵不同基因树的成对差异——因此,每棵树中有 15 个相同名称的树枝。树之间的这种相似性通常是进行比较和获取距离所必需的,只有满足这些条件,我们才能进行这样的分析。
准备就绪
对于这个食谱,我们将使用treespace
包来计算距离和聚类。我们将使用ape
和adegraphics
来加载附加功能和可视化函数。这里的输入数据将是datasets/ch4/gene_trees
中的所有 20 个文件,每个文件都是一个 Newick 格式的树,代表 15 个物种中每个物种的单个基因。
如何进行...
使用treespace
量化树之间的差异可以通过以下步骤执行:
- 加载库:
library(ape)
library(adegraphics)
library(treespace)
- 将所有树文件加载到一个
multiPhylo
对象中:
treefiles <- list.files(file.path(getwd(), "datasets", "ch4", "gene_trees"), full.names = TRUE)
tree_list <- lapply(treefiles, read.tree)
class(tree_list) <- "multiPhylo"
- 计算 Kendall-Colijn 距离:
comparisons <- treespace(tree_list, nf = 3)
- 绘制成对距离:
adegraphics::table.image(comparisons$D, nclass=25)
- 绘制主成分分析(PCA)和聚类:
plotGroves(comparisons$pco, lab.show=TRUE, lab.cex=1.5)
groves <- findGroves(comparisons, nclust = 4)
plotGroves(groves)
它是如何工作的...
这里简短而强大的代码非常有效——在少数几条命令中就能提供大量的分析。
在步骤 1中,首先加载我们需要的库。
在步骤 2中,加载必要的库后,我们创建一个字符向量treefiles
,它保存我们希望使用的 20 棵树的路径。我们使用的list.files()
函数接受一个文件系统路径作为参数,并返回该路径下找到的文件名。由于treefiles
是一个向量,我们可以将其作为lapply()
的第一个参数。
如果你不熟悉,lapply()
是一个迭代函数,它返回一个 R 列表(因此是lapply()
)。简单来说,lapply()
会将第二个参数中指定的函数应用于第一个参数中的列表。当前元素作为目标函数的第一个参数传递。因此,在第 2 步中,我们在treefiles
中列出的每个文件上运行ape的read.tree()
函数,返回一个phylo
树对象的列表。最后一步是确保tree_list
对象具有类multiPhylo
,这样我们就满足了后续函数的要求。值得一提的是,multiPhylo
对象本身就是一个类似列表的对象,因此我们只需要通过class()
函数将multiPhylo
字符串添加到类属性中即可。
在第 3 步中,来自同名包的treespace()
函数做了大量的分析。首先,它对输入中的所有树进行成对比较,然后使用 PCA 进行聚类。结果以列表对象返回,成员D包含树的成对距离,pco
包含 PCA。默认的距离度量,Kendall-Colijn 距离,特别适用于我们这里的有根基因树,尽管该度量可以更改。nf
参数仅告诉我们保留多少个主成分。由于我们的目标是绘图,我们不需要超过三个主成分。
在第 4 步中,我们使用adegraphics
中的table.image()
函数绘制comparisons$D
中的距离矩阵—这是一个方便的热图风格函数。nclass
参数告诉我们要使用多少个颜色层级。我们得到的图如下所示:
在第 5 步中,plotGroves()
函数直接绘制treespace
对象,因此我们可以看到 PCA 的绘图:
我们可以使用findGroves()
函数将树分组为由nclust
参数给定的组数,并重新绘制以查看结果:
还有更多...
如果你有很多树,并且图表看起来拥挤,你可以使用以下代码创建一个可以缩放和平移的交互式图:
plotGrovesD3(comparisons$pco, treeNames=paste0("species_", 1:10) )
使用 ape 提取和操作子树
在这个简短的教程中,我们将看看操作树是多么简单;具体来说,如何将子树提取为一个新对象,以及如何将树组合成其他树。
准备工作
我们需要一棵示例树;datasets/ch4
文件夹中的mammal_tree.nwk
文件就可以。我们需要的所有函数都可以在ape
包中找到。
如何操作...
使用ape
提取和操作子树可以通过以下步骤执行:
- 加载
ape
库,然后加载树:
library(ape)
newick <-read.tree(file.path(getwd(), "datasets", "ch4", "mammal_tree.nwk"))
- 获取所有子树的列表:
l <- subtrees(newick)
plot(newick)
plot(l[[4]], sub = "Node 4")
- 提取特定的子树:
small_tree <- extract.clade(newick, 9)
- 合并两棵树:
new_tree <- bind.tree(newick, small_tree, 3)
plot(new_tree)
它是如何工作的...
本食谱中的函数非常简单,但非常有用。
第一步是一个常见的树加载步骤。我们需要一个 phylo 对象的树来进行后续操作。
第二步使用subtrees()
函数,它提取所有非平凡的(超过一个节点的)子树,并将它们放入一个列表中。列表中的成员根据原始树中的节点编号进行编号,每个列表中的对象都是一个phylo
对象,像父树一样。我们可以使用plot()
函数查看原始树和节点 4 的子树,并生成以下图示:
在第三步中,我们使用extract.clade()
函数获取一个特定的子树。该函数的第一个参数是树,第二个参数是将被提取的节点。实际上,所有该节点下游的节点都会被提取,返回一个新的phylo
对象。
最后的示例展示了如何使用bind.tree()
函数将两个phylo
对象结合起来。第一个参数是主树,它将接收第二个参数中的树。在这里,我们将把small_tree
接到 Newick 树上。第三个参数是主树中要加入第二棵树的节点。同样,返回的是一个新的phylo
对象。当我们绘制新的树时,可以看到相对于原始树的重复部分:
还有更多...
上述函数的一个小问题是它们要求我们知道要操作的节点编号。一种简单的访问方式是使用交互式的subtreeplot()
命令。subtreeplot(newick)
代码会生成一个交互式的树图,如这里的例子。通过点击树中的特定节点,我们可以让查看器渲染该子树并打印节点 ID。然后我们可以在函数中使用这个节点 ID:
创建对齐可视化的点图
对齐序列对的点图可能是最古老的对齐可视化方法。在这些图中,两个序列的位置分别绘制在x轴和y轴上,对于该坐标系中的每个坐标点,如果该点处的字母(核苷酸或氨基酸)相对应,就会画出一个点。由于该图能够展示出两个序列中不一定在同一区域匹配的区域,这是一种视觉上快速发现插入、删除以及结构重排的好方法。在这个例子中,我们将展示如何使用dotplot
包和一些代码,快速构建一个点图,并获取文件中所有序列对的点图网格。
准备工作
我们将需要datasets/ch4/bhlh.fa
文件,其中包含豌豆、大豆和莲花的三种基本螺旋-环-螺旋(bHLH)转录因子序列。我们还需要dotplot
包,它不在 CRAN 或 Bioconductor 上,因此你需要使用devtools
包从 GitHub 安装它。以下代码应该可行:
library(devtools)
install_github("evolvedmicrobe/dotplot", build_vignettes = FALSE)
如何实现...
创建用于对齐可视化的点图可以通过以下步骤完成:
- 加载库和序列:
library(Biostrings)
library(ggplot2)
library(dotplot)
seqs <- readAAStringSet(file.path(getwd(), "datasets", "ch4", "bhlh.fa"))
- 创建一个基本的点图:
dotPlotg(as.character(seqs[[1]]), as.character(seqs[[2]] ))
- 更改点图并应用
ggplot2
主题和标签:
dotPlotg(as.character(seqs[[1]]), as.character(seqs[[2]] ), wsize=7, wstep=5, nmatch=4) +
theme_bw() +
labs(x=names(seqs)[1], y=names(seqs)[2] )
- 创建一个函数,从提供的序列和序列索引中生成点图:
make_dot_plot <- function(i=1, j=1, seqs = NULL){
seqi <- as.character(seqs[[i]])
seqj <- as.character(seqs[[j]])
namei <- names(seqs)[i]
namej <- names(seqs)[j]
return( dotPlotg(seqi, seqj ) + theme_bw() + labs(x=namei, y=namej) )
}
- 设置数据结构以运行函数:
combinations <- expand.grid(1:length(seqs),1:length(seqs))
plots <- vector("list", nrow(combinations) )
- 对所有可能的序列对组合运行该函数:
for (r in 1:nrow(combinations)){
i <- combinations[r,]$Var1[[1]]
j <- combinations[r,]$Var2[[1]]
plots[[r]] <- make_dot_plot(i,j, seqs)
}
- 绘制点图网格:
cowplot::plot_grid(plotlist = plots)
它是如何工作的...
本食谱的第一部分非常熟悉。我们加载库,并使用 Biostrings
加载我们的蛋白质序列。请注意,seqs
变量中的序列是 XStringSet
类的一个实例。
在 步骤 2 中,我们可以使用 dotplotg()
函数创建一个基本的点图。参数是我们希望绘制的序列。注意,我们不能直接传递 XStringSet
对象;我们需要传递字符向量,因此我们使用 as.character()
函数将序列转换为该格式。运行此代码会生成以下点图:
在 步骤 3 中,我们通过首先改变匹配的考虑方式来详细说明基本点图。通过 wsize=7
选项,我们表示一次查看七个残基(而不是默认的一个),wstep=5
选项告诉绘图程序每次跳过五个残基(而不是默认的一个),nmatch=4
选项告诉绘图程序当四个残基相同时,标记该窗口为匹配。然后,我们通过添加 ggplot2
主题并以通常的 ggplot
方式进行自定义,最后使用标签函数添加轴名称。由此,我们得到了与第一个不同的点图:
自定义函数 make_dot_plot()
,在 步骤 4 中定义,接收两个数字变量 i
和 j
以及 seqs
参数中的 XStringSet
对象。然后,它将 seqs
对象中的第 i 个和第 j 个序列转换为字符,并将其存储在 seqi
和 seqj
变量中。同时,它提取这些序列的名称并分别存储在 namei
和 namej
中。最后,它创建并返回一个使用这些变量生成的点图。
要使用该函数,我们需要两个条件:要绘制的序列组合和一个用于存储结果的列表。在 步骤 4 中,使用 expand.grid()
函数创建所有可能的序列组合的数据框,并将其存储在 combinations
变量中。使用 vector()
函数创建的 plots
变量包含一个 list
对象,该对象有足够的槽位来存储结果的点图。
步骤 6 是一个循环,遍历组合数据框中的每一行,提取我们希望处理的序列编号,并将其存储在 i
和 j
变量中。然后,调用 make_dot_plot()
函数,传入 i
、j
和 seqs
,并将其结果存储在我们创建的 plots
列表中。
最后,在步骤 7中,我们使用cowplot
库的plot_grid()
函数,结合我们的图形列表,生成所有可能组合的主图,如下所示:
使用 phangorn 从对齐中重建树
到目前为止,在本章节中,我们假设树已经可用并准备好使用。当然,构建系统树有很多方法,在本食谱中,我们将看看一些可用的不同方法。
准备工作
在本章节中,我们将使用datasets/ch4/
文件、酵母 ABC 转运蛋白序列的abc.fa
文件、Bioconductor Biostrings
包以及来自 CRAN 的msa
和phangorn
包。
操作步骤...
使用phangorn
构建树可以通过以下步骤执行:
- 加载库和序列,并进行对齐:
library(Biostrings)
library(msa)
library(phangorn)
seqs <- readAAStringSet(file.path(getwd(), "datasets", "ch4", "abc.fa"))
aln <- msa::msa(seqs, method=c("ClustalOmega"))
- 将对齐转换为
phyDat
对象:
aln <- as.phyDat(aln, type = "AA")
- 从距离矩阵生成 UPGMA 和邻接法树:
dist_mat <- dist.ml(aln)
upgma_tree <- upgma(dist_mat)
plot(upgma_tree, main="UPGMA")
nj_tree <- NJ(dist_mat)
plot(nj_tree,"unrooted", main="NJ")
- 计算自助法并绘制:
bootstraps <- bootstrap.phyDat(aln,FUN=function(x) { NJ(dist.ml(x)) } , bs=100)
plotBS(nj_tree, bootstraps, p = 10)
它是如何工作的...
第一步执行加载和氨基酸序列对齐,就像我们在之前的食谱中使用msa
包时看到的那样,返回一个MsaAAMultipleAlignment
对象。
第二步使用as.phyDat()
函数将对齐转换为可以由phangorn
函数使用的phyDat
对象。
在步骤 3中,我们实际上生成树。树是由距离矩阵构建的,我们可以使用dist.ml()
和我们的对齐来计算距离矩阵(这是一个最大似然距离度量;如果需要,也可以使用其他函数)。然后将dist_mat
传递给upgma()
和NJ()
函数,分别生成 UPGMA 和邻接法树。这些函数返回标准的phylo
对象,可以在许多其他函数中使用。这里,我们直接绘制:
在最后一步,我们使用bootstraps.phyDat()
函数来计算树中各个分支的自助法支持。第一个参数是phyDat
对象aln
,而第二个参数FUN
需要一个函数来计算树。这里,我们使用一个匿名函数,包装了我们最初用于生成nj_tree
的NJ()
方法。bs
参数告诉函数计算多少次自助法。最后,我们可以使用plotBS()
函数将结果图绘制到树上。
第五章:宏基因组学
高通量测序技术已经大大推动了宏基因组学的发展,从一个专注于研究单一序列变异的领域(例如 16S 核糖体 RNA (rRNA) 序列)发展到研究样本中可能存在的多个物种的整个基因组。识别物种或分类单元及其在样本中的丰度是一项计算挑战,要求生物信息学家处理序列准备、分类分配、分类比较以及定量分析。为此,许多专业实验室已经开发出相关的包,这些包创造了特定于宏基因组学中序列处理的新工具和新可视化。
在本章中,我们将查看一些在 R 中进行宏基因组学复杂分析的配方:
-
使用
phyloseq
加载层级分类数据 -
使用
metacoder
进行计数稀疏化以纠正样本差异 -
使用
dada2
从原始读取中读取扩增子数据 -
使用
metacoder
中的热图树可视化分类丰度 -
使用
vegan
计算样本多样性 -
将序列文件拆分为操作性分类单元
技术要求
你需要的示例数据可以从本书的 GitHub 库获取:github.com/PacktPublishing/R-Bioinformatics-Cookbook
. 如果你想按原样使用代码示例,你需要确保这些数据位于你的工作目录的子目录中。
下面是你需要的 R 包。大多数包可以通过install.packages()
安装;其他一些包安装起来稍微复杂一些:
-
ape
-
Bioconductor
-
dada2
-
phyloseq
-
-
corrplot
-
cowplot
-
dplyr
-
kmer
-
magrittr
-
metacoder
-
RColorBrewer
-
vegan
Bioconductor 非常庞大,并且拥有自己的安装管理器。你可以通过以下代码安装它:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
BiocManager::install()
进一步的信息可以通过以下链接获取:www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接按名称使用函数。这在交互式会话中非常方便,但在加载多个包时可能会造成混乱。为了明确我在某一时刻使用的是哪个包和函数,我偶尔会使用packageName::functionName()
的约定。
有时候,在一个配方的中间,我会中断代码,方便你查看一些中间输出或对象结构,这对于理解非常重要。每当这种情况发生时,你会看到一个代码块,其中每一行都以##
(双井号)符号开头。请看下面的命令:
letters[1:5]
这将给我们以下输出:
## a b c d e
注意,输出行前面有##
的前缀。
使用 phyloseq 加载层级分类数据
元基因组学管道通常从大型测序数据集开始,这些数据集会在强大且功能齐全的程序中处理,如 QIIME 和mothur
。在这些情况下,我们希望将这些工具的结果整理成报告或进一步进行特定分析。在这个步骤中,我们将展示如何将 QIIME 和mothur
的输出导入 R 中。
准备工作
对于这个简短的步骤,我们需要从Bioconductor
安装phyloseq
包,并从本书数据仓库的datasets/ch5
文件夹中获取文件。
如何操作…
使用phyloseq
加载层次分类数据可以通过以下步骤完成:
- 加载库:
library(phyloseq)
- 导入 QIIME 的
.biom
文件:
biom_file <- file.path(getwd(), "datasets", "ch5", "rich_sparse_otu_table.biom")
qiime <- import_biom(biom_file)
- 访问
phyloseq
对象的不同部分:
tax_table(qiime)
## Taxonomy Table: [5 taxa by 7 taxonomic ranks]:
## Rank1 Rank2 Rank3
## GG_OTU_1 "k__Bacteria" "p__Proteobacteria" "c__Gammaproteobacteria"
## GG_OTU_2 "k__Bacteria" "p__Cyanobacteria" "c__Nostocophycideae"
otu_table(qiime)
## OTU Table: [5 taxa and 6 samples]
## taxa are rows
## Sample1 Sample2 Sample3 Sample4 Sample5 Sample6
## GG_OTU_1 0 0 1 0 0 0
## GG_OTU_2 5 1 0 2 3 1
sample_data(qiime)
## BarcodeSequence LinkerPrimerSequence BODY_SITE Description
## Sample1 CGCTTATCGAGA CATGCTGCCTCCCGTAGGAGT gut human gut
- 导入
mothur
数据文件:
mothur <- import_mothur(
mothur_list_file = file.path(getwd(), "datasets", "ch5", "esophagus.fn.list"),
mothur_group_file = file.path(getwd(), "datasets", "ch5", "esophagus.good.groups"),
mothur_tree_file = file.path(getwd(), "datasets", "ch5", "esophagus.tree")
)
- 访问
phyloseq
对象中的otu
对象:
otu_table(mothur)
## OTU Table: [591 taxa and 3 samples]
## taxa are rows
## B C D
## 9_6_14 2 0 0
## 9_6_25 1 0 0
它是如何工作的…
在这个简单的步骤中,我们创建对象并使用访问器函数。
在第 1 步中,我们按惯例加载phyloseq
库。
然后,在第 2 步中,我们定义一个文件,并将其作为import_biom()
函数的第一个参数。该函数可以读取来自 QIIME 的现代biom
格式输出,支持未压缩的 JSON 和压缩的 HDF5 格式。类型会自动检测。我们将得到一个完全填充的phyloseq
对象。
在第 3 步中,我们使用访问器函数获取对象的子部分,使用tax_table()
获取分类数据,使用otu_table()
获取 OTU,使用sample_data()
获取样本数据;这些都可以作为类似矩阵的对象在后续处理中轻松使用。
在第 4 步中,我们改变方向,使用mothur
的输出数据。我们至少需要一个列表文件和一个分组文件,这些文件路径通过mothur_list_file
和mothur_group_file
参数指定。这里,我们还通过mothur_tree_file
参数指定一个 Newick 格式的树。
同样,我们可以使用phyloseq
的访问器函数otu_table()
来获取 OTU。对于最小的mothur
数据,我们在此指定不能获取样本数据或分类学表。
还有更多…
如果你有来自旧版 QIIME 的专有格式数据,可以使用import_qiime()
函数。若你附加了树对象,也有一个访问器函数——phy_tree()
。
另请参见
QIIME 和mothur
程序的官网和 wiki 页面非常出色地展示了如何在 R 中处理它们管道输出的数据,尤其是mothur
。如果你想要一些数据分析的思路,可以试试它们。
使用 metacoder 稀释计数并校正样本差异。
在宏基因组学中,一个常见的问题是询问某个样本中存在哪些物种,以及两个或多个样本之间的差异。由于样本可能由不同数量的观察值组成——在宏基因组学中,这意味着生成的不同数量的读取——因此样本的分类丰富度会随着测序深度的增加而增加。为了公平地评估样本中不同类群的多样性,宏基因组学家通常会对计数进行稀释,以确保样本的测序深度相同。本质上,这意味着将样本深度减少到最小的样本深度。在这个步骤中,我们将对来自 biom
文件的 OTU 计数进行稀释。
准备工作
对于这个步骤,你将需要 metacoder
包和 datasets/ch5/rich_high_count_otu.biom
,这是一个包含六个样本(标记为 Sample1
–Sample6
)和五个 OTU 的示例 biom
文件。当然,这是一个非常小的文件,只用于学习代码的工作原理。真实的宏基因组数据集要大得多。
如何操作...
使用 metacoder
对计数进行稀释并修正样本差异可以通过以下步骤完成:
- 加载库和文件:
library(metacoder)
biom_file <- file.path(getwd(), "datasets", "ch5", "rich_high_count_otu.biom")
taxdata <- parse_qiime_biom(biom_file)
- 创建样本中计数的直方图:
sample_ids <- paste0("Sample", 1:6)
hist_data <- colSums(taxdata$data$otu_table[, sample_ids])
hist(hist_data, prob= TRUE, breaks=3)
lines(density(hist_data, adjust = 2), col="red")
- 调用稀释函数并过滤掉可能生成的低 OTU:
taxdata$data$rarefied_otus <- rarefy_obs(taxdata, "otu_table", other_cols = TRUE)
low_otu_index <- rowSums(taxdata$data$rarefied_otus[, sample_ids]) <=20
taxdata <- filter_obs(taxdata, "rarefied_otus", ! low_otu_index)
taxdata$data$rarefied_otus
工作原理...
这里的总体模式是加载文件,检查样本 OTU 计数的分布,并应用稀释法。
第一步是加载库并导入示例文件。我们通过准备 rich_high_count_otu.biom
文件来完成此操作,并将其传递给 parse_qiime()
函数。这个 metacoder
函数只是读取生物群落文件并返回一个 taxmap
对象(另一种用于保存分类数据的对象),我们可以在 metacoder
函数中使用它。
接下来,我们希望检查样本 OTU 计数的分布,这可以通过准备一个直方图来完成。我们使用 paste()
函数创建一个包含样本名称的字符向量,并用它来通过命名索引提取 otu_table
中的计数列。这些列的子集被传递给 colSums()
函数,后者会获取 hist_data
向量中每个样本的总计数。现在,我们可以使用 hist()
创建这些计数的直方图,并通过 lines()
和 density()
函数在 hist_data
上添加密度曲线。请注意,结果图(在以下直方图中)看起来较为稀疏,因为示例文件中的样本数量较少。这里的最低计数为我们提供了最低测序样本的概念。如果存在明显较低的样本,最好先去除这些列:
现在,我们可以进行稀释分析。我们在 taxdata
上使用 rarefy_obs()
函数;第二个参数(值为 "otu_table"
)是 taxdata
对象中包含 OTU 计数的槽位名称。由于稀释会减少计数,因此我们现在需要去除在所有样本中已经减少过多的计数。因此,我们使用 rowSums()
函数,并通过样本名称索引 taxdata$data$rarefied_otus
对象,得到一个逻辑向量,表示哪些 OTU 的总计数低于 20。最后,我们在 taxdata
上使用 filter_obs()
函数;第二个参数(值为 "rarefied_otus"
)是 taxdata
对象中包含稀释 OTU 计数的槽位名称。!
字符用于反转低计数 OTU 的逻辑向量,因为 filter_obs()
会保留通过的行,而我们希望移除这些行。最终输出的是一个稀释后的 OTU 计数集。
请注意,在以下输出中,OTU 行 3 已通过低计数被移除:
## # A tibble: 4 x 8
## taxon_id otu_id Sample1 Sample2 Sample3 Sample4 Sample5 Sample6
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 ax GG_OTU_1 24 1004 847 1979 1070 1170
## 2 ay GG_OTU_2 872 0 704 500 1013 689
## 3 ba GG_OTU_4 875 1144 1211 217 0 1180
## 4 ax GG_OTU_5 1270 893 276 338 953 0
还有更多……
我们可以通过稀释曲线估算一个有用的计数水平。通过这些曲线,计数会在不同的样本大小下随机抽取,并计算 OTU 中的物种数。当物种数量停止增加时,我们就知道我们已经有足够的读取数据,并且不会从处理额外的计数中获得更多价值。rarecurve()
函数在 vegan
包中可以帮助我们完成这一任务。我们提供一个 OTU 表(请注意,该函数需要样本按行排列,因此我们必须使用 t()
函数旋转我们的 taxdata
OTU 表)。然后,我们为 sample
参数传入最小样本大小。我们使用 colSums()
和 min()
函数来获取该最小样本 OTU 计数。输出结果如下图所示:
在这里,我们可以清楚地看到,超过 20,000 的样本并没有增加物种的丰富度。
使用 dada2 从原始读取数据中读取扩增子数据
作为元基因组学中的一项长期技术,特别是对于有兴趣进行细菌微生物组研究的人,使用 16S 或 18S rRNA 基因的克隆拷贝(扩增子)测序来创建物种谱图。这些方法可以利用低通量测序以及目标序列的知识来对每个克隆序列进行分类,从而简化了将分类单元分配到读取序列的复杂任务。在这个方案中,我们将使用 dada2
包从原始 fastq
序列读取中运行扩增子分析。我们将执行质量控制和 OTU 分配步骤,并使用一种有趣的机器学习方法来对序列进行分类。
准备工作
对于这个食谱,我们需要 Bioconductor 的dada2
包和 CRAN 的cowplot
包。我们将使用来自短读档案实验SRR9040914的某些宏基因组序列数据,其中检测了一个旅游中心潮汐海湖水中的物种组成,因为游客向湖里投掷硬币并许愿。我们将使用二十个fastq
文件,每个文件包含 2500 条数据,每个文件经过压缩,且可以在本书的仓库datasets/ch5/fq
中找到。这是 Illumina 读取的一个小子集。我们还需要datasets/ch5/rdp_train_set_14.fa
文件,它是由dada
团队维护的 16S 序列训练集之一。更多训练集请参见benjjneb.github.io/dada2/training.html
。
如何进行...
使用dada2
从原始读取数据中读取扩增子数据,可以按照以下步骤进行:
- 加载库并为每个
fastq
文件准备绘图:
library(dada2)
library(cowplot)
fq_dir <- file.path(getwd(), "datasets", "ch5", "fq")
read_files <- list.files(fq_dir, full.names = TRUE, pattern = "fq.gz")
quality_plots <- lapply(read_files, plotQualityProfile)
plot_grid(plotlist = quality_plots)
- 对文件进行质量修剪和去重复:
for (fq in read_files ){
out_fq <- paste0(fq, ".trimmed.filtered")
fastqFilter(fq, out_fq, trimLeft=10, truncLen=250,
maxN=0, maxEE=2, truncQ=2,
compress=TRUE)
}
trimmed_files <- list.files(fq_dir, full.names = TRUE, pattern = "trimmed.filtered")
derep_reads <- derepFastq(trimmed_files)
- 从样本子集估算
dada2
模型:
trimmed_files <- list.files(fq_dir, full.names = TRUE, pattern = "trimmed.filtered")
derep_reads <- derepFastq(trimmed_files)
dd_model <- dada(derep_reads[1:5], err=NULL, selfConsist=TRUE)
- 使用第 3 步中估算的参数推测样本的序列组成:
dada_all <- dada(derep_reads, err=dd_model[[1]]$err_out, pool=TRUE)
- 给序列分配分类:
sequence_tb <-makeSequenceTable( dada_all )
taxonomy_tb <- assignTaxonomy(sequence_tb, refFasta = file.path(getwd(), "datasets", "ch5", "rdp_train_set_14.fa"))
taxonomy_tb[1, 1:6]
它是如何工作的...
我们首先通过将包含fastq
目录的fq_dir
变量传递给list.files()
函数,创建一个所有fastq
文件路径的向量。然后,我们使用循环函数lapply()
,遍历每个fastq
文件路径,并依次运行dada
函数plotQualityProfile()
。每个结果绘图对象都会保存到列表对象quality_plots
中。当将绘图列表传递给plotlist
参数时,cowplot
函数plot_grid()
将把这些绘图以网格形式显示出来。
我们得到下图所示的绘图。请注意,fastq
质量分数在前 10 个核苷酸左右较差,且在大约 260 个核苷酸后出现问题。这些将是下一步的修剪点:
为了进行修剪,我们对 read_files
中的 fastq
文件运行一个循环。在每次循环迭代中,我们通过将文本 "trimmed.filtered"
附加到文件名上来创建一个输出的 fastq
文件名 out_fq
(因为我们将修剪后的读取结果保存到新文件中,而不是内存中),然后运行 fastqFilter()
修剪函数,传递输入文件名 fq
、输出文件名 out_fq
和修剪参数。循环结束后,我们将得到一个包含修剪过的读取文件的文件夹。通过再次使用 list.files()
函数加载这些文件的名称,这次只匹配文件名中带有 "trimmed.filtered"
模式的文件。所有这些文件都被加载到内存中,并使用 derepFaistq()
函数进行去重。接着,我们通过对部分文件使用 dada()
函数,计算组成推断步骤的参数。我们通过索引 derep_reads
对象,传递前五组去重后的文件。通过将 err
设置为 NULL
和 selfConsist
设置为 TRUE
,我们强制 dada()
从数据中估算参数,并将结果保存在 dd_model
变量中。
接下来,我们对所有数据运行 dada()
函数,将 err
参数设置为先前估算并存储在 dd_model
中的值。此步骤计算整个数据集的最终序列组成。
最后,我们可以使用 dada()
函数的结果创建序列表,并利用该表通过 assignTaxonomy()
查找 OTU。此函数使用朴素贝叶斯分类器将序列分配到分类群中,基于提供的 rdp_train_set_14.fa
文件中的训练集分类。该函数的输出是每个序列的分类。结果表格 taxonomy_tb
中的一行如下所示:
## Kingdom Phylum
## "Bacteria" "Cyanobacteria/Chloroplast"
## Class Order
## "Chloroplast" "Chloroplast"
## Family Genus
## "Bacillariophyta" NA
另见
本配方中使用的函数 fastqFilter()
和 derepFastQ()
也有用于配对读取的变体。
使用 metacoder
可视化分类丰度的热树
无论我们如何估算分类丰度,通常都需要创建一种可视化方法,以便在单一图形中总结数据的广泛趋势。一个表达力强且易于解读的可视化方式是热树。这些是对感兴趣的分类群的系统发育树的呈现,其中数据被映射到图形的视觉元素上。例如,一个分类群被观察到的次数可能通过改变树枝的颜色或粗细来表示。不同的数据集可以通过比较每个数据集的树形图来轻松发现差异。在本配方中,我们将构建一棵热树并进行自定义。
准备工作
我们需要输入 .biom
文件 datasets/ch5/rich_high_count_otu.biom
和 metacoder
、RColorBrewer
包。
如何实现...
使用 metacoder
可视化分类丰度的热树可以通过以下步骤完成:
- 加载库和输入文件:
library(metacoder)
library(RColorBrewer)
biom_file <- file.path(getwd(), "datasets", "ch5", "rich_high_count_otu.biom")
taxdata <- parse_qiime_biom(biom_file)
- 将自定义选项传递给树绘制函数:
heat_tree(taxdata,
node_label = taxon_names,
node_size = n_obs,
node_color = n_supertaxa,
layout = "gem",
title = "sample heat tree",
node_color_axis_label = "Number of Supertaxa",
node_size_axis_label = "Number of OTUs",
node_color_range = RColorBrewer::brewer.pal(5, "Greens")
)
工作原理...
首先,我们加载库并使用parse_qiime_biom()
函数从biom
文件获取一个metacoder taxmap
对象。
然后我们使用heat_tree()
函数来渲染树。只需要传递taxdata taxmap
对象——这将生成默认的树——其他的参数则是用来定制树的。node_label
指定在taxdata
对象中用于节点标签的列;在这里,我们使用taxon_names
,特别注意这里没有加引号,因为该函数使用非标准评估方式,类似于你在tidyverse
和ggplot
函数中可能已经熟悉的方式。node_size
根据给定的列控制节点大小。在这里,n_obs
和node_color
提供了影响节点颜色变化的参数(注意,这不是颜色的集合——而是应该被相同/不同颜色标记的内容)。接下来,layout
参数告诉函数如何在渲染中展开树的分支。在接下来的三个参数标题中,node_color_axis
和node_size_axis_label
仅仅是图形的标签。最后,node_color_range
获取一个颜色标识符向量,用来绘制图形。这里,我们使用RColorBrewer
包的函数brewer.pal()
,它返回这样的内容。它的第一个参数是要返回的颜色数量,第二个参数是要选择的调色板名称。设置好所有这些后,我们从我们的输入小文件中得到如下图:
使用 vegan 计算样本多样性
在生态学和宏基因组学研究中,一个常见的任务是估算样本内或样本之间的物种(或分类学)多样性,以查看哪些样本多样性较高或较低。有多种度量方法可以衡量样本内外的多样性,包括辛普森指数和布雷指数。在这个示例中,我们将查看能够从常见的 OTU 表格中返回多样性度量的函数。
准备工作
我们需要样本的.biom
输入文件,datasets/ch5/rich_high_count_otu.biom
,以及vegan
包。
如何实现……
使用vegan
计算样本多样性可以通过以下步骤完成:
- 加载库并从样本文件准备 OTU 表格:
library(vegan)
biom_file <- file.path(getwd(), "datasets", "ch5", "rich_high_count_otu.biom")
taxdata <- metacoder::parse_qiime_biom(biom_file)
otu_table <- taxdata$data$otu_table[, paste0("Sample", 1:6)]
- 计算α多样性:
alpha_diversity <- diversity(otu_table, MARGIN=2, index="simpson")
barplot(alpha_diversity)
- 计算β多样性:
between_sample <- vegdist(t(otu_table), index = "bray")
between_sample_m <- as.matrix(between_sample, ncol = 6)
corrplot::corrplot(between_sample_m, method="circle", type = "upper", diag = FALSE )
它是如何工作的……
第一步非常简单。在这里,我们使用metacoder parse_qiime_biom()
函数加载我们的biom
文件,然后对生成的taxdata$data$otu_table
插槽进行子集化,提取一个简单的 OTU 表格到otu_table
中。
现在我们可以调用vegan
包中的diversity()
函数。index
参数设置为"simpson"
,所以函数将使用辛普森指数来计算样本内的多样性。MARGIN
参数告诉函数样本是按行还是按列排列:1 表示按行,2 表示按列。diversity()
函数返回一个命名的向量,便于使用barplot()
函数进行可视化,生成如下图:
现在我们可以使用vegdist()
函数运行样本间的多样性度量;同样,index
参数设置要使用的指数,这里我们选择 Bray 指数。vegdist()
期望样本数据是按行排列的,所以我们使用t()
函数来旋转otu_table
。生成的对象存储在between_sample
中——它是一个成对相关性表格,我们可以在corrplot
中可视化它。为了做到这一点,我们需要通过as.matrix()
将其转换为矩阵;ncol
参数应与样本的数量匹配,以便为每个样本生成一列。返回的矩阵between_sample_m
可以传递给corrplot()
函数进行渲染。通过将method
设置为circle
,type
设置为upper
,并将diag
设置为false
,我们可以得到一个只显示矩阵上三角部分的图,而没有自我与自我的比较,从而减少图中的冗余。
输出结果如下:
另见...
本示例中的相关性图明确显示了几个样本的相关性,但在非常大的实验中可能变得不易处理。在此阶段,您可能需要考虑 PCA 或其他某种多维尺度方法。
将序列文件拆分为 OTU
对于经过清理和修剪的测序数据,最常见的任务之一是将序列划分为 OTU。在这方面有很多方法;在本示例中,我们将探讨一种将序列拆分为指定长度的子序列并对其执行某种层次聚类的方式,以创建组。
准备工作
这里的关键包是kmer
包,我们将使用datasets/ch5/fq
文件夹中的一个示例fastq
序列文件。我们还将使用dplyr
和magrittr
包以提高便利性。
如何做...
将序列文件拆分为 OTU 可以通过以下步骤完成:
- 加载数据并计算 OTU:
library(kmer)
library(magrittr)
library(ape)
seqs <- ape::read.fastq(file.path(getwd(), "datasets", "ch5","fq", "SRR9040914ab.fq.gz")
otu_vec <- otu(seqs, k = 6, threshold = 0.99 )
- 计算每个 OTU 簇的频率:
data.frame(
seqid = names(otu_vec),
cluster = otu_vec,
row.names = NULL) %>%
dplyr::group_by(cluster) %>%
dplyr::summarize(count = dplyr::n() )
它是如何工作的...
加载库后,我们使用ape
包中的read.fastq()
函数获取表示序列的DNAbin
对象。kmer
包中的关键函数otu()
可以直接使用DNAbin seqs
对象创建长度为k
的 k-mer,并对其执行层次聚类。threshold
参数设置 OTU 的身份阈值。该函数返回一个命名向量,其中名称为序列 ID,每个值为该序列所属簇的 ID。
然后我们可以使用otu_vec
通过data.frame
构建一个中间数据框,使用names
属性设置一个seqid
列,并将聚类成员信息放入一个名为cluster
的列中。通过将row.names
设置为NULL
,我们去掉行名称。接着,我们使用magrittr
管道符号%>%
,通过dplyr::group()
按聚类对数据框进行分组,并通过dplyr::summarize()
创建一个汇总数据框。通过将计数设置为dplyr::n()
函数的结果,我们可以得到每个聚类在命名向量中出现的次数——即每个聚类中分配到的读取次数。
第六章:从光谱到注释的蛋白质组学
质谱(MS)数据通常包含必须经过生物信息学处理的光谱,以识别候选肽。这些肽包括分配,计数可以使用各种技术和包进行分析。用于蛋白质组学的各种图形用户界面驱动工具意味着出现了多种文件格式,最初可能很难处理。这些配方将探索如何利用RforProteomics
项目中的优秀解析器和格式转换工具来分析和验证光谱,甚至向你展示如何在基因组浏览器中查看你的肽以及基因模型等其他基因组信息。
本章将涵盖以下配方:
-
以视觉方式表示原始 MS 数据
-
在基因组浏览器中查看蛋白质组学数据
-
可视化肽命中次数的分布以寻找阈值
-
转换 MS 格式以在工具之间传输数据
-
使用 protViz 将光谱与肽匹配进行验证
-
应用质量控制滤波器到光谱
-
识别与肽匹配的基因组位置
技术要求
你将需要的样本数据可以从本书的 GitHub 仓库中获取,地址是github.com/danmaclean/R_Bioinformatics_Cookbook
[.] 如果你想按原样使用代码示例,那么你需要确保该数据位于工作目录的子目录中。
以下是你将需要的 R 包。通常,你可以通过install.packages("package_name")
来安装这些包。列在Bioconductor
下的包需要通过专用的安装程序进行安装,如此处所述。如果你需要做其他事情,安装方法将在这些包所使用的配方中描述:
-
Bioconductor
-
EnsDb.Hsapiens.v86
-
MSnID
-
MSnbase
-
mzR
-
proteoQC
-
rtracklayer
-
-
data.table
-
dplyr
-
ggplot2
-
protViz
Bioconductor
非常庞大,并且拥有自己的安装管理器。你可以通过以下代码安装该管理器:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
然后,你可以使用此代码安装软件包:
BiocManager::install("package_name")
更多信息可以在www.bioconductor.org/install/
找到。
在 R 中,用户通常会加载一个库并直接通过名称使用其中的函数。这在交互式会话中非常方便,但在加载多个包时可能会造成困惑。为了明确在某一时刻我正在使用哪个包和函数,我偶尔会使用packageName::functionName()
这种惯例。
有时,在配方中间,我会中断代码,以便你查看一些中间输出或对你理解非常重要的对象结构。每当发生这种情况时,你会看到一个代码块,其中每行以##(即双哈希符号)开头。考虑以下命令:
letters[1:5]
这将产生以下输出:
## a b c d e
请注意,输出行以##
为前缀。
以可视化方式表示原始质谱数据
蛋白质组学分析的原始数据就是由质谱仪生成的光谱。每种类型的质谱仪都有不同的本地文件格式来编码光谱。检查和分析光谱从加载文件并将其强制转换为通用的对象类型开始。在这个示例中,我们将学习如何加载不同类型的文件,查看元数据,并绘制光谱本身。
准备就绪
对于这个示例,我们需要使用Bioconductor
包、mzR
包以及本书数据仓库中的一些文件,这些文件位于datasets/ch6
文件夹中。我们将使用三个不同的文件,它们的选择并不是基于文件中的数据,而是因为它们分别代表了最常见的三种质谱文件类型:mzXML
、mzdata
和mzML
。这些示例文件都来自mzdata
包。由于它们是提取出来的,你不需要安装该包,但如果你需要更多的示例文件,这个包是一个不错的选择。
如何做到...
原始质谱数据可以通过以下步骤以可视化方式表示:
- 加载库:
library(mzR)
library(MSnbase)
- 将文件加载到对象中:
mzxml_file <- file.path(getwd(), "datasets", "ch6", "threonine_i2_e35_pH_tree.mzXML")
ms1 <- openMSfile(mzxml_file)
mzdata_file <- file.path(getwd(), "datasets", "ch6", "HAM004_641fE_14-11-07--Exp1.extracted.mzdata")
ms2 <- openMSfile(mzdata_file)
mzml_file <- file.path(getwd(), "datasets", "ch6", "MM8.mzML")
ms3 <- openMSfile(mzml_file)
- 查看可用的元数据:
runInfo(ms3)
## $scanCount
## [1] 198
##
## $lowMz
## [1] 95.51765
##
## $highMz
## [1] 1005.043
##
## $dStartTime
## [1] 0.486
##
## $dEndTime
## [1] 66.7818
##
## $msLevels
## [1] 1
##
## $startTimeStamp
## [1] "2008-09-01T09:48:37.296+01:00"
sampleInfo(ms1)
## [1] ""
- 绘制光谱:
msn_exp <- MSnbase::readMSData(mzxml_file)
MSnbase::plot(msn_exp, full = TRUE)
MSnbase::plot(msn_exp[5], full = TRUE)
它是如何工作的...
在步骤 1中,我们加载了所需的库。主要的库是mzR
。
在步骤 2中,我们使用系统无关的file.path()
函数定义了我们将要加载的文件路径,该函数返回一个包含文件名的字符向量。然后,我们使用该文件名通过mzR
中的openMSfile()
函数来创建一个代表相应文件中数据的mzR
对象。请注意,我们本质上运行了相同的代码三次,只是每次更改文件和输入文件类型。openMSfile()
函数会自动检测文件的格式。
在步骤 3中,我们使用mzR
包的访问器函数runInfo()
和sampleInfo()
来提取输入文件中的一些元数据。请注意,sampleInfo()
与ms1
一起使用时没有返回任何内容——这是因为该特定文件中没有包含这些数据。可以返回的元数据取决于文件和文件类型。
在步骤 4中,我们使用MSnbase
包的readMSData()
函数加载文件。该函数在后台使用mzR
,因此可以执行相同的操作,但它返回一个修改后的MSnbase
类对象。这意味着一些通用的绘图函数将会生效。接着,我们使用plot()
函数创建文件中所有光谱的图像:
然后,通过使用索引,我们创建了仅包含文件中第五个光谱的图像:
在基因组浏览器中查看蛋白质组学数据
一旦我们获取了质谱数据,并使用如 Xtandem、MSGF+或 Mascot 等搜索引擎软件识别了光谱描述的肽段和蛋白质,我们可能希望在其基因组上下文中查看这些数据,并与其他重要数据一起展示。在本食谱中,我们将展示如何从搜索文件中提取肽段和 Uniprot ID,找到这些 Uniprot ID 映射的基因,并创建一个显示这些基因的基因组浏览器轨道。可以将这些发送到 UCSC 人类基因组浏览器,互动网页将会自动在本地浏览器中加载。
准备工作:
对于这个食谱,您需要安装 Bioconductor 包MSnID
、EnsDB.Hsapiens.v86
和rtracklayer
,以及本书仓库中datasets/ch6
文件夹下的HeLa_180123_m43_r2_CAM.mzid.gz
文件。为了使这个食谱生效,您还需要连接到互联网,并拥有一个能够运行 UCSC 基因组浏览器的现代浏览器,网址是genome.ucsc.edu
。
如何操作...
通过以下步骤可以在基因组浏览器中查看蛋白质组学数据:
- 加载库:
library(MSnID)
library(EnsDb.Hsapiens.v86)
library(rtracklayer)
- 创建并填充搜索文件对象:
msnid <- MSnID()
msnid <- read_mzIDs(msnid, file.path(getwd(), "datasets", "ch6", "HeLa_180123_m43_r2_CAM.mzid.gz"))
- 提取包含有用命中的行和包含有用信息的列:
real_hits <- msnid@psms[! msnid@psms$isDecoy, ]
required_info <- real_hits[, c("spectrumID", "pepSeq", "accession", "start", "end")]
- 从
accession
列提取 Uniprot ID:
uniprot_ids <- unlist(lapply(strsplit(required_info$accession, "\\|"), function(x){x[2]}) )
uniprot_ids <- uniprot_ids[!is.na(uniprot_ids)]
- 创建数据库连接并获取与我们的 Uniprot ID 匹配的基因:
edb <- EnsDb.Hsapiens.v86
genes_for_prots <- genes(edb,
filter = UniprotFilter(uniprot_ids),
columns = c("gene_name", "gene_seq_start", "gene_seq_end", "seq_name"))
- 设置基因组浏览器轨道:
track <- GRangesForUCSCGenome("hg38",
paste0("chr",seqnames(genes_for_prots)),
ranges(genes_for_prots),
strand(genes_for_prots),
genes_for_prots$gene_name,
genes_for_prots$uniprot_id )
- 设置浏览器会话并查看:
session <- browserSession("UCSC")
track(session, "my_peptides") <- track
first_peptide <- track[1]
view <- browserView(session, first_peptide * -5, pack = "my_peptides")
工作原理...
步骤 1是我们的标准库加载步骤。
步骤 2是数据加载步骤。这一步有点特殊。我们不仅仅调用一个文件读取函数,而是首先创建并清空MSnID
对象,并将数据加载到其中。我们使用MSnID()
函数创建msnid
,然后将其传递给read_mzid()
函数,实际上将数据加载到其中。
步骤 3关注于从msnid
对象中提取我们关心的信息。我们需要的是匹配实际命中的行,而不是诱饵行,因此我们直接访问msnid@psms
槽,该槽包含有用数据,并筛选出isDecoy
值为FALSE
的行。这将给我们一个对象,我们将其保存在real_hits
变量中。接下来,我们使用real_hits
从原始对象中的许多列中选择一些有用的列。
步骤 4 帮助我们提取嵌入在 accession 列字段中的 Uniprot ID。需要注意的是,这些值来自于搜索引擎数据库中使用的名称。自然地,这一步会根据数据库的精确格式有所不同,但一般的模式适用。我们有一组嵌套得相当密集的函数,其解析过程如下:内层的匿名函数function(x){x[2]}
返回它所传入的任何向量的第二个元素。我们使用lapply()
将这个函数应用于从 strsplit()
函数返回的 accession 列中的每个元素。最后,由于lapply()
返回的是列表,我们使用unlist()
将其展开成我们所需的向量。有时,这会生成 NAs,因为某些 Uniprot ID 可能不存在,所以我们通过子集和 is.na()
将其从向量中删除。
在 步骤 5 中,我们连接到 Ensembl 数据库包,并使用 genes()
函数获取与我们的 Uniprot ID 匹配的 Ensembl 基因。Uniprot ID 的向量被传递到 UniprotFilter()
函数中,并且通过 columns
参数,我们选择从数据库中返回的数据。这为我们提供了一个 GRanges
对象,包含了构建浏览器轨道所需的所有信息。
在 步骤 6 中,我们使用辅助函数GRangesForUCSCGenome()
,并传入我们希望查看的基因组版本——hg38
,然后是基本的染色体名称、坐标和链信息,这是创建 GRanges
对象所需的数据。我们可以使用 seqnames()
、ranges()
和 strand()
访问函数,从我们之前创建的 genes_for_prots
对象中提取这些信息。UCSC 中的序列名称以 chr
为前缀,因此我们使用 paste
将其添加到我们的序列名称数据中。我们还为基因名称和基因 ID 创建了列,以便在最终的视图中保留这些信息。我们将结果保存在 track
变量中。
最后,在 步骤 7 中,我们可以渲染我们创建的轨道。首先,我们创建一个表示 UCSC 会话的会话对象,并分别使用 session()
和 track()
函数将轨道添加到其中。我们通过将第一个肽传递给 view()
函数来选择关注的肽,view()
函数会实际打开一个新的网页浏览器窗口,显示请求的数据。view()
的第二个参数指定缩放级别,通过将参数公式化为 first_peptide * -5
,我们可以获得一个可以容纳五个请求特征的缩放视图。
在写作时,这个配方生成了如下视图。请注意,最顶部的轨道是我们的 my_peptides
轨道:
还有更多内容……
你可能注意到,这个示例实际上绘制的是整个基因,而不是我们最初开始时的肽段命中数据。绘制基因是最简单的情况,但要绘制肽段只需要稍作修改。在步骤 5中,我们创建了一个对象genes_for_prots
,它给出了基因的起始和结束位置。早期的msnid@psms
对象包含这些基因内肽段的起始和结束位置,这些位置是从命中开始处索引的,因此通过将两个数据合并在一起,就可以创建一个代表肽段而非基因的对象。
对于那些没有使用 UCSC 浏览器中的生物体的用户,仍然可以生成命中的 GFF 文件,并上传到其他基因组浏览器——许多浏览器都提供这种功能。只需在步骤 5结束时停止该示例,并使用rtracklayer::export()
函数创建一个 GFF 文件。
可视化肽段命中数分布以找到阈值
每个质谱实验都需要了解哪些肽段命中数代表噪声或异常特征,例如在蛋白质组中出现过度表达的肽段。在这个示例中,我们将使用tidyverse
工具,如dplyr
和ggplot
,结合一些巧妙的可视化技巧,创建图形,帮助你了解质谱实验中肽段命中的分布和限制。
准备工作
对于这个示例,你将需要MSnId
、data.table
、dplyr
和ggplot
包。我们将使用来自本书仓库datasets/ch6
文件夹中的mzid
文件HeLa_180123_m43_r2_CAM.mzid.gz
。
如何实现...
可视化肽段命中数分布以找到阈值,可以使用以下步骤完成:
- 加载库和数据:
library(MSnID)
library(data.table)
library(dplyr)
library(ggplot2)
msnid <- MSnID()
msnid <- read_mzIDs(msnid, file.path(getwd(), "datasets", "ch6", "HeLa_180123_m43_r2_CAM.mzid.gz"))
peptide_info <- as(msnid, "data.table")
- 过滤掉虚假数据行,并统计每个肽段出现的次数:
per_peptide_counts <- peptide_info %>%
filter(isDecoy == FALSE) %>%
group_by(pepSeq) %>%
summarise(count = n() ) %>%
mutate(sample = rep("peptide_counts", length(counts) ) )
- 创建一个小提琴图和抖动图,显示命中计数:
per_peptide_counts %>%
ggplot() + aes( sample, count) + geom_jitter() + geom_violin() + scale_y_log10()
- 创建一个肽段命中数的累计图,并按命中数排序:
per_peptide_counts %>%
arrange(count) %>%
mutate(cumulative_hits = cumsum(count), peptide = 1:length(count)) %>%
ggplot() + aes(peptide, cumulative_hits) + geom_line()
- 过滤掉非常低和非常高的肽段命中数,然后重新绘制它们:
filtered_per_peptide_counts <- per_peptide_counts %>%
filter(count >= 5, count <= 2500)
filtered_per_peptide_counts %>%
ggplot() + aes( sample, count) + geom_jitter() + geom_violin() + scale_y_log10()
它是如何工作的...
在步骤 1中,我们进行了一些库加载,并添加了数据加载步骤。如前所述,使用MSnID
时,情况有些不同。我们不是直接调用文件读取函数,而是首先创建并清空MSnID
对象,然后将数据加载到其中。我们通过MSnID()
函数创建msnid
,然后将其传递给read_mzid()
函数,以实际将数据放入其中。接下来,我们使用as()
函数将msnid
转换为data.table
对象——一个像数据框的对象,专门为大数据集优化。
在步骤 2中,我们使用tidyverse
包中的dplyr
和ggplot
准备图形。tidyverse
包相互协作得非常好,因为它们专注于处理数据框。通常的工作方式是使用管道操作符%>%
将数据从一个函数传递到另一个函数,而无需保存中间对象。按照惯例,上游函数的结果作为下游函数的第一个参数传递,因此我们不需要显式指定它。这就形成了我们所见的结构。我们将peptide_info
对象通过%>%
操作符传递给dplyr filter()
函数,后者执行工作并将结果传递给group_by()
函数,依此类推。每个函数执行完后将数据传递给下一个函数。因此,在这个管道中,我们使用filter()
保留所有不是诱饵的行,然后使用group_by(pepSeq)
将长data.table
根据pepSeq
行的值分组为子表——实际上就是按肽序列获取一个表格。接下来的步骤使用summarise()
,它生成一个包含count
列的汇总表,count
列的内容是n()
函数的结果,n()
函数统计表格中的行数,得出的表格每行代表一个肽,告诉我们这个肽在表中出现的次数。如果不清楚这些对象是如何逐步构建起来的,逐个函数地调试代码是个好主意。最后,我们使用mutate()
添加一个名为sample
的新列到表中,这列的长度与当前表相同,并填充为peptide_counts
,然后将其添加到表中。该表被保存在名为per_peptide_counts
的变量中。
在步骤 3中,我们将per_peptide_counts
数据传递给ggplot()
函数,该函数会设置一个ggplot
对象。这些是内置层,因此我们使用+
操作符通过aes()
函数添加一个美学层。这个层通常包含要绘制在 x 轴和 y 轴上的变量——在这里,它们是sample
和count
。然后,我们再次使用+
来添加一个geom
——一个定义图形外观的层。首先,我们添加geom_jitter()
,它绘制数据点,并在 x 轴和 y 轴上添加一些随机噪声,使点略微分散开。接着,我们添加另一个 geom,geom_violin()
,它生成一个小提琴密度图。最后,我们添加一个尺度层,将尺度转换为以 10 为底的对数尺度。最终的图形如下所示:
在第 4 步中,我们通过将per_peptide_counts
数据传递给arrange()
函数,创建了一个累计命中图表。该函数按照指定的变量(在此案例中为计数)对数据框进行升序排序。结果被传递给mutate()
,以添加一个新列cumulative_hits
,它的值是cumsum()
函数对计数列的计算结果。我们还添加了一个名为peptide
的列,它获取表格的行号,同时也提供了一个方便的变量,使我们可以在图表中按顺序排列肽段。我们可以通过将排序后的数据直接传递给ggplot()
并添加aes()
函数来生成图表,使得peptide
显示在 x 轴,cumulative_hits
显示在 y 轴。然后,通过添加geom_line()
,生成的图表如下所示:
从两个图表中,我们可以看到数据点的分布,并评估我们希望应用的阈值。
在第 5 步中,我们再次使用filter()
函数,保留计数值大于 5 且小于 2500 的行,并将这些新数据放入我们在第 3 步中创建的同一绘图配方中。这将生成如下图表,显示了阈值外的点被移除:
转换 MS 格式以在工具之间移动数据
在生物信息学中,我们花费大量时间在不同文件格式之间转换,这是不可避免的事实。在这个简短的配方中,我们将介绍一些 R 语言中的方便方法,帮助我们在 MS 数据格式之间进行转换。
准备工作
对于这个配方,我们需要使用mzR
包和来自本书仓库datasets/ch6
文件夹中的threonine_i2_e35_pH_tree.mzXML
文件。某些依赖项依赖于封装的 Java 代码,因此你需要为你的系统安装Java 运行时环境(JRE);有关安装说明,请参考docs.oracle.com/goldengate/1212/gg-winux/GDRAD/java.htm
。请在安装 R 包之前安装 JRE。
如何操作...
使用以下步骤可以在工具之间转换 MS 格式:
- 加载库并导入源数据文件:
library(mzR)
mzxml_file <- file.path(getwd(), "datasets", "ch6", "threonine_i2_e35_pH_tree.mzXML")
mzdata <- openMSfile(mzxml_file)
- 提取标题和峰数据:
header_info <- header(mzdata)
peak_data_list <- spectra(mzdata)
- 将数据写入新格式的文件:
writeMSData(peak_data_list,
file.path(getwd(), "datasets", "ch6", "out.mz"),
header = header_info,
outformat = "mzml",
rtime_seconds = TRUE
)
它是如何工作的...
第一步是一个简单的数据加载步骤,我们在之前的配方中见过。我们使用openMSfile()
函数,它会自动检测输入文件类型。
第 2 步是关键步骤;为了创建输出,我们需要制作一个标题对象和一个峰值列表。因此,我们使用header()
和spectra()
访问器函数从我们的mzdata
对象中提取它们。输出函数将需要一个列表,因此如果文件中只有一个光谱,使用list()
函数将spectra()
函数包装起来。
最后的步骤是写入文件;在这里,第一个参数是峰值列表,第二个参数是要创建的文件名称,第三个参数是你选择的输出格式—可以选择mzml
、mzxml
和mzdata
。最后一个参数表示保留时间是否以秒为单位编码;选择FALSE
会将输出设置为以分钟为单位。
使用 protViz 进行光谱与肽段的匹配验证
尽管大多数光谱/肽段匹配是在高通量搜索引擎中完成的,但有时你可能希望检查竞争的模糊匹配的质量,或者与一个完全任意的感兴趣序列进行比较。运行整个搜索引擎管道可能是过于复杂的操作,因此在本教程中,我们将介绍一种便捷的方法,用于将单个光谱与单个肽段序列匹配,并生成理论离子大小与光谱中存在的离子之间的吻合图。
准备工作
对于本教程,我们只需要protViz
包、mzR
包以及本书仓库中datasets/ch6
文件夹下的MM8.mzml
文件。
如何实现...
使用protViz
将光谱与肽段匹配可以通过以下步骤完成:
- 加载库和 MS 数据:
library(mzR)
library(protViz)
mzml_file <- file.path(getwd(), "datasets", "ch6", "MM8.mzML")
ms <- openMSfile(mzml_file)
- 从光谱中提取峰值和保留时间:
p <- peaks(ms,2)
spec <- list(mZ = p[,1], intensity = p[,2])
- 创建理论与观测离子质量的图表:
m <- psm("PEPTIDESEQ", spec, plot=TRUE)
它是如何工作的...
在步骤 1中,我们加载所需的库,并使用mzR
函数openMSFile()
来创建代表质谱数据的对象。
在步骤 2中,我们使用peaks()
函数,该函数会将保留时间和峰值强度提取为矩阵对象。注意,第一列包含保留时间,而第二列包含强度。peaks()
的第二个参数是我们需要的光谱的索引,因此我们正在获取该文件中的第二个光谱。如果省略此参数,则会返回所有光谱的列表。接下来的步骤中,我们需要将保留时间和强度数据封装在一个列表中,我们使用list()
函数完成此操作,列表的成员命名为mZ
和intensity
。
最后,我们可以使用psm()
函数生成图表。该函数的第一个参数是一个序列(这里是一个无意义的序列,保证匹配不佳),第二个参数是我们之前创建的光谱数据列表。通过将图表参数设置为TRUE
,我们可以得到如下结果图表:
在图表中,每个点表示预测离子质量与光谱中最接近的观测质量之间的差异。我们可以看到,离子 b8、b7 和 c1 的质量偏差约为 1 Da,或者与任何预测质量相比更为分散,表明该肽段序列与光谱的拟合效果较差。
对光谱应用质量控制过滤器
原始蛋白质组学数据的质量控制是确保管道和分析能够给出可信和有用结果的关键步骤。需要大量的指标和数据图表来评估特定实验是否成功,这意味着在我们开始从数据中提取任何新知识之前,必须进行大量的分析。在这个配方中,我们将查看一个集成的管道,该管道执行一系列相关且有用的质量控制步骤,并将结果呈现为一个单一的、易于阅读的报告。
准备工作
在这个配方中,我们将研究大肠杆菌细胞膜的蛋白质组学实验。由于该文件太大,无法在本书的代码库中托管,因此我们将通过代码直接下载它。因此,在进行此实验时,您需要保持在线。我们还需要目标有机体的肽段文件,即Escherichia_coli.pep.all.fa
文件,可以在本书代码库的datasets/ch6
文件夹中找到。我们的主要功能将来自proteoQC
库。
如何操作...
可以使用以下步骤将质量控制过滤器应用于光谱:
- 加载库并下载源数据:
library(proteoQC)
online_file <- "ftp://ftp.pride.ebi.ac.uk/pride/data/archive/2017/11/PXD006247/CS_130530_ORBI_EMCP2156_b2469_narQ_DDM_AmH_X_5.mzXML"
mzxml_file <- file.path(getwd(), "datasets", "ch6", "PXD006247_mz.xml.gz" )
download.file(online_file, mzxml_file, "internal")
- 创建设计文件:
design_df <- data.frame(
file = c(mzxml_file),
sample = c(1),
bioRep = c(1),
techRep = c(1),
fraction = c(1)
)
design_file <- file.path(getwd(), "datasets", "ch6", "design_file.txt")
write.table(design_df, file = design_file, quote = FALSE, row.names = FALSE)
- 设置质量控制管道并运行以下命令:
qc <- msQCpipe(
spectralist = design_file,
fasta = file.path(getwd(), "datasets", "ch6", "Escherichia_coli.pep.all.fa"),
outdir = file.path(getwd(), "qc_result"),
enzyme = 1, varmod = 2, fixmod =1,
tol = 10, itol = 0.6, cpu = 2,
mode = "identification"
)
它是如何工作的...
在步骤 1中加载库后,我们设置要从www.proteomexchange.org/
获取的文件的 URL;我们只需要获取PXD006247
这个文件,并将 URL 保存在online_file
变量中。我们还创建了一个指向本地文件系统中不存在的文件PXD006247_mz.xml.gzX
的mzmxl_file
变量——这将是下载文件保存的名称。download.file()
函数实际上执行下载;第一个参数是在线源,第二个参数是文件下载到本地机器的存放位置。最后一个参数internal
是下载方法。我们选择的设置应该使用一个系统无关的下载器,可以在任何地方使用,但如果你喜欢,也可以将其更改为其他更快或更符合系统的设置。文档将解释这些选项。
在步骤 2中,我们创建一个设计文件,描述实验。在我们的小示例中,我们只有一个文件,但你可以在此处指定更多文件。在第一部分,我们创建一个数据框,包含文件、样本、生物重复、技术重复和分级列。我们只有一个文件,所以表格只有一行。它看起来是这样的:
文件 | 样本 | 生物重复 | 技术重复 | 分级 |
---|---|---|---|---|
PXD006247_mz.xml.gz |
1 | 1 | 1 | 1 |
如果你的实验更为复杂,那么每个文件会有更多的行来描述样本和生物重复(bioRep)。接下来,我们使用write.table()
和适当的选项将这个文件保存到磁盘,以便在下一步中使用。需要注意的是,虽然为了演示目的,我们是通过编程方式创建了这个文件,但如果我们通过电子表格程序或文本编辑器手动创建它,这个文件同样有效。
最后,我们在第 3 步中设置并运行 QC 流程。主要函数msQCpipe()
是核心部分,需要一些选项设置。spectralist
选项需要指向我们创建的设计文件路径,以便知道打开哪些文件以及如何处理它们。fasta
选项需要目标生物体蛋白质序列的fasta
格式文件。这使得 QC 流程能够使用rtandem
包中的XTandem
进行谱肽识别。outdir
参数指定了一个新文件夹的路径,用于保存将要创建的众多报告文件。在这里,我们的文件夹将被命名为qc_result
,并且它将是当前工作目录的一个子目录。enzyme
、varmod
和fixmod
参数分别描述了用于消化的酶(1 = 胰蛋白酶)、可能存在的可变修饰以及所有残基上将存在的固定修饰。tol
和itol
参数指定了肽质量值的容差和误差窗口。cpu
参数指定了源机器上要使用的计算核心数,而mode
参数指定了运行的类型。
当 QC 流程完成后,我们会在qc_result
文件夹中得到一系列报告。qc_report.html
文件包含了可以浏览的 QC 结果。多个描述结果的页面应该能够让你了解实验的成功程度。
还有更多…
要找到合适的enzyme
、varmod
和fixmod
变量值,你可以使用showMods()
和showEnzymes()
函数查看列表及其关键数字。
确定与肽匹配的基因组位点
找到基因组中肽匹配的确切位置可能是一个具有挑战性的任务,尤其是当基因组没有出现在原始搜索文件中时。在这个方法中,我们将结合使用经典的命令行 BLAST 方法,在翻译后的基因组序列中寻找短的、几乎精确的肽匹配,并通过针对 BLAST 命中的GRanges
对象,将其与各种 R 基因组学管道相结合。
准备就绪
对于这个食谱,我们将使用MSnID
、dplyr
、withR
、GenomicRanges
和Biostrings
包,以及来自大肠杆菌谱图的搜索引擎输出文件,该文件可以在本书的datasets/ch6
文件夹中的PXD006247.mzXML.mzid
文件中找到。你还需要本地安装 BLAST+版本。可以通过 conda 包管理器使用conda install -c bioconda blast
命令安装。你还需要知道 BLAST+中的 tblastn 程序安装的位置。可以在 macOS 和 Linux 系统上使用终端命令which tblastn
找到,Windows 系统亦然。
如何做...
可以使用以下步骤来识别与肽段匹配的基因组位点:
- 加载库和数据:
library(MSnID)
library(dplyr)
library(Biostrings)
msnid <- MSnID() # create object
msnid <- read_mzIDs(msnid, file.path(getwd(), "datasets", "ch6", "PXD006247.mzXML.mzid"))
peptide_info <- as(msnid, "data.table") %>%
filter(isDecoy == FALSE) %>%
select(spectrumID, pepSeq, ) %>%
mutate(fasta_id = paste0( spectrumID, ":", 1:length(spectrumID)) )
- 提取肽段序列并将其保存为 fasta 文件:
string_set <- AAStringSet(peptide_info$pepSeq )
names(string_set) <- peptide_info$fasta_id
writeXStringSet(string_set[1], file.path(getwd(), "datasets", "ch6", "peptides.fa"))
- 准备 BLAST 运行的文件名:
input_seqs <- file.path(getwd(), "datasets", "ch6", "peptides.fa")
genome_seqs <- file.path(getwd(), "datasets", "ch6", "ecoli_genome.fasta")
output_blast <- file.path(getwd(), "datasets", "ch6", "out.blast")
- 准备
BLAST
命令:
command <- paste0(
"tblastn",
" -query ", input_seqs ,
" -subject ", genome_seqs,
" -out ", output_blast,
" -word_size 2 -evalue 20000 -seg no -matrix PAM30 -comp_based_stats F -outfmt 6 -max_hsps 1"
)
- 将 BLAST 作为后台进程运行:
library(withr)
with_path("/Users/macleand/miniconda2/bin", system(command, wait = TRUE) )
- 将 BLAST 转换为
GFF
和GRanges
:
results <- read.table(output_blast)
blast_to_gff <- function(blst_res){
blst_res %>%
mutate(
seqid = V2,
source = rep("tblastn", length(V1)),
type = rep(".", length(V1)),
start = V9,
end = V10,
score = V3,
strand = rep(".", length(V1)),
phase = rep(".", length(V1)),
attributes = paste("Name=",V1)
) %>%
select( - starts_with("V") )
}
gff_df <- blast_to_gff(results)
library(GenomicRanges)
granges<-makeGRangesFromDataFrame(gff_df)
它是如何工作的...
步骤 1加载库,并使用MSnID
包将数据加载到一个对象中,然后使用dplyr
管道进行处理,正如本章中食谱 3的步骤 2所描述的。如果你不熟悉这种语法,可以查看那里进行深入解释。简而言之,虽然管道移除了假设行,但它仅保留spectrumID
和pepSeq
列,并添加了一个名为fasta_id
的新列,该列将谱图 ID 粘贴为唯一编号。结果数据框被保存到peptide_info
变量中。
步骤 2使用peptide_info$pepSeq
列和peptide_info$fasta_id
列的名称,通过names()
函数创建一个Biostrings
对象。然后,使用writeXStringSet()
函数将结果BioStrings
字符串集对象以 fasta 格式写入名为peptides.fa
的文件。请注意,string_set
末尾的索引[1]
;这是一个小技巧,确保只写入第一个肽段。我们之所以这样做,仅仅是因为这是一个演示,且我们希望代码在短时间内完成。对于真实分析,你可以完全去掉索引,写入所有的序列。
在步骤 3中,我们只是设置了 BLAST 运行的输入和输出文件名。请注意,我们映射到的参考基因组ecoli_genome.fasta
将位于本书的datasets/ch6
文件夹中的仓库内。
在步骤 4中,我们指定了BLAST
命令,这里的代码只是简单地将变量和文本粘贴在一起,形成一个长字符串并保存到命令中。值得详细查看的是,第一行指定了要运行的 BLAST+程序;这里是tblastn
,它使用蛋白质输入和翻译后的核苷酸数据库。接下来的三行指定了输入的肽序列、用于 BLAST 的参考基因组,以及保存结果的输出文件。最后的长行指定了 BLAST+选项,以允许进行短小且几乎精确的匹配。设置了这些特定选项后,BLAST 运行可能需要一些时间,因此在开发过程中建议只运行一个序列。
在步骤 5中,指定了BLAST
命令后,我们可以执行实际的 BLAST。我们这里的主要函数是基本的 R 函数system()
,它将在后台运行系统命令。然而,为了帮助这个函数在不同系统之间移植,我们使用了withR
库中的with_path()
函数,它暂时将一个特定文件夹添加到系统的 PATH 中——这个 PATH 是包含程序的文件夹列表。这个步骤是必要的,因为有时 R 和 RStudio 不会识别非标准的安装位置,比如 conda 包管理器使用的那些位置。因此,这里第一个参数是tblastn
文件夹的路径。注意,/Users/macleand/miniconda2/bin
是我机器上的路径;你需要使用类似which tblastn
的命令在终端或命令行中获取你机器上的路径,并进行替换。添加路径后,with_path()
将运行其第二个参数,我们的system()
函数,进而运行 BLAST。实际运行 BLAST 程序会花费一些时间。
一旦命令完成,在步骤 6中,我们首先通过read.table()
函数将 BLAST 生成的输出文件加载到结果变量中。然后,我们创建一个自定义函数,将结果的行转换为 GFF 兼容的表格。blast_to_gff()
函数使用dplyr mutate()
函数添加相关列,然后使用select()
函数与-
选项选择不以字母 V 开头的列,因为所有原始列的名称都是以 V 开头的。现在我们可以使用GenomicRanges
函数makeGRangesFromDataFrame()
,将我们的 GFF 风格数据框转换为GRanges
对象。这是最后一步,我们现在拥有一个匹配肽的基因组位置对象,可以在 R 的所有标准基因组学管道中使用,并且可以在本书中的基因组学配方中使用。
第七章:生成出版物和 Web-ready 可视化图形
设计和制作出版质量的可视化图形是生物信息学家的核心任务之一,也是他们在数据处理过程中最具成就感的部分。R 提供了许多优秀的图形制作包,超越了强大的基础图形系统和ggplot2
。在本章的配方中,我们将探讨如何为许多不同类型的数据制作图表,这些数据通常不是典型的条形图/散点图类型。我们还将讨论网络、交互式图形、3D 图形和圆形基因组图。
本章将介绍以下配方:
-
使用 ridgeplots 可视化多个分布
-
为双变量数据创建色图
-
将关系数据表示为网络
-
使用 plotly 创建交互式 Web 图形
-
使用 plotly 构建三维图形
-
构建多组学数据的圆形基因组图
技术要求
本章所需的示例数据可以在本书的 GitHub 仓库中找到:github.com/danmaclean/R_Bioinformatics_Cookbook
. 如果你想按照书中的代码示例运行,你需要确保数据位于工作目录的子目录中。
以下是你需要的 R 包。你可以使用install.packages("package_name")
来安装它们。列在Bioconductor
下的包需要使用专用的安装器进行安装,安装步骤也在本节中描述。如果你需要做其他操作,安装步骤将在包使用的配方中描述:
-
circlize
-
dplyr
-
ggplot2
-
ggridges
-
gplots
-
plotly
-
RColorBrewer
-
readr
-
magrittr
-
tidyr
-
viridis
Bioconductor
非常庞大,且有自己的安装管理器。你可以使用以下代码安装该管理器:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
然后,你可以使用以下代码安装这些包:
BiocManager::install("package_name")
更多信息可以在www.bioconductor.org/install/
中找到。
通常,在 R 中,用户会加载一个库并直接通过名称使用其中的函数。这在交互式会话中非常有效,但当加载多个包时,可能会导致混淆。为了明确在某一时刻我使用的是哪个包和函数,我会偶尔使用packageName::functionName()
的约定。
有时,在配方的中间,我会打断代码流程,让你看到一些中间输出或对理解非常重要的对象结构。每当发生这种情况,你会看到一个代码块,其中每行以##双哈希符号开头。请考虑以下命令:
letters[1:5]
这将产生以下输出:
## a b c d e
请注意,输出行以##
为前缀。
使用 ridgeplots 可视化多个分布
可视化某些测量数量的分布是生物信息学中的一项常见任务,R 的基础功能通过hist()
和density()
函数以及通用的plot()
方法,能很好地处理这种任务,后者可以创建对象的图形。ggplot
图形系统以一种整洁的方式绘制多个密度图,每个因子水平对应一个图形,从而生成一个紧凑且非常易读的图形——即所谓的山脊图。在这个示例中,我们将看看如何创建一个山脊图。
准备工作
在这个示例中,我们将使用ggplot
和ggridges
包。数据集方面,我们将使用datasets
包中的一个数据集,该包通常是 R 的预安装包之一。我们将使用airquality
数据集。你可以直接在 R 控制台中输入airquality
来查看它。
如何实现...
使用山脊图可视化多个分布可以通过以下步骤完成:
- 加载库:
library(ggplot2)
library(ggridges)
- 构建一个
ggplot
描述:
ggplot(airquality) + aes(Temp, Month, group = Month) + geom_density_ridges()
- 显式将
Month
转换为因子:
ggplot(airquality) + aes(Temp, as.factor(Month) ) + geom_density_ridges()
- 给山脊上色:
ggplot(airquality) + aes(Temp, Month, group = Month, fill = ..x..) +
geom_density_ridges_gradient() +
scale_fill_viridis(option = "C", name = "Temp")
- 重塑数据框并添加面板:
library(tidyr)
airquality %>%
gather(key = "Measurement", value = "value", Ozone, Solar.R, Wind, Temp ) %>%
ggplot() + aes(value, Month, group = Month) +
geom_density_ridges_gradient() +
facet_wrap( ~ Measurement, scales = "free")
工作原理...
在步骤 1中加载所需的库后,步骤 2中我们使用ggridges
包的geom_ridges()
函数创建了一个标准的ggplot
描述。如果你以前没见过ggplot
图,它们非常简单。一个ggplot
图有三个层次,至少需要通过三个函数构建——ggplot()
函数始终是第一个,它允许我们指定数据集。接下来,使用+
运算符添加的函数是aes()
函数或美学函数,我们可以将其视为我们希望在图表中看到的内容。第一个参数表示X轴上的元素,而第二个参数表示Y轴上的元素。group = Month
参数是特定于山脊图的,它告诉绘图函数如何对数据点进行分组。由于Month
数据是数字型,而不是因子型,因此这里需要此参数。最后,我们添加geom_density_ridges()
来创建正确类型的图。
在步骤 3中,我们遵循了与步骤 2类似的程序,但这次,我们作为替代方法使用了as.factor(Month)
,它在处理和渲染分组之前显式地将Month
数据转换为因子类型。因此,Month
的步骤变得不再必要。这些步骤生成的图形如下,步骤 2在左,步骤 3在右:
在第 4 步中,我们为山脊添加了颜色。基本上,ggplot
构建过程没有变化,只是在 aes()
函数中增加了 fill = ..x..
,它告诉绘图填充 x 轴方向的颜色。然后我们使用了一个稍微不同的 geom
函数,geom_density_ridges_gradient()
,它能够为山脊着色。在最后一层,我们使用 scale_fill_viridis()
,选择了来自 viridis 颜色库的颜色尺度(该库在顶部加载)。"C"
选项指定了特定的颜色尺度,而 name
用来指定尺度的名称。结果图像如下所示:
最后,在第 5 步中,我们通过进一步的维度对数据进行了拆分,并在相同样式的图中添加了包含同一数据集不同方面的面板。为了实现这一点,airquality
数据需要进行一些预处理。我们加载了 tidyr
包,并使用 gather()
函数将命名列的值(具体是 Ozone、Solar.R、Wind 和 Temp)合并到一个名为 value 的列中,并添加一个名为 Measurement 的新列,记录原始列中观察值的来源。然后,我们将结果传递到 ggplot()
。构建过程几乎与之前相同(请注意,我们的 x 轴现在是 value,而不是 Temp,因为温度数据已经存储在重塑后的数据框中),最后添加了 facet_wrap()
函数,它使用公式符号选择数据的子集并显示在各个面板中。选项 scales 为 "free"
,允许每个结果面板有独立的坐标轴刻度。结果如下所示:
为双变量数据创建颜色映射
颜色映射,也称为热图,是二维矩阵的绘图,其中数值在特定尺度上转换为颜色。我们可以通过多种方式在 R 中绘制这些图表;大多数图形包都提供了这类功能。在本教程中,我们将使用基础包的 heatmap()
函数来可视化一些矩阵。
准备工作
我们只需要 ggplot
包和内置的 WorldPhones
数据集。
操作步骤...
为双变量数据创建颜色映射可以通过以下步骤完成:
- 创建基本的热图:
heatmap(WorldPhones)
- 移除树状图:
heatmap(WorldPhones, Rowv = NA, Colv = NA)
- 为分组添加颜色尺度:
cc <- rainbow(ncol(WorldPhones), start = 0, end = .3)
heatmap(WorldPhones, ColSideColors = cc)
- 更改调色板:
library(RColorBrewer)
heatmap(WorldPhones, ColSideColors = cc,
col = colorRampPalette(brewer.pal(8, "PiYG"))(25))
- 重新缩放数据:
heatmap(WorldPhones, ColSideColors = cc, scale = "column")
它是如何工作的...
在第 1 步中,我们将基础的 heatmap()
函数应用于一个矩阵,返回一个如下所示的图:
在第 2 步中,我们使用了 Rowv
和 Colv
参数来去除树状图。请注意,在结果图中,列的顺序与矩阵中的顺序一致。通过使用树状图,我们可以重新排列列和行。没有树状图的图表如下所示:
在第 3 步中,我们使用rainbow()
函数创建了一个调色板对象,该函数返回绘图所需的颜色。rainbow()
的第一个参数是颜色的数量。这里,我们使用ncol(WorldPhones)
来为数据集的每一列获取一种颜色。start
和end
参数指定了彩虹中颜色选择的起始和结束位置。然后,我们可以在ColSideColors
参数中使用CC调色板对象,以获取列的颜色键。我们可以使用更多相似的列来获得更多相似的颜色,具体如下:
在第 4 步中,我们为col
参数提供了一个调色板对象,以更改热图的整体调色板。我们使用了colorRampPalette()
函数,从一小部分特定颜色列表中生成顺序调色板。这个函数会插值生成完整的调色板。我们将RColorBrewer
包中的brewer.pal()
函数作为参数传给colorRampPalette()
,该函数根据提供的选项返回粉色-黄色-绿色(PiYG)调色板中的八种颜色。生成的热图颜色如下所示:
最后,在第 5 步中,我们在可视化步骤中对数据进行了数值转换。我们使用heatmap()
的scale
选项来规范化图表中的数据。请注意,设置为column时按列进行规范化,而设置为row
时按行进行规范化。默认的基础包scale()
函数用于此操作。图表中的数字重新缩放是颜色变化的原因,而不是直接从调色板中选择的结果。图表如下所示:
另见
heatmap()
函数随后有了其他包,这些包遵循类似的语法,但扩展了其功能。尝试使用gplots
包中的heatmap.2()
和heatmap.3()
。以下直方图展示了heatmap.2()
的绘图效果。它与heatmap()
非常相似,但默认添加了颜色键和直方图:
表示关系数据为网络
网络,或图形,是表示实体之间关系的强大数据表示方式,对于许多生物学研究至关重要。网络分析可以揭示生态学研究中的社区结构,揭示蛋白质-蛋白质相互作用中的潜在药物靶点,并帮助我们理解复杂代谢反应中的相互作用。表示网络的基础数据结构可能是复杂的。幸运的是,R 有一些非常强大的包,特别是igraph
和ggraph
,我们可以使用它们来访问网络信息并进行绘图。在本教程中,我们将探索一些生成适当大小网络图的方式。
准备工作
在这个示例中,我们需要ggraph
和igraph
包及其依赖项,包括magrittr
、readr
和dplyr
。我们还需要来自本书代码库中datasets/ch7
文件夹的bio-DM-LC.edges
文件。这个文件包含来自 WormNet 的一些基因功能关联数据。网络包含大约 1,100 条边和 650 个节点。你可以在这里阅读更多关于数据的信息:networkrepository.com/bio-DM-LC.php
。
如何操作...
将关系数据表示为网络可以通过以下步骤完成:
- 加载包并准备数据框:
library(ggraph)
library(magrittr)
df <- readr::read_delim(file.path(getwd(), "datasets", "ch7", "bio-DM-LC.edges"),
delim = " ",
col_names = c("nodeA", "nodeB", "weight")) %>%
dplyr::mutate(edge_type = c("A","B"), length(weight), replace = TRUE))
- 创建一个
igraph
对象并在基础图形中使用它:
graph <- igraph::graph_from_data_frame(df)
ggraph(graph, layout = "kk") +
geom_edge_link() +
geom_node_point() +
theme_void()
- 根据边的值或类型给边着色:
ggraph(graph, layout = "fr") +
geom_edge_link(aes(colour = edge_type)) +
geom_node_point() +
theme_void()
- 添加节点属性并相应地为节点着色:
igraph::V(graph)$category <- sample(c("Nucleus", "Mitochondrion", "Cytoplasm"), length(igraph::V(graph)), replace = TRUE )
igraph::V(graph)$degree <- igraph::degree(graph)
ggraph(graph, layout = "fr") +
geom_edge_link(aes(colour = edge_type)) +
geom_node_point(aes(size = degree, colour = category)) +
theme_void()
它是如何工作的...
在第 1 步中,我们加载了所需的库,并从边列表文件中准备了数据框。输入文件基本上是一个边列表。每一行描述了一个连接,第一列是目标节点之一,第二列是另一个目标节点。第三列包含一个值,表示这两个节点之间交互的强度,我们可以将其视为边的权重。字段由空格分隔,文件没有列名标题。因此,我们适当地设置了delim
和col_names
参数的值。然后,我们将数据框传递给dplyr::mutate()
函数,添加一个名为edge_type
的额外列。在该列中,我们使用sample()
函数随机为每一行分配"A"
或"B"
。最终的对象保存在df
变量中。
在第 2 步中,我们使用igraph::graph_from_data_frame()
函数从df
创建了igraph
对象,并将其保存在graph
变量中。我们将igraph
图形对象作为第一个对象传递给ggraph()
函数,后者与ggplot()
类似。它接收graph
对象和一个布局参数。(在这里,我们使用"kk"
,但具体使用哪个布局将高度依赖于数据本身。)然后,我们使用+
运算符添加层。首先,我们添加了geom_edge_link()
层,它绘制了边,然后是geom_node_point()
,它绘制了节点,最后,我们添加了theme_void()
,它去除了背景的灰色面板和白色线条,为网络留下了一个清晰的背景。初始图形如下所示:
在第 3 步中,我们添加了一些基于数据的自定义设置。我们首先将布局算法更改为"fr"
,这种算法提供了更美观且更分散的视图。然后,我们在geom_edge_link()
中使用aes()
函数将边的颜色映射到edge_type
值。剩下的层像之前一样添加。通过这样做,我们得到了以下图形:
在 第 4 步 中,我们为节点设置了一些属性。这比看起来要简单。igraph
中的 V()
函数返回一个简单的节点 ID 向量(在 igraph 行话中,节点被称为顶点),因此我们计算该向量的长度,并用它生成一个包含 Nucleus
、Mitochondrion
和 Cytoplasm
值的随机向量。然后我们可以通过使用带有 $
索引的 V()
函数将这些新值分配给节点。我们可以创建任何我们喜欢的属性,因此 igraph::V(graph)$category
创建了一个名为 category 的新属性。我们可以使用标准的 *<-*
赋值运算符将新值直接分配给该属性。接下来的步骤类似;igraph::V(graph)$degree
创建了一个名为 degree 的属性。在我们的案例中,我们将 igraph::degree()
函数的结果分配给它。Degree 是图论中的术语,表示与一个节点相连的边的数量。现在我们拥有了新的属性,可以根据这些属性对图形进行上色。ggraph()
构建过程与之前相同,但在 geom_node_point()
层中,我们使用 aes()
将颜色映射到我们新的 category 属性,并将大小映射到我们新的 degree 属性。最终的图形如下所示:
还有更多内容...
蜂窝图是绘制网络的好方法,尤其是在你有三种节点类型或某种方向性结构时。你可以使用我们已经拥有的相同类型的数据创建一个蜂窝图,如下所示:
ggraph(graph, 'hive', axis = 'category') +
geom_edge_hive(aes(colour = edge_type, alpha = ..index..)) +
geom_axis_hive(aes(colour = category)) +
theme_void()
在这里,我们设置布局类型为 hive
,并指定用于制作轴的属性为 category
。在 geom_edge_hive()
中的边描述与之前差不多,alpha
参数为 ..index..
,它根据边的绘制顺序为边缘添加透明度元素。geom
节点被替换为 geom_axis_hive()
,我们在其中使用 aes()
将颜色映射到 category 属性。生成的图形如下所示:
使用 plotly 创建交互式网页图形
通过图形用户界面交互式地探索数据集是一种有益且启发性的方式,能够分析和审视数据。动态地在图形中添加和删除数据、放大或缩小特定部分,或让图形随着时间变化(依赖于底层数据)可以让我们看到一些在静态图形中看不见的趋势和特征。在这个实例中,我们将使用 plotly
库在 R 中创建交互式图形,从一个基本图形开始,逐步构建一个更复杂的图形。
准备工作
在这个实例中,我们将使用内置的 Orange 数据集,它描述了橙树树干围长随时间的变化。该数据集是 datasets
包的一部分(通常已经预装),因此你应该能够立即访问它。
如何操作...
使用 plotly
创建交互式网页图形可以通过以下步骤完成:
- 加载库并创建一个基本图形:
library(plotly)
plot_ly(data = Orange, x = ~age, y = ~circumference)
- 映射标记的颜色和大小以及悬停文本到数据:
plot_ly(data = Orange, x = ~age, y = ~ circumference,
color = ~Tree, size = ~age,
text = ~paste("Tree ID: ", Tree, "<br>Age: ", age, "Circ: ", circumference)
)
- 添加第二个系列/迹线:
trace_1 <- rnorm(35, mean = 120, sd = 10)
new_data <- data.frame(Orange, trace_1)
plot_ly(data = new_data, x = ~age, y = ~ circumference,
color = ~Tree, size = ~age,
text = ~paste("Tree ID: ", Tree, "<br>Age: ", age, "Circ: ", circumference)
) %>% add_trace(y = ~trace_1, mode = 'lines' ) %>%
add_trace(y = ~circumference, mode = 'markers' )
- 添加一个下拉菜单,以便您可以选择图表类型:
plot_ly(data = Orange, x = ~age, y = ~ circumference,
color = ~Tree, size = ~age,
text = ~paste("Tree ID: ", Tree, "<br>Age: ", age, "Circ: ", circumference)
) %>%
add_trace(y = ~circumference, mode = 'marker' ) %>%
layout(
title = "Plot with switchable trace",
updatemenus = list(
list(
type = "dropdown",
y = 0.8,
buttons = list(
list(method = "restyle",
args = list("mode", "markers"),
label = "Marker"
),
list(method = "restyle",
args = list("mode", "lines"),
label = "Lines"
)
)
)
)
)
它是如何工作的...
在第 1 步加载库后,我们使用核心的plot_ly()
函数创建了可能最简单的图。我们向plot_ly()
传递了数据框的名称,以及作为公式的x和y轴的列,因此使用了~
符号。在这一点上,我们还没有明确指定迹线类型,即plotly
称其为系列或数据轨道,所以它猜测并创建了一个散点图,如下图所示:
注意图表顶部的菜单图标以及当鼠标悬停在数据点上时显示的悬停文本。这些图形在交互式 R 会话中可以很好地进行交互,但更适合于 HTML-based 文档,例如编译的 R markdown。
在第 2 步中,我们将图中的特征映射到数据的各个方面。我们将大小和颜色设置为与树木 ID 和年龄列相对应的公式,再次使用~
语法。我们还为每个数据点设置了悬停文本,并使用paste()
函数编译了确切的格式。请注意,悬停文本是基于 HTML 的,我们可以使用诸如<br>
这样的标签来按照选择的方式格式化悬停效果。我们的图现在改进为如下所示:
在第 3 步中,我们的主要变化是明确指定了迹线数据。为了突出显示迹线可以携带超出原始数据框之外的数据,我们使用rnorm()
创建了一个名为trace_1
的新数据向量,其中包含 35 个均值为 120,标准差为 1 的随机数。我们与在第 2 步创建图表的方式相同,但这次我们使用了magrittr
管道将图形对象发送到add_trace()
函数。在这里,我们将新的trace_1
对象作为我们的y
值传递,并将mode
设置为"lines"
以获得折线图。再次,我们将其传递到另一个add_trace()
函数(我们可以通过多个迹线系列构建一个图),但这次使用原始数据框列周长,并将mode
设置为"markers"
。生成的图表看起来像这样:
在第 4 步中,我们将菜单引入了我们的图表。我们实现的菜单将允许我们在不同的追踪类型之间切换——从线条到标记,再到线条。此步骤以相同的基本plot_ly()
调用开始,然后这次只传入一个追踪。接下来,我们传入了layout
函数,该函数接受一个图表标题作为title
参数,并为updatemenus
参数提供一个复杂的选项列表。你必须将一个包含三个成员的列表传递给updatemenus
——type
、y
和buttons
。type
设置菜单类型——在这种情况下,我们想要一个下拉菜单;y
设置菜单在y轴上的位置,值范围为 0 到 1,而buttons
需要另一个列表,其中每个子列表描述一个菜单选项。每个子列表都有members
方法,以及args
和labels
。setting
方法用于"restyle"
,意味着图表将在选择菜单时更新。args
成员需要另一个列表,指定当前菜单选项的"mode"
和"type"
。最后,label
指定此菜单选项在菜单中显示的文本。当我们在下拉菜单中选择Marker时,图表如下所示,渲染在左侧:
使用 plotly 构建三维图表
我们在生物信息学中生成的大多数图表都是静态的,并且局限于二维平面,但现代网络技术使我们能够与三维对象进行互动并动态渲染。plotly
库提供了渲染不同类型 3D 图表的工具,在这个教程中,我们将展示如何构建一个 3D 表面图和一个带有x、y、z轴的散点图。
准备就绪
在这个教程中,我们将再次使用plotly
库,并且使用内置的longley
经济数据集。
如何做...
使用plotly
构建三维图表可以通过以下步骤完成:
- 设置数据对象:
library(plotly)
d <- data.frame(
x <- seq(1,10, by = 0.5),
y <- seq(1,10, by = 0.5)
)
z <- matrix(rnorm(length(d$x) * length(d$y) ), nrow = length(d$x), ncol = length(d$y))
- 创建基本的表面图:
plot_ly(d, x = ~x , y = ~y, z = ~z) %>%
add_surface()
- 添加一个响应式等高线图层:
plot_ly(d, x = ~x , y = ~y, z = ~z) %>%
add_surface(
contours = list(
z = list(
show=TRUE,
usecolormap=TRUE,
highlightcolor="#ff0000",
project=list(z=TRUE)
)
)
)
它是如何工作的...
在第 1 步中,我们首先构建一个适合图表类型的数据集。对于表面三维图,我们需要一个包含x和y坐标的数据框,我们直接使用data.frame()
函数创建,并将其保存在一个名为d
的变量中。数据框d
包含了 x 和 y 列之间从 0 到 10 的 20 个值(每列 10 个值)。你可以将这个数据框视为指定三维场的宽度和长度,而不是实际的数据值。数据值是通过与数据框指定的维度相对应的矩阵对象获得的。我们使用matrix()
函数创建了一个适当维度的矩阵,矩阵中的值是通过rnorm()
函数生成的随机正态值。一旦我们有了这两个结构,就可以像之前为二维图表做的那样,使用它们在plot_ly()
函数中指定d
、x
和y
,并添加新的z轴来获取我们的矩阵。结果被传递给add_surface()
函数,将数据渲染为三维表面。该图表的效果类似如下:
请注意,点击并拖动图表区域,你可以调整相机视角。
在第 2 步中,我们通过在三维表面下方(或上方)添加一个反应式等高线图来进一步完善图表。我们在add_surface()
函数中使用了 contours 选项。它接受一个选项列表。第一个z
指定如何处理等高线。它还接受一个包含多个成员的列表,用于控制等高线图的外观,最重要的选项是highlightcolor
,它指定了在等高线图上绘制的颜色,用于显示鼠标悬停的当前三维图层。渲染后的图像类似于以下效果:
在第 3 步中,我们改变了方向,绘制了一个三维散点图。这更加直接。我们将 Longley 数据传递给plot_ly()
函数,并指定了维度和数据列进行映射。我们还添加了一个标记选项,将颜色映射到 GNP 列。最后,我们将基本图对象传递给add_markers()
函数,得到最终图表,效果如下所示:
构建多组学数据的圆形基因组图
对多条数据序列的整体基因组分析通常以圆形方式呈现,采用同心圆,每个圆显示不同种类的数据,每个数据在圆中的表示方式不同。这些图表被称为 Circos 图,具有极大的表现力,可以在紧凑的形式中显示大量的密集信息。在本教程中,我们将学习如何在 R 中构建这样的图表,使用常见的基因组数据文件。
准备工作
为了制作 Circos 图,我们将使用circlize
包以及本书仓库中datasets/ch7/
文件夹下的四个以arabidopsis
为前缀的文件。
如何操作...
构建多组学数据的圆形基因组图可以通过以下步骤完成:
- 加载库并读取染色体长度信息:
library(circlize)
df <- readr::read_tsv(file.path( getwd(), "datasets", "ch7", "arabidopsis.gff"), col_names = FALSE) %>%
dplyr::select(X1, X4, X5)
- 初始化图表和染色体轨迹,然后添加链接:
circos.genomicInitialize(df)
circos.link("Chr4", c(9000000, 1200000),
"Chr5", c(12000000,15000000),
col = "red")
- 从文件加载链接信息并绘制:
circos.clear()
source_links <- read.delim(file.path(getwd(), "datasets", "ch7", "arabidopsis_out_links.bed"), header = FALSE)
target_links <- read.delim(file.path(getwd(), "datasets", "ch7", "arabidopsis_in_links.bed"), header = FALSE)
circos.genomicInitialize(df)
circos.genomicLink(source_links, target_links, col = "blue")
- 加载基因位置并添加密度轨迹:
circos.clear()
gene_positions <- read.delim(file.path(getwd(), "datasets", "ch7", "arabidopsis_genes.bed"), header = FALSE)
circos.genomicInitialize(df)
circos.genomicDensity(gene_positions, window.size = 1e6, col = "#0000FF80", track.height = 0.1)
- 加载热图数据。然后,添加一个热图轨迹:
circos.clear()
heatmap_data <- read.delim(file.path(getwd(), "datasets", "ch7", "arabidopsis_quant_data.bed"), header = FALSE)
col_fun = colorRamp2(c(10, 12, 15), c("green", "black", "red"))
circos.genomicInitialize(df)
circos.genomicHeatmap(heatmap_data, col = col_fun, side = "inside", border = "white")
- 合并这些轨迹:
circos.clear()
circos.genomicInitialize(df)
circos.genomicHeatmap(heatmap_data, col = col_fun, side = "inside", border = "white")
circos.genomicDensity(gene_positions, window.size = 1e6, col = "#0000FF80", track.height = 0.1)
circos.genomicLink(source_links, target_links, col = "blue")
它是如何工作的...
在步骤 1中,我们开始通过读取arabidopsis.gff
文件来构建图表,该文件描述了我们希望在图表中使用的染色体长度。我们只需要名称、开始和结束列,因此我们将数据传递给dplyr::select()
函数,保留适当的列,即X1
、X4
和X5
。由于.gff
文件没有列标题,read_tsv()
函数会给出列名 X1 ... Xn。我们将结果保存在df
对象中。
在步骤 2中,我们开始构建图表。我们使用circos.genomicInitialize()
函数和df
来创建图表的骨架和坐标系统,然后手动添加了一个单独的链接。circos.link()
函数允许我们使用染色体名称和 c(start, end)格式创建单个来源和目标,从而将链接着色为请求的颜色。当前图表如下所示:
在步骤 3的开始,我们使用了circos.clear()
来完全重置图表。重置仅在本教程中需要,因为我们想逐步构建内容;在你自己的编码中,你可能可以忽略它。下一步是加载一个基因组区域文件,代表一些链接的来源,以及一个独立的基因组区域文件,代表一些链接的目标。这两个文件应该是 BED 格式,并且源文件中的第 N 行必须与目标文件中的第 N 行对应。然后,我们使用circos.genomicInitialize()
重新初始化图表,并使用circos.genomicLink()
在一个命令中添加多个链接,传入源链接数据和目标数据的对象,然后将它们全部着色为蓝色。图表如下所示:
在步骤 4中,在清除图表后,我们从arabidopsis_genes.bed
文件中读取另一个基因位置的 BED 文件。我们希望将这些信息添加为一个密度轨迹,计算用户指定长度的窗口中功能的数量,并将其绘制为密度曲线。为此,我们使用circos.genomicDensity()
函数,传入gene_positions
数据框,选择一个 1 百万的窗口大小,一种颜色(注意颜色使用八位十六进制格式,允许我们为颜色添加透明度),以及track.height
,它指定了图表中此轨迹所占的比例。轨迹如下所示:
在第 5 步中,我们添加了一个更复杂的轨迹——一个热图,可以表示许多列的定量数据。这里的文件格式是扩展的 BED 格式,包含染色体名称、起始位置和结束位置,以及后续列中的数据。在我们的示例文件arabidopsis_quant_data.bed
中有三个额外的数据列。我们通过read.delim()
将 BED 文件加载到heatmap_data
中。接下来,我们创建了一个颜色函数,并将其保存为col_fun
,用于绘制热图。colorRamp2()
函数接受数据的最小值、中值和最大值的向量作为其参数,然后使用第二个参数中指定的颜色。因此,使用10
、12
和15
以及绿色
、红色
和黑色
,我们分别将 10 绘制为绿色,12 绘制为黑色,15 绘制为红色。介于这些值之间的颜色会由colorRamp2()
自动计算。为了绘制热图,我们使用了circos.genomicHeatmap()
函数,并将col_fun
传递给col
参数。side
参数指定是否在圆内或圆外绘制,而border
参数指定热图元素之间线条的颜色。绘图效果如下:
最后,在第 6 步中,我们将所有内容结合在一起。通过清除并重新初始化绘图,我们通过按从外到内的顺序调用相关函数来指定轨迹的顺序:
最终的图形,如前面所见,先是circos.genomicHeatmap()
,然后是circos.genomicDensity()
,最后是circos.genomicLink()
,以得到圆形基因组图。
第八章:与数据库和远程数据源的工作
大规模模式生物有机体测序项目,如人类基因组计划(HGP)或 1001 植物基因组测序项目,已经使大量基因组数据公开可用。同样,个别实验室的开放数据共享也使基因组和转录组的原始测序数据得到了广泛的共享。通过编程处理这些数据意味着可能需要解析或本地存储一些非常庞大或复杂的文件。因此,许多努力已投入使这些资源通过 API 和其他可查询接口(如 BioMart)尽可能易于访问。在本章中,我们将介绍一些食谱,帮助我们在不下载整个基因组文件的情况下搜索注释,并能够在数据库中找到相关信息。我们还将了解如何在代码中提取实验的原始读取数据,并借此机会研究如何对下载的数据进行质量控制。
本章将覆盖以下食谱:
-
从 BioMart 检索基因和基因组注释
-
检索和处理 SNP
-
获取基因本体信息
-
从 SRA/ENA 查找实验和读取数据
-
对高通量测序读取进行质量控制和过滤
-
完成使用外部程序的读到参考比对
-
可视化读到参考比对的质量控制图
技术要求
你所需的示例数据可以在本书的 GitHub 仓库中找到,地址为 github.com/PacktPublishing/R-Bioinformatics-Cookbook
. 如果你想按原样使用代码示例,你需要确保这些数据位于你的工作目录的子目录中。
以下是你需要的 R 包。一般来说,你可以使用install.packages("package_name")
来安装这些包。在Bioconductor
下列出的包需要使用专用的安装器进行安装。具体安装方法将在本节中描述。如果需要进一步操作,安装过程将在包使用的食谱中进行描述:
-
Bioconductor
-
biomaRt
-
ramwas
-
ShortRead
-
SRAdb
-
Bioconductor 非常庞大,并且拥有自己的安装管理器。你可以使用以下代码来安装管理器:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
然后,你可以使用以下代码安装这些包:
BiocManager::install("package_name")
更多信息请参见 www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接通过名称使用其中的函数。这在交互式会话中非常方便,但在加载多个包时可能会导致混淆。为了明确我在某一时刻使用的是哪个包和函数,我会偶尔使用packageName::functionName()
的惯例。
有时,在配方中间,我会中断代码,这样您就可以看到一些中间输出或者重要对象的结构。每当这种情况发生时,您将看到每行以##
(双重井号)符号开头的代码块。考虑以下命令:
letters[1:5]
这将给我们以下输出:
## a b c d e
请注意,输出行前缀为##
。
从BioMart
检索基因和基因组注释
一旦准备好了某个基因组序列的草稿,就会进行大量的生物信息学工作,以找到基因和其他功能特征或重要的基因座。这些注释很多,执行和验证都很困难,通常需要大量的专业知识和时间,并且不希望重复。因此,基因组项目联合体通常会通过某种方式共享他们的注释,通常是通过一些公共数据库。BioMart
是一种常见的数据结构和 API,通过它可以获取注释数据。在这个配方中,我们将看看如何以编程方式访问这些数据库,以获取我们感兴趣的基因的注释信息。
准备工作
对于这个配方,我们需要名为biomaRt
的Bioconductor
包以及一个可用的互联网连接。我们还需要知道要连接的BioMart
服务器 —— 全球约有 40 个这样的服务器,提供各种信息。最常访问的是Ensembl
数据库,这些是这些包的默认设置。您可以在这里查看所有BioMart
的列表:www.biomart.org/notice.html
。我们将开发的代码将适用于这些BioMart
中的任何一个,只需稍微修改表名和 URL。
如何实现...
可以通过以下步骤从BioMart
检索基因和基因组注释:
- 列出所选示例数据库
gramene
中的mart
列表:
library(biomaRt)
listMarts(host = "ensembl.gramene.org")
- 创建到所选
mart
的连接:
gramene_connection <- useMart(biomart = "ENSEMBL_MART_PLANT", host = "ensembl.gramene.org")
- 列出该
mart
中的数据集:
data_sets <- listDatasets(gramene_connection)
head(data_sets)
data_set_connection <- useMart("atrichopoda_eg_gene", biomart = "ENSEMBL_MART_PLANT", host = "ensembl.gramene.org")
- 列出我们实际可以检索的数据类型:
attributes <- listAttributes(data_set_connection)
head(attributes)
- 获取所有染色体名称的向量:
chrom_names <- getBM(attributes = c("chromosome_name"), mart = data_set_connection )
head(chrom_names)
- 创建一些用于查询数据的过滤器:
filters <- listFilters(data_set_connection)
head(filters)
- 获取第一个染色体上的基因 ID:
first_chr <- chrom_names$chromosome_name[1]
genes <- getBM(attributes = c("ensembl_gene_id", "description"), filters = c("chromosome_name"), values = c(first_chr), mart = data_set_connection )head(genes)
head(genes)
工作原理...
该配方围绕对数据库进行一系列不同的查找操作,每次获取一些更多信息来处理。
在第一步中,我们使用listMarts()
函数获取指定主机 URL 上所有可用的BioMart
列表。在需要连接到不同服务器时,请根据需要更改 URL。我们得到一个可用mart
的数据框,并使用该信息。
在第二步中,我们使用useMart()
函数创建一个名为gramene_connection
的连接对象,传入服务器 URL 和第一步中的具体BioMart
。
在步骤 3中,我们将gramene_connection
传递给listDatasets()
函数,以检索该biomart
中的数据集。选择其中一个数据集(atrichopda_eg_gene
)后,我们可以运行useMart()
函数来创建一个到该biomart
中数据集的连接,并将对象命名为data_set_connection
。
在步骤 4中,我们几乎完成了确定可以使用哪些数据集的工作。在这里,我们使用在listAttributes()
函数中创建的data_set_connection
,获取我们可以从该数据集中检索的各种信息类型的列表。
在步骤 5中,我们最终通过主要函数getBM()
获取一些实际信息。我们将attributes
参数设置为我们希望返回的数据的名称;在这里,我们获取chromosome_name
的所有值,并将它们保存到一个向量chrom_names
中。
在步骤 6中,我们设置过滤器——即接收哪些值的限制。我们首先询问data_set_connection
对象,使用listFilters()
函数查看我们可以使用哪些过滤器。从返回的filters
对象中可以看到,我们可以在chromosome_name
上进行过滤,所以我们将使用这个。
在步骤 7中,我们设置一个完整的查询。在这里,我们打算获取第一个染色体上的所有基因。请注意,我们已经在步骤 5中获得了一个染色体列表,因此我们将使用chrom_names
对象中的第一个元素作为过滤器,并将其保存在first_chr
中。为了执行查询,我们使用getBM()
函数,并指定ensembl_gene_id
和description
属性。我们将filter
参数设置为我们希望过滤的数据类型,并将values
参数设置为我们希望保留的过滤器值。我们还将data_set_connection
对象作为要使用的 BioMart 传递。最终生成的genes
对象包含了第一个染色体上的ensembl_gene_id
和描述信息,如下所示:
## ensembl_gene_id description
## 1 AMTR_s00001p00009420 hypothetical protein
## 2 AMTR_s00001p00015790 hypothetical protein
## 3 AMTR_s00001p00016330 hypothetical protein
## 4 AMTR_s00001p00017690 hypothetical protein
## 5 AMTR_s00001p00018090 hypothetical protein
## 6 AMTR_s00001p00019800 hypothetical protein
获取和处理 SNP
SNP 和其他多态性是重要的基因组特征,我们通常希望在特定基因组区域内检索已知的 SNP。在这里,我们将介绍如何在两个不同的 BioMart 中执行此操作,这些 BioMart 存储了不同类型的 SNP 数据。在第一部分中,我们将再次使用 Gramene 来查看如何获取植物 SNP。在第二部分中,我们将了解如何在主要的 Ensembl 数据库中查找人类 SNP 的信息。
准备工作
如前所述,我们只需要biomaRt
包,它来自Bioconductor
,并且需要一个正常工作的互联网连接。
如何操作……
获取和处理 SNP 可以通过以下步骤完成:
- 从 Gramene 获取数据集、属性和过滤器列表:
library(biomaRt)
listMarts(host = "ensembl.gramene.org")
gramene_connection <- useMart(biomart = "ENSEMBL_MART_PLANT_SNP", host = "ensembl.gramene.org")
data_sets <- listDatasets(gramene_connection)
head(data_sets)
data_set_connection <- useMart("athaliana_eg_snp", biomart = "ENSEMBL_MART_PLANT_SNP", host = "ensembl.gramene.org")
listAttributes(data_set_connection)
listFilters(data_set_connection)
- 查询实际的 SNP 信息:
snps <- getBM(attributes = c("refsnp_id", "chr_name", "chrom_start", "chrom_end"), filters = c("chromosomal_region"), values = c("1:200:200000:1"), mart = data_set_connection )
head(snps)
它是如何工作的……
步骤 1将与之前的食谱中的步骤 1到6类似,我们将建立初始连接并让它列出我们可以在此 BioMart 中使用的数据集、属性和过滤器;这是相同的模式,每次使用 BioMart 时都会重复(直到我们能熟记它)。
在步骤 2中,我们利用收集到的信息提取目标区域的 SNP。同样,我们使用getBM()
函数并设置chromosomal_region
过滤器。这允许我们指定一个描述基因组中特定位点的值。value
参数采用Chromosome:Start:Stop:Strand
格式的字符串;具体而言,1:200:20000:1
,这将返回染色体 1 上从第 200 到 20000 个碱基的所有 SNP,且位于正链上(注意,正链 DNA 的标识符为1
,负链 DNA 的标识符为-1
)。
还有更多...
从 Ensembl 中查找人类 SNP 的步骤基本相同。唯一的区别是,由于 Ensembl 是默认服务器,我们可以在useMart()
函数中省略服务器信息。对于人类的类似查询会像这样:
data_set_connection <- useMart("hsapiens_snp", biomart = "ENSEMBL_MART_SNP")
human_snps <- getBM(attributes = c("refsnp_id", "allele", "minor_allele", "minor_allele_freq"), filters = c("chromosomal_region"), value = c("1:200:20000:1"), mart = data_set_connection)
另见
如果你拥有dbSNP refsnp ID
编号,可以通过rnsps
包和ncbi_snp_query()
函数直接查询这些 ID。只需将有效的refsnp
ID 向量传递给该函数。
获取基因本体论信息
基因本体论(GO)是一个非常有用的受限词汇,包含用于基因和基因产物的注释术语,描述了注释实体的生物过程、分子功能或细胞成分。因此,这些术语在基因集富集分析和其他功能-组学方法中非常有用。在本节中,我们将看看如何准备一个基因 ID 列表,并为它们获取 GO ID 和描述信息。
准备工作
由于我们仍在使用biomaRt
包,所以只需要该包以及一个有效的互联网连接。
如何操作...
获取基因本体论信息的步骤如下:
- 连接到 Ensembl BioMart 并找到适当的属性和过滤器:
library(biomaRt)
ensembl_connection <- useMart(biomart = "ENSEMBL_MART_ENSEMBL")
listDatasets(ensembl_connection)
data_set_connection <- useMart("hsapiens_gene_ensembl", biomart = "ENSEMBL_MART_ENSEMBL")
att <- listAttributes(data_set_connection)
fil <- listFilters(data_set_connection)
- 获取基因列表,并使用它们的 ID 获取 GO 注释:
genes <- getBM(attributes = c("ensembl_gene_id"), filters = c("chromosomal_region"), value = c("1:200:2000000:1"), mart = data_set_connection)
go_ids <- getBM(attributes = c("go_id", "goslim_goa_description"), filters = c("ensembl_gene_id"), values = genes$ensembl_gene_id, mart = data_set_connection )
工作原理...
如同前两个步骤,步骤 1 包括找到适合的 biomart、数据集、属性和过滤器的值。
在步骤 2中,我们使用getBM()
函数获取特定染色体区域中的ensembl_gene_id
属性,将结果保存在genes
对象中。然后我们再次使用该函数,使用ensembl_gene_id
作为过滤器,go_id
和goslim_goa_description
来获取仅选定基因的 GO 注释。
查找 SRA/ENA 中的实验和读取数据
短序列数据归档(SRA)和欧洲核苷酸库(ENA)是记录原始高通量 DNA 序列数据的数据库。每个数据库都是相同高通量序列数据集的镜像版本,这些数据集是来自世界各地各个生物学领域的科学家提交的。通过这些数据库免费获取高通量序列数据意味着我们可以设想并执行对现有数据集的新分析。通过对数据库进行搜索,我们可以识别出我们可能想要处理的序列数据。在本配方中,我们将研究如何使用SRAdb
包查询 SRA/ENA 上的数据集,并以编程方式检索选定数据集的数据。
准备工作
这个配方的两个关键元素是来自Bioconductor
的SRAdb
包和一个有效的互联网连接。
如何操作...
查找来自 SRA/ENA 的实验和数据读取可以通过以下步骤完成:
- 下载 SQL 数据库并建立连接:
library(SRAdb)
sqlfile <- file.path(system.file('extdata', package='SRAdb'), 'SRAmetadb_demo.sqlite')
sra_con <- dbConnect(SQLite(),sqlfile)
- 获取研究信息:
dbGetQuery(sra_con, "select study_accession, study_description from study where study_description like '%coli%' ")
- 获取关于该研究包含内容的信息:
sraConvert( c('ERP000350'), sra_con = sra_con )
- 获取可用文件的列表:
listSRAfile( c("ERR019652","ERR019653"), sra_con, fileType = 'sra' )
- 下载序列文件:
getSRAfile( c("ERR019652","ERR019653"), sra_con, fileType = 'fastq', destDir = file.path(getwd(), "datasets", "ch8") )
它是如何工作的...
加载库后,第一步是设置一个本地的 SQL 文件,名为sqlfile
。该文件包含了关于 SRA 上研究的所有信息。在我们的示例中,我们使用的是包内的一个小版本(因此,我们通过system.file()
函数提取它);真实的文件大小超过 50GB,因此我们暂时不会使用它,但可以通过以下替换代码获取:sqlfile <- getSRAdbfile()
。一旦我们拥有一个sqlfile
对象,就可以使用dbConnect()
函数创建与数据库的连接。我们将连接保存在名为sra_con
的对象中,以便重用。
接下来,我们使用dbGetQuery()
函数对sqlfile
数据库执行查询。该函数的第一个参数是数据库文件,第二个参数是一个完整的 SQL 格式的查询。写出的查询非常直观;我们希望返回包含coli
一词的描述时的study_accession
和study_description
。更复杂的查询是可能的——如果你准备好用 SQL 编写它们。关于这方面的教程超出了本配方的范围,但有许多书籍专门介绍这个主题;你可以尝试阅读 Upom Malik、Matt Goldwasser 和 Benjamin Johnston 合著的《SQL for Data Analytics》,由 Packt Publishing 出版:www.packtpub.com/big-data-and-business-intelligence/sql-data-analysis
。该查询返回一个类似于以下内容的 dataframe 对象:
## study_accession study_description
## ERP000350 Transcriptome sequencing of E.coli K12 in LB media in early exponential phase and transition to stationary phase
步骤 3 使用我们提取的访问号,利用sraConvert()
函数获取与该研究相关的所有提交、样本、实验和运行信息。这将返回如下表格,我们可以看到该研究的运行 ID,展示了包含序列的实际文件:
## study submission sample experiment run
## 1 ERP000350 ERA014184 ERS016116 ERX007970 ERR019652
## 2 ERP000350 ERA014184 ERS016115 ERX007969 ERR019653
在步骤 4中,我们使用 listSRAfile()
函数获取运行中特定序列的实际 FTP 地址。这将提供 SRA 格式文件的地址,这是一个压缩且方便的格式,如果你希望了解的话:
run study sample experiment ftp
## 1 ERR019652 ERP000350 ERS016116 ERX007970 ftp://ftp-trace.ncbi.nlm.nih.gov/sra/sra-instant/reads/ByRun/sra/ERR/ERR019/ERR019652/ERR019652.sra
## 2 ERR019653 ERP000350 ERS016115 ERX007969 ftp://ftp-trace.ncbi.nlm.nih.gov/sra/sra-instant/reads/ByRun/sra/ERR/ERR019/ERR019653/ERR019653.sra
但是在步骤 5中,我们使用 getSRAfile()
函数,并将 fileType
参数设置为 fastq
,以便以标准的 fastq
格式获取数据。文件将下载到 destDir
参数指定的文件夹中。
还有更多...
不要忘记定期刷新本地的 SQL 数据库,并使用以下代码获取完整版本:sqlfile <- getSRAdbfile()
。
对高通量序列读取进行质量控制和过滤
当我们有一组新的序列读取需要处理时,无论是来自新实验还是数据库,我们都需要执行质量控制步骤,去除任何序列接头、去除质量差的读取,或者根据需要修剪质量差的序列。在这个配方中,我们将介绍如何在 R 中使用 Bioconductor ShortRead
包来实现这一点。
准备工作
你需要安装 ShortRead
包,并且需要运行本章中 查找 SRA/ENA 中的实验和读取 这一配方的代码。在该配方的最后一步,会创建两个文件,我们将使用其中一个。运行该代码后,文件应该位于本书的 datasets/ch8/ERRR019652.fastq.gz
目录中。
如何执行...
对高通量序列读取进行质量控制和过滤可以通过以下步骤完成:
- 加载库并连接到文件:
library(ShortRead)
fastq_file <- readFastq(file.path(getwd(), "datasets", "ch8", "ERR019652.fastq.gz") )
- 过滤掉质量低于 20 的任何核苷酸的读取:
qualities <- rowSums(as(quality(fastq_file), "matrix") <= 20)
fastq_file <- fastq_file[qualities == 0]
- 修剪读取的右侧:
cut_off_txt <- rawToChar(as.raw(40))
trimmed <- trimTails(fastq_file, k =2, a= cut_off_txt)
- 设置自定义过滤器以移除 N 和同源重复序列:
custom_filter_1 <- nFilter(threshold=0)
custom_filter_2 <- polynFilter(threshold = 10, nuc = c("A", "T", "C", "G"))
custom_filter <- compose(custom_filter_1, custom_filter_2)
passing_rows <- custom_filter(trimmed)
trimmed <- trimmed[passing_rows]
- 写出保留的读取:
writeFastq(trimmed, file = file.path(getwd(), "datasets", "ch8", "ERR019652.trimmed.fastq.gzip"), compress = TRUE)
它是如何工作的...
第一步将读取加载到 ShortReadQ
对象中,该对象表示 DNA 读取及其相关的质量评分;这个特殊的对象使我们能够在一次操作中处理序列和质量。
第二步让我们找到所有质量分数都高于 20 的读取。这里的代码有点特殊,所以请花时间理解它。首先,我们在 fastq_file
上使用 quality()
函数提取质量值,然后将其传递给 as()
函数,要求返回一个矩阵。接着,我们对该矩阵使用 rowSums()
计算每行的总和,最终通过比较得到一个逻辑向量 qualities
,它指示哪些 rowSums()
的值小于 20。在下一行中,我们使用 qualities
向量来对 fastq_file
进行子集筛选,去除低质量的读取。
在步骤 3中,我们修剪读取的右侧(以纠正读取质量低于阈值的地方)。这里的主要功能是trimTails()
,它接受两个参数:k
,开始修剪所需的失败字母数,以及a
,开始修剪的字母。这当然意味着我们所认为的 Phred 数值质量分数(如步骤 2中,我们仅使用了 20)需要根据质量分数的文本编码转换为其 ASCII 等价物。这就是第一行发生的事情;数字 40 通过as.raw()
转换为原始字节,然后通过rawToChar()
转换为字符。生成的文本可以通过将其存储在cut_off_txt
变量中来使用。
步骤 4应用了一些自定义过滤器。第一行,custom_filter_1
,为包含名为N的碱基的序列创建过滤器,阈值参数允许序列包含零个N。第二行,custom_filter_2
,为长度等于或大于阈值的同质聚合物读取创建过滤器。nuc
参数指定要考虑的核苷酸。一旦指定了过滤器,我们必须使用compose()
函数将它们合并成一个单一的过滤器,该函数返回一个我们称之为custom_filter()
的过滤器函数,然后对修剪后的对象进行调用。它返回一个SRFFilterResult
对象,可以用来对读取进行子集化。
最后,在步骤 5中,我们使用writeFastQ()
函数将保留的读取写入文件。
使用外部程序完成读取到参考的比对
高通量读取的比对是本书中许多方法的一个重要前提,包括 RNAseq 和 SNP/INDEL 调用。在第一章,执行定量 RNAseq,以及第二章,使用 HTS 数据查找遗传变异中我们对其进行了深入讨论,但我们没有涉及如何实际执行比对。我们通常不会在 R 中执行此操作;进行这些比对所需的程序是强大的,并且作为独立进程从命令行运行。但 R 可以控制这些外部进程,因此我们将探讨如何运行外部进程,以便你可以从 R 包装脚本中控制它们,最终使你能够开发端到端的分析管道。
准备中...
本教程仅使用基础的 R 语言,因此你无需安装任何额外的包。你需要准备参考基因组 FASTA 文件datasets/ch8/ecoli_genome.fa
,以及我们在寻找 SRA/ENA 实验和读取数据教程中创建的datasets/ch8/ERR019653.fastq,gz
文件。本教程还需要系统中安装 BWA 和samtools
的可执行文件。相关软件的网页可以在samtools.sourceforge.net/
和bio-bwa.sourceforge.net/
找到。如果你已经安装了conda
,可以通过conda install -c bioconda bwa
和conda install -c bioconda samtools
来安装它们。
如何操作……
使用以下步骤完成读取到参考的比对,借助外部程序:
- 设置文件和可执行文件路径:
bwa <- "/Users/macleand/miniconda2/bin/bwa"
samtools <- "/Users/macleand/miniconda2/bin/samtools"
reference <- file.path(getwd(), "datasets", "ch8", "ecoli_genome.fa")
- 准备
index
命令并执行:
command <- paste(bwa, "index", reference)
system(command, wait = TRUE)
- 准备
alignment
命令并执行:
reads <- file.path(getwd(), "datasets", "ch8", "ERR019653.fastq.gz")
output <- file.path(getwd(), "datasets", "ch8", "aln.bam")
command <- paste(bwa, "mem", reference, reads, "|", samtools, "view -S -b >", output)
system(command, wait = TRUE)
它是如何工作的……
第一步非常简单:我们仅仅创建了几个变量来保存程序和文件所在目录的路径。bwa
和samtools
分别保存了这些程序在系统中的路径。请注意,你的系统上的路径可能与此不同。在 Linux 和 macOS 系统中,你可以通过which
命令在终端中查找路径;在 Windows 机器上,你可以使用where
命令或等效命令。
在步骤 2中,我们概述了运行系统命令的基本模式。首先,通过paste()
函数,我们将命令作为字符串创建,并保存在一个名为command
的变量中。在这里,我们正在准备一个命令行,在执行 BWA 进行读取比对之前创建所需的索引。然后,我们将该命令作为第一个参数传递给system()
函数,后者会实际执行该命令。命令作为一个全新的进程在后台启动,默认情况下,一旦进程开始,控制权会立即返回给 R 脚本。如果你打算在后台进程输出后立即在 R 中继续工作,你需要将system()
函数中的wait
参数设置为TRUE
,这样 R 进程将在后台进程完成后才继续。
在步骤 3中,我们扩展了模式,创建了读取和输出变量,并组合了一个更长的命令行,展示了如何构建一个有效的命令行。然后我们重复执行system
命令。此过程将生成一个最终的 BAM 文件,位于datasets/ch8/aln.bam
。
可视化读取到参考比对的质量控制
一旦完成读取的比对,通常建议检查比对的质量,确保读取的模式和期望的插入距离等没有异常。这在草拟参考基因组中尤其有用,因为高通量读取的异常比对可能会揭示参考基因组的拼接错误或其他结构重排。在本教程中,我们将使用一个名为ramwas
的包,它有一些易于访问的图形,可以帮助我们评估比对的质量。
正在准备中...
对于这个配方,我们需要准备好的 bam_list.txt
和 sample_list.txt
信息文件,位于本书的 datasets/ch8
目录下。我们还需要来自同一位置的小文件 ERR019652.small.bam
和 ERR019653.small.bam
。
如何执行...
可通过以下步骤可视化读取到参考基因组的比对质量控制:
- 设置运行的参数:
library(ramwas)
param <- ramwasParameters( dirbam = ".", filebamlist = "bam_list.txt",
dirproject = file.path(getwd(), "datasets", "ch8"),
filebam2sample = "sample_list.txt")
- 执行质量控制:
ramwas1scanBams(param)
qc <- readRDS(file.path(getwd(), "datasets", "ch8", "rds_qc", "ERR019652.small.qc.rds")$qc
- 查看图表:
plot(qc$hist.score1)
plot(qc$bf.hist.score1)
plot(qc$hist.length.matched)
plot(qc$bf.hist.length.matched)
它是如何工作的...
步骤 1 使用 ramwasParameters()
函数设置一个包含参数的对象。我们只需提供信息文件(bam_list.txt
和 sample_list.txt
),分别指定要使用的 BAM 文件及其包含的样本位置。dirproject
参数指定结果应该写入系统的路径。请注意,这些结果会被写入磁盘,而不是直接返回内存。
步骤 2 使用参数通过 ramwas1scanBams()
函数运行质量控制(QC)。结果被写入磁盘,因此我们使用基本的 R readRDS()
函数加载返回的 RDS 文件。qc
对象包含许多成员,代表了不同的比对质量控制方面。
步骤 3 使用通用的 plot
函数来创建一些 qc
对象中的质量控制统计图。
第九章:有用的统计和机器学习方法
在生物信息学中,对不同大小和组成的数据集进行统计分析是常见的任务。R 无疑是一个功能强大的统计语言,拥有丰富的选项来处理各种任务。在本章中,我们将重点关注一些有用但不常讨论的方法,尽管这些方法本身并不构成完整的分析,但它们可以成为你经常进行的分析的有力补充。我们将查看模拟数据集的食谱,以及用于类别预测和降维的机器学习方法。
本章将涵盖以下食谱:
-
校正 p 值以考虑多重假设
-
生成一个模拟数据集来表示背景
-
在数据中学习分组并使用 kNN 进行分类
-
使用随机森林预测类别
-
使用 SVM 预测类别
-
在没有先验信息的情况下对数据进行分组学习
-
使用随机森林识别数据中最重要的变量
-
使用 PCA 识别数据中最重要的变量
技术要求
你需要的示例数据可以从本书的 GitHub 仓库获取,网址是github.com/PacktPublishing/R-Bioinformatics-Cookbook
。如果你想按原样使用代码示例,那么你需要确保这些数据位于你工作目录的子目录中。
这里是你需要的 R 包。通常,你可以使用install.packages("package_name")
来安装这些包。列在Bioconductor
下的包需要使用专门的安装器安装。如果需要进一步的操作,安装过程将在使用这些包的食谱中进行描述:
-
Bioconductor
Biobase
-
caret
-
class
-
dplyr
-
e1071
-
factoextra
-
fakeR
-
magrittR
-
randomForest
-
RColorBrewer
Bioconductor
非常庞大,并且有自己的安装管理器。你可以使用以下代码安装该管理器:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
然后,你可以使用以下代码安装这些包:
BiocManager::install("package_name")
更多信息可以在www.bioconductor.org/install/
找到。
通常,在 R 中,用户会加载一个库并通过名称直接使用函数。这在交互式会话中非常方便,但当加载了多个包时,可能会引起混淆。为了明确我在使用哪个包和函数,我会偶尔使用packageName::functionName()
这种约定。
有时,在执行食谱的过程中,我会暂停代码,以便你能看到一些中间输出或对象的结构,这对理解非常重要。每当发生这种情况时,你会看到一个代码块,每行以##
(双井号)符号开头。请考虑以下命令:
letters[1:5]
这将给我们以下输出:
## a b c d e
请注意,输出行的前缀是##
。
校正 p 值以考虑多重假设
在生物信息学,特别是在基因组学项目中,我们常常在分析中执行数千次统计检验。但这可能会导致我们结果中的显著误差。考虑一个基因表达实验,每个处理的测量数很少(通常只有三次),但基因数量有成千上万。一个执行统计检验的用户,在p <= 0.05的情况下,会错误地拒绝零假设 5%的时间。对进行多个假设检验的校正可以帮助我们减少此类分析中的错误率。我们将查看一种简单的校正方法。
准备开始
所有需要的函数都是 R 的基础函数,我们将通过代码创建自己的数据。
如何实现...
校正 p 值以考虑多个假设的步骤如下:
- 运行 10,000 次 t 检验:
set.seed(1)
random_number_t_test <- function(n){
x <- rnorm(10)
y <- rnorm(10)
return(t.test(x,y)$p.value)
}
p_values <- sapply(1:10000, random_number_t_test )
- 评估 p-值数量,
<= 0.05
:
sum(p_values <= 0.05)
- 调整 p 值:
adj_p_values <- p.adjust(p_values, method = "holm")
- 重新评估
<= 0.05
的 p 值数量:
sum(adj_p_values <= 0.05)
它是如何工作的...
步骤 1中的第一行代码简单地固定了随机数生成器,以便我们能够在不同计算机之间获得一致的结果;除了为了对比本书中的结果,你不需要这部分代码。接下来是创建一个自定义函数,生成两组(x和y)10 个随机数,然后执行 t 检验并返回 p 值。由于这些只是来自相同分布的随机数,因此没有实际差异。最后一行使用 sapply()
函数运行我们自定义的函数并创建一个包含 10,000 个 p 值的向量。
在步骤 2中,我们仅仅统计 p 值小于 0.05 的数量。我们得到如下结果:
## [1] 506
这表示我们有 506 个错误判定为显著的结果。
在步骤 3中,我们使用p.adjust()
函数应用一种校正方法。argument
方法可以是几种可用方法之一。实践中,最好尝试holm
或BH
(Benjamini Hochberg),因为这些方法提供了准确的假阳性率。一种广泛使用但效果不太好的方法是Bonferroni
;大多数情况下应避免使用它。
在步骤 4中,我们重新评估小于 0.05 的 p 值数量。这次,结果正如我们预期的那样:
## [1] 0
生成一个代表背景的模拟数据集
构建模拟数据集以作为合理的对照,进行与预期背景分布的适当比较,并拥有一个合适的背景人群来抽取样本,这是许多研究的重要方面。在本配方中,我们将探讨从头开始或通过混合现有数据框来生成这些数据的各种方式。
准备开始
我们将使用fakeR
包和内置的iris
数据集。
如何实现...
生成一个代表背景的模拟数据集可以按照以下步骤进行:
- 创建一个具有与给定数据集相同特征的随机数据集:
library(fakeR)
fake_iris <- simulate_dataset(iris)
- 创建一个均值和标准差与给定向量相同的正态分布随机数向量:
sample_mean <- mean(iris$Sepal.Length)
sample_sd <- sd(iris$Sepal.Length)
random_sepal_lengths <- rnorm(iris$Sepal.Length, mean = sample_mean, sd = sample_sd)
hist( random_sepal_lengths)
- 在一个范围内创建一个均匀分布的随机整数向量:
low_num <- 1
high_num <- 6
hist(runif(1500, low_num, high_num))
- 创建一个二项分布成功次数的向量:
number_of_coins <- 1
p_heads <- 0.5
hist(rbinom(1500, number_of_coins, p_heads ))
number_of_coins <- 5
hist(rbinom(1500, number_of_coins, p_heads ))
- 从列表中随机选择元素,且每个元素的选择概率不同:
random_from_list <- sample(c("Low", "Medium", "High"), 100, replace = TRUE, prob = c(0.2, 0.6, 0.2))
table(random_from_list)
它是如何工作的……
步骤 1 使用了fakeR
包中的simulate_dataset()
函数,生成一个新的数据集,该数据集与源数据集(iris
)具有相同数量的值、相同的列名、相同数量的因子水平和水平名称,以及相同数量的行。值是随机化的,但数据框架是完全相同的。注意,使用str()
函数报告iris
和新的fake_iris
对象的结构是完全相同的:
str(iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
str(fake_iris)
## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.26 6.69 5.63 5.21 5.28 6.45 6.8 5.71 6.01 6.44 ...
## $ Sepal.Width : num 2.84 2.78 2.83 2.44 2.19 3.87 3.14 2.58 2.78 3.25 ...
## $ Petal.Length: num 4.03 4.84 2.64 2.83 5.37 3.63 5.54 4.74 4.63 4.29 ...
## $ Petal.Width : num 1.63 1.33 0.7 0.61 2.03 1.17 2.05 1.6 1.57 1.32 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 3 2 2 3 1 2 1 3 3 1 ...
在步骤 2中,我们的目标是生成一个随机数向量,它的均值和标准差与iris
数据集中Sepal.Length
列的均值和标准差相同。为此,我们首先使用mean()
和sd()
函数计算这些数值。然后,我们将这些值作为参数传递给rnorm()
函数的mean
和sd
参数。通过运行hist()
绘制生成的random_sepal_lengths
向量,我们可以确认其分布和参数。
在步骤 3中,我们希望创建一个数值(浮动点)向量,这些数值能够以相等的概率出现——这类似于反复掷骰子:每个选项的可能性是一样的。事实上,在这个步骤中,我们将范围的低值(low_num
)设置为 1,将高值(high_num
)设置为 6,以模拟这个过程。我们通过runif()
函数生成 1,500 个符合这些低值和高值的随机数,并通过再次使用hist()
绘制结果,我们可以看到每个区间内的频率相对均匀,从而确认这些数值的均匀性。
在步骤 4中,我们希望模拟一个掷硬币式的概率实验——所谓的二项成功概率分布。我们首先必须决定每次实验的试验次数——在掷硬币实验中,这指的是我们掷的硬币数量。在这里,我们将number_of_coins
变量设置为 1。我们还必须决定成功的概率。同样,模拟掷硬币实验时,我们将p_heads
变量设置为 0.5。为了进行模拟,我们将这些值传递给rbinom()
函数,请求进行 1,500 次独立的实验重复。hist()
函数显示,0 次成功(掷出反面)和 1 次成功(掷出正面)的频率在所有 1,500 次重复中大致相等。接下来,我们通过改变number_of_coins
变量的值,将试验次数改为 5,模拟每次重复实验时使用五枚硬币。我们再次使用rbinom()
函数并通过hist()
绘制结果,这时我们可以观察到,两个和三个成功(正面)是每次五枚硬币实验中最常见的结果。
最后,在步骤 5中,我们通过sample()
函数来选择向量中的项目。sample
的第一个参数是要从中采样的向量——这里是整数 1 到 10。第二个参数是要选择的项目数量——这里我们选择 10 个。请注意,默认情况下,sample()
会进行不放回抽样,因此每个项目不会被选择两次,尽管向量中的每个项目每次都有相等的被选中概率。sample()
的第二种用法将replacement
参数设置为TRUE
,意味着可以重复选择项目。此用法还设置了prob
参数——一个包含选择初始向量中每个值的概率的向量。运行此采样并将结果传递给table()
函数确认我们获得的选择符合预期的近似概率。
数据中的学习分组与 kNN 分类
k-最近邻(kNN)算法是一种监督学习算法,它会根据给定的数据点,尝试根据其与一组已知类别的训练样本的相似性对其进行分类。在这个食谱中,我们将学习如何使用数据集,将其划分为测试集和训练集,并根据训练集构建的模型预测测试集的类别。这种方法广泛应用于生物信息学,并且在聚类分析中非常有价值,尤其是当我们有一些已知的目标类别示例时。
准备工作
对于这个食谱,我们需要几个新的包:caret
、class
、dplyr
和magrittr
。作为数据集,我们将使用内置的iris
数据集。
如何进行...
使用以下步骤可以在数据中进行学习分组并进行 kNN 分类:
- 标准化数据并删除非数值列:
set.seed(123)
scaled_iris <- iris %>% mutate_if( is.numeric, .funs = scale)
labels <- scaled_iris$Species
scaled_iris$Species <- NULL
- 提取训练集和测试集:
train_rows <- sample(nrow(scaled_iris), 0.8 * nrow(scaled_iris), replace = FALSE)
train_set <- scaled_iris[train_rows, ]
test_set <- scaled_iris[-train_rows, ]
train_labels <- labels[train_rows]
test_set_labels <- labels[-train_rows]
- 构建模型并对测试集进行预测:
test_set_predictions <- knn(train = train_set, test = test_set, cl = train_labels, k = 10)
- 比较预测结果与实际类别:
caret::confusionMatrix(test_set_predictions, test_set_labels)
它是如何工作的...
在步骤 1中,我们首先使用set.seed()
确保随机数的可复现性,然后使用dplyr mutate_if()
函数对数据集的每一列进行标准化。mutate_if()
的第一个参数是要测试的条件;.funs
参数是如果条件为真则应用的函数。在这里,我们对iris
数据框的一列应用scale()
函数,如果该列是数值型,则返回我们称之为scaled_iris
的数据框。列间的标准化在 kNN 中非常重要,因为实际值的大小可能会有很大影响,所以我们需要确保它们在各列之间的尺度相似。接下来,我们从数据中复制Species
列,因为它包含类标签,并通过将列赋值为NULL
将其从数据框中删除——接下来的步骤中,数据框应仅包含数值数据。
在步骤 2中,我们决定哪些行应该包含在训练集和测试集中。我们使用sample()
函数从 1 到iris
行数的向量中选择;我们无替代地选择 80%的行号,因此train_rows
是一个整数向量,表示scaled_iris
中的行,我们将在训练集中使用它。在这一步的其余部分,我们使用子集和负子集来准备我们需要的scaled_iris
子集。
在步骤 3中,我们应用 kNN 算法,通过knn()
函数构建模型,并在一次操作中对测试集进行分类。train
参数表示我们为训练预留的数据部分,test
参数表示我们为测试预留的数据部分,cl
(类别)参数表示训练集的标签。k
参数是用于分类每个未知测试点的邻居数。该函数返回一个包含测试数据中每行预测类别的向量,我们将其保存在test_set_predictions
中。
在步骤 4中,我们使用caret
包的confusionMatrix()
函数评估预测结果。该函数接受预测类别和真实类别,并生成一组统计数据,包括以下表格,其中行表示Real
标签,列表示Predicted
标签。该模型错误地将一个versicolor
行预测为virginica
,但其余所有预测都正确:
## Reference
## Prediction setosa versicolor virginica
## setosa 8 0 0
## versicolor 0 9 1
## virginica 0 0 12
使用随机森林进行类别预测
随机森林是另一种监督学习算法,它使用决策树的集合来进行多次类别预测,最终预测最常出现的类别作为模型的最终预测。随机森林通常是有用的,因为它可以同时处理分类和数值数据,并且可以应用于分类和回归任务。在本章的使用随机森林识别数据中最重要的变量配方中,我们将再次使用它来预测数据中最重要的变量。在这个配方中,我们将使用随机森林来预测数据的类别。
做好准备
对于这个配方,我们需要caret
和randomForest
包以及内置的iris
数据集。
如何执行此操作...
使用随机森林进行类别预测可以通过以下步骤完成:
- 从
iris
数据集中准备训练集:
library(randomForest)
train_rows <- sample(nrow(iris), 0.8 * nrow(iris), replace = FALSE)
train_set <- iris[train_rows, ]
test_set <- iris[-train_rows, ]
- 在训练数据上构建模型:
model <- randomForest(Species ~ . , data = train_set, mtry = 2)
- 使用模型对测试数据进行预测:
test_set_predictions <- predict(model, test_set, type = "class")
caret::confusionMatrix(test_set_predictions, test_set$Species)
它是如何工作的...
步骤 1的整个过程是准备训练集和测试集。我们使用sample()
函数从 1 到iris
行数的向量中选择;我们无替代地选择 80%的行号,因此train_rows
是一个整数向量,表示iris
中的行,我们将在训练集中使用它。在这一步的其余部分,我们使用子集和负子集来准备我们需要的iris
子集。
在步骤 2 中,我们直接进行模型构建和预测。randomForest()
函数以其第一个参数为 R 公式,命名要预测的列(即Species
,响应变量),并且是训练数据的数据框列——在这里,我们使用所有列,表示为一个.
字符。data
参数是源数据框的名称,mtry
参数是一个可调参数,告诉算法使用多少分裂。这个最佳值通常是列数的平方根,但优化它可能会有帮助。生成的模型保存在一个名为model
的变量中,可以打印进行检查。
在步骤 3 中,我们使用predict()
函数与model
、test_set
数据和设置为class
的type
参数来预测测试集的类别。然后,我们使用caret::confusionMatrix()
来评估它们,得到以下结果:
## Reference
## Prediction setosa versicolor virginica
## setosa 13 0 0
## versicolor 0 8 0
## virginica 0 0 9
##
结果表明测试集完美分类。
还有更多内容
可以使用非常相似的方法进行回归(预测数值)预测。看看以下建立回归模型和进行评估的代码的相似性。在这里,我们基于其他列预测萼片长度。模型构建后,我们像以前一样运行预测;注意我们如何删除type
参数(因为实际上回归是默认的)。最后,我们通过计算均方误差(MSE)来评估,在其中我们平方预测值和萼片长度的实际值之间的差异,然后取均值:
model <- randomForest(Sepal.Length ~ . , data = train_set, mtry = 2)
test_set_predictions <- predict(model, test_set)
mean( (test_set$Sepal.Length - test_set_predictions )² )
使用 SVM 预测类别
支持向量机(SVM)算法是一种分类器,通过在数据的多个维度中找到类之间的最大距离——有效地是类之间最大的间隙——并使用该间隙的中点作为分类的边界。在这个配方中,我们将使用 SVM 来执行监督类别预测,并通过图形化地说明边界。
准备就绪
我们将继续使用内置的iris
数据集和e1071
包。
如何做...
使用 SVM 预测类别可以通过以下步骤完成:
- 构建训练集和测试集:
library(e1071)
train_rows <- sample(nrow(iris), 0.8 * nrow(iris), replace = FALSE)
train_set <- iris[train_rows, ]
test_set <- iris[-train_rows, ]
- 构建模型:
model <- svm(Species~., data=train_set, type="C-classification", kernel="radial", gamma=0.25)
- 绘制模型边界:
cols_to_hold <- c("Sepal.Length", "Sepal.Width")
held_constant <- lapply(cols_to_hold, function(x){mean(train_set[[x]])})
names(held_constant) <- cols_to_hold
plot(model, train_set, Petal.Width ~ Petal.Length, slice = held_constant)
- 在测试集上进行预测:
test_set_predictions <- predict(model, test_set, type = "class")
caret::confusionMatrix(test_set_predictions, test_set$Species)
工作原理...
在步骤 1 中,我们有可能熟悉的训练集和测试集生成步骤,我们在前面的配方中讨论过。简而言之,在这里,我们创建一个行号向量作为训练集,并使用子集和负子集来提取新的子数据集。
在第 2 步中,我们使用svm()
函数创建模型。第一个参数是一个 R 公式,指定了要用作类别的列(响应变量Species
),在~
后,我们使用.
字符表示所有其他列将作为构建模型的数据。我们将data
参数设置为train_set
数据框,并为kernel
和gamma
类型选择合适的值。type
可以是基于分类或回归的;kernel
是为不同数据和问题设计的多种函数之一;而gamma
是核函数的一个参数。你可能希望查看函数文档以获取详细信息,这些值也可以通过经验进行优化。
在第 3 步中,我们创建了一些对象,用来在二维空间中呈现四维边界。首先,我们选择不需要绘制的列(这些列将保持不变),然后使用lapply()
函数遍历这些列名的字符向量,应用一个函数计算命名列的均值。我们将列名添加到cols_to_hold
变量中的结果列表中。接着,我们使用通用的plot()
函数,传入模型、训练数据、绘图的两个维度(通过公式Petal.Width ~ Petal.Length
指定),以及一个slice
参数,从held_constant
列表中提取其他列的均值。
结果如下所示,显示了每个类别的边界颜色:
在第 4 步中,我们使用predict()
对测试集进行预测,并通过caret::confusionMatrix()
生成混淆矩阵以查看准确性。
在没有先验信息的情况下学习数据中的分组
在生物信息学中,通常需要在没有预先知道分组信息或数量的情况下将事物分类。这一过程通常被称为聚类,是一种无监督的机器学习方法。这种方法常见于基因组学实验,特别是在 RNA 测序及相关表达技术中。在本教程中,我们将从一个包含约 150 个样本的大型基因表达数据集开始,学习如何估算样本的分组数量,并应用一种基于主成分分析(PCA)降维的方法进行聚类,接着使用 k 均值聚类。
准备工作
对于本教程,我们需要factoextra
和biobase
库(后者来自Bioconductor
),以及本书仓库中的datasets/ch1
文件夹中的modencodefly_eset.RData
文件。
如何操作……
在没有先验信息的情况下,了解数据中的分组可以通过以下步骤完成:
- 加载数据并运行 PCA:
library(factoextra)
library(Biobase)
load(file.path(getwd(), "datasets", "ch1", "modencodefly_eset.RData") )
expr_pca <- prcomp(exprs(modencodefly.eset), scale=TRUE, center=TRUE ) fviz_screeplot(expr_pca)
- 提取主成分并估算最佳聚类:
main_components <- expr_pca$rotation[, 1:3]
fviz_nbclust(main_components, kmeans, method = "wss")
- 执行 k 均值聚类并进行可视化:
kmean_clus <- kmeans(main_components, 5, nstart=25, iter.max=1000)
fviz_cluster(kmean_clus, data = main_components,
palette = RColorBrewer::brewer.pal(5, "Set2"),
ggtheme = theme_minimal(),
main = "k-Means Sample Clustering"
)
它是如何工作的……
在步骤 1中,我们使用load()
函数将modencodefly.eset
对象导入内存;这是一个基因表达数据集。然后,我们使用Biobase
函数exprs()
提取表达测量值,并将其传递给prcomp()
函数,后者执行 PCA 并返回一个 PCA 对象,我们将其存储在expr_pca
变量中。
然后我们使用factoextra
函数fviz_screeplot()
绘制 PCA 图,并看到以下图示:
这显示了每个主成分所捕获的数据变异度。前三个成分捕获了超过 70%的变异度。因此,我们可以使用这三个成分,而不是整个 150 列的数据集,从而大大简化过程并加速分析。
在步骤 2中,我们通过对子集化expr_pca
对象的旋转槽提取主要成分,提取前三列——这些列对应于前三个成分。我们将它们保存在一个名为main_components
的变量中,并使用fviz_nbclust()
函数对main_components
和kmeans
函数进行处理,生成以下图示:
在这个函数中,数据被划分为越来越多的聚类,同时计算wss
(组内平方和),这是衡量聚类内变异性的一个指标。图示表明,组内平方和在约 5 个聚类之前大幅下降,之后没有明显改善,表明数据大约包含 5 个聚类。
在步骤 3中,我们使用kmeans()
函数执行 k-means 聚类,将main_components
作为第一个参数的数据,将5
作为聚类数的第二个参数。nstart
和iter.max
参数的值对于大多数算法运行来说是合理的选择。最后,我们将kmeans_clust
对象传递给fviz_cluster()
函数,并设置一些显示选项,生成以下图示:
还有更多
我们已经对这个数据集的样本或列进行了 k-means 聚类。如果你希望对基因或行做同样的操作,可以从步骤 2中的未旋转数据的x槽中提取主要成分:
main_components <- expr_pca$x[, 1:3]
如果你希望获取每个样本的实际聚类 ID,可以在kmeans_clus
对象的cluster
槽中找到:
kmean_clus$cluster[1:5]
## SRX007811 SRX008180 SRX008227 SRX008238 SRX008258
## 2 2 2 2 2
使用随机森林识别数据中最重要的变量
在本章中,我们已经看到过随机森林算法的应用,在使用随机森林预测类别的示例中,我们用它进行类别预测和回归。这里,我们将用它来做不同的事情——尝试找出数据集中哪些变量对训练模型的分类或回归准确度贡献最大。这只需要对已有代码做一个简单的修改,并使用一个或两个新函数。
准备开始
我们将需要randomForest
包和内置的iris
数据集。
如何操作...
使用随机森林识别数据中最重要的变量可以通过以下步骤完成:
- 准备训练数据和测试数据:
library(randomForest)
train_rows <- sample(nrow(iris), 0.8 * nrow(iris), replace = FALSE)
train_set <- iris[train_rows, ]
test_set <- iris[-train_rows, ]
- 训练模型并创建
importance
图:
model <- randomForest(Species ~ . , data = train_set, mtry = 2, importance = TRUE)
varImpPlot(model)
它是如何工作的...
在步骤 1中,我们进行类似于之前几个配方中的数据集拆分。使用sample()
函数,我们创建了一个包含原始iris
数据 80%行号的列表,然后,使用子集和负子集提取这些行。
在步骤 2中,我们使用randomForest()
函数训练模型。这里的第一个参数是一个公式;我们指定Species
是我们希望预测的值,基于所有其他变量,这些变量由.
描述。data
是我们的train_set
对象。此配方的关键是确保将importance
变量设置为TRUE
,这意味着模型将测试哪些变量在从模型构建中省略时,会导致准确度的最大下降。一旦模型构建并测试完毕,我们可以使用varImpPlot()
函数可视化每个变量的重要性。这样,我们得到以下图表:
我们可以看到,当省略Petal.Width
和Petal.Length
变量时,会导致模型准确度的最大下降,因此,根据这一标准,它们是最重要的。
使用 PCA 识别数据中最重要的变量
我们已经在无先验信息的数据分组学习配方中看到过 PCA 的应用,它是一种降维技术——一种在保留重要信息的同时减少数据集大小的方法。正如你所想,这意味着我们可以了解哪些原始变量对降维后的表示贡献最大,因此可以确定哪些是最重要的。我们将在本配方中看到这一点。
准备就绪
对于此配方,我们将使用factoextra
包和内置的iris
数据集。
如何操作...
使用 PCA 识别数据中最重要的变量可以通过以下步骤完成:
- 执行 PCA:
library(factoextra)
pca_result <- prcomp(iris[,-5], scale=TRUE, center=TRUE )
- 创建变量图:
fviz_pca_var(pca_result, col.var="cos2")
它是如何工作的...
这个简短的配方在步骤 1开始时简单构建了从prcomp()
函数获得的pca_result
。我们将iris
数据作为第一个参数(不包括第五个分类列),并对数据进行缩放和中心化——这可以避免不同量纲的测量差异占用不当的权重。
构建好pca_result
后,我们可以使用fviz_pca_var()
函数绘制变量,得到以下图表:
在其中,我们可以看到箭头表示每个变量。箭头从中心移动的角度表示变量的一个特征;箭头之间的距离越近,变量越相似——因此,Petal.Length
和Petal.Width
是高度相关的变量。箭头的颜色表示一个复杂的量(称为cos2
),它代表变量贡献的质量。变量的贡献越高,cos2
就越高。在这里,我们可以看到Sepal.Width
和Petal.Length
对主成分分析(PCA)的贡献较大。Petal.Width
由于过于相似,无法被考虑。这与通过随机森林识别数据中最重要的变量的结果不同,因为这两种技术提出的问题不同。
第十章:使用 Tidyverse 和 Bioconductor 编程
R 是一种适合交互式使用的优秀语言;然而,这也意味着许多用户没有体验过将其作为编程语言使用——也就是说,在自动化分析和节省重复工作所需时间和精力方面的应用。在本章中,我们将介绍一些实现这一目标的技术——特别是,我们将探讨如何将基础 R 对象集成到 tidyverse
工作流中,如何扩展 Bioconductor
类以满足我们自己的需求,以及如何使用文献编程和笔记本风格的编码来保持我们的工作记录具有表达性和可读性。
本章将介绍以下食谱:
-
使基础 R 对象整洁
-
使用嵌套数据框
-
为
mutate
编写函数 -
使用编程方式操作 Bioconductor 类
-
开发可重用的工作流和报告
-
使用 apply 系列函数
技术要求
你需要的示例数据可以从本书的 GitHub 仓库获取,链接为 github.com/PacktPublishing/R-Bioinformatics-Cookbook
. 如果你希望按原样使用代码示例,你需要确保这些数据位于你的工作目录的子目录中。
以下是你需要的 R 包。通常,你可以使用 install.packages("package_name")
来安装这些包。列在 Bioconductor
下的包需要使用专用的安装程序进行安装。如果需要做其他操作,安装过程将在使用这些包的食谱中描述:
-
Bioconductor
:-
Biobase
-
biobroom
-
SummarizedExperiment
-
-
broom
-
dplyr
-
ggplot2
-
knitr
-
magrittr
-
purrr
-
rmarkdown
-
tidyr
Bioconductor
非常庞大,并且有自己的安装管理器。你可以通过以下代码安装该管理器:
if (!requireNamespace("BiocManager"))
install.packages("BiocManager")
然后,你可以使用以下代码安装这些包:
BiocManager::install("package_name")
更多信息请参见 www.bioconductor.org/install/
。
通常,在 R 中,用户会加载一个库并直接使用函数名。这在交互式会话中很方便,但当加载了许多包时,可能会导致混淆。为了明确当前使用的是哪个包和函数,我偶尔会使用 packageName::functionName()
这种约定。
有时,在某个代码片段中,我会暂停代码执行,以便你能够看到一些中间输出或理解某个重要对象的结构。每当发生这种情况时,你会看到一个代码块,其中每行前面都以 ##
(双井号)符号开头。考虑以下命令:
letters[1:5]
这将给我们以下输出:
## a b c d e
请注意,输出行前缀为 ##
。
使基础 R 对象整洁
tidyverse
软件包集(包括dplyr
、tidyr
和magrittr
)通过应用整洁的工作方式,在数据处理和分析中对 R 语言产生了巨大影响。本质上,这意味着数据保持在一种特定的整洁格式中,每一行包含一个单独的观测值,每一列包含一个变量的所有观测值。这样的结构意味着分析步骤有可预测的输入和输出,可以构建成流畅且富有表现力的管道。然而,大多数基础 R 对象并不整洁,往往需要大量的编程工作来提取所需的部分,以便下游使用。在这个教程中,我们将介绍一些函数,用于自动将一些常见的基础 R 对象转换为整洁的数据框。
准备工作
我们将需要tidyr
、broom
和biobroom
包。我们将使用内置的mtcars
数据和来自本书仓库datasets/ch1
文件夹中的modencodefly_eset.RData
。
如何操作...
使基础 R 对象整洁可以通过以下步骤完成:
- 整理一个
lm
对象:
library(broom)
model <- lm(mpg ~ cyl + qsec, data = mtcars)
tidy(model)
augment(model)
glance(model)
- 整理一个
t_test
对象:
t_test_result <- t.test(x = rnorm(20), y = rnorm(20) )
tidy(t_test_result)
- 整理一个 ANOVA 对象:
anova_result <- aov(Petal.Length ~ Species, data = iris)
tidy(anova_result)
post_hoc <- TukeyHSD(anova_result)
tidy(post_hoc)
- 整理一个
Bioconductor
ExpressionSet
对象:
library(biobroom)
library(Biobase)
load(file.path(getwd(), "datasets", "ch1", "modencodefly_eset.RData") )
tidy(modencodefly.eset, addPheno = TRUE)
它是如何工作的...
步骤 1 展示了使用lm()
函数整理lm
对象的一些函数。第一步是创建对象。在这里,我们使用mtcars
数据执行一个多元回归模型。然后,我们对该模型使用tidy()
函数,返回模型组件的对象摘要,例如系数,以整洁的数据框形式。augment()
函数返回额外的逐观测数据,如果需要的话——同样是整洁格式。glance()
函数检查模型本身,并返回关于模型的摘要——自然地,以整洁格式显示。glance()
对于比较模型非常有用。
步骤 2 展示了相同的过程,适用于t.test
对象。首先,我们对两个随机数向量进行 t 检验。tidy()
函数会将所有细节以整洁的数据框形式返回。
在步骤 3中,我们对iris
数据运行方差分析(ANOVA)。我们使用aov()
函数查看Species
对Petal.Length
的影响。我们可以再次对结果使用tidy()
,但它会给出模型组件的摘要。实际上,我们更感兴趣的可能是来自后验检验的比较结果,该检验使用TukeyHSD()
函数进行,它也可以在tidy()
中使用。
在步骤 4中,我们使用biobroom
版本的tidy()
对ExpressionSet
对象进行处理。这将表达值的方阵转化为整洁的数据框,并包含样本和其他数据类型的列。额外的参数addPheno
是特定于此类对象的,并将ExpressionSet
元数据容器中的表型元数据插入其中。请注意,结果数据框超过 200 万行——生物学数据集可能很大,并且生成非常大的数据框。
使用嵌套数据框
数据框(dataframe)是整洁工作方式的核心,我们通常将其视为一个类似电子表格的矩形数据容器,每个单元格中仅包含一个值。实际上,数据框可以嵌套——即它们可以在特定的单个单元格中包含其他数据框。这是通过将数据框的向量列替换为列表列来实现的。每个单元格实际上是列表的一个成员,因此任何类型的对象都可以保存在外部数据框的概念性单元格中。在本教程中,我们将介绍创建嵌套数据框的方式以及与之合作的不同方法。
准备工作
我们将需要tidyr
、dplyr
、purrr
和magrittr
库。我们还将使用来自ggplot2
包的diamonds
数据,但我们不会使用任何函数。
工作原理...
使用嵌套数据框可以通过以下步骤完成:
- 创建一个嵌套数据框:
library(tidyr)
library(dplyr)
library(purrr)
library(magrittr)
library(ggplot2)
nested_mt <- nest(mtcars, -cyl)
- 添加一个新列表列,包含
lm()
的结果:
nested_mt_list_cols <- nested_mt %>% mutate(
model = map(data, ~ lm(mpg ~ wt, data = .x))
)
- 添加一个新列表列,包含
tidy()
的结果:
nested_mt_list_cols <- nested_mt_list_cols %>% mutate(
tidy_model = map(model, tidy)
)
- 解开整个数据框:
unnest(nested_mt_list_cols, tidy_model)
- 在单个步骤中运行管道:
models_df <- nest(mtcars, -cyl) %>%
mutate(
model = map(data, ~ lm(mpg ~ wt, data = .x)),
tidy_model = map(model, tidy)
) %>%
unnest(tidy_model)
工作原理...
在步骤 1中,我们使用nest()
函数将mtcars
数据框进行嵌套。-
选项告诉函数哪些列在嵌套时要排除;将cyl
列转换为因子,用于创建不同的子集。从概念上讲,这类似于dplyr::group_by()
函数。检查该对象会得到以下内容:
A tibble: 3 x 2
## cyl data
## <dbl> <list>
## 1 6 <tibble [7 × 10]>
## 2 4 <tibble [11 × 10]>
## 3 8 <tibble [14 × 10]>
嵌套数据框包含一个名为data
的新数据框列,以及被简化的cyl
列。
在步骤 2中,我们通过使用mutate()
在数据框中创建一个新列。在此过程中,我们使用purrr
包中的map()
函数,它遍历作为第一个参数提供的列表项(即我们的数据框列),并在作为第二个参数提供的代码中使用它们。在这里,我们对嵌套数据使用lm()
函数,一次处理一个元素——注意,.x
变量表示我们当前正在处理的内容——也就是列表中的当前项。运行后,列表看起来是这样的:
## cyl data model
## <dbl> <list> <list>
## 1 6 <tibble [7 × 10]> <lm>
## 2 4 <tibble [11 × 10]> <lm>
## 3 8 <tibble [14 × 10]> <lm>
新的model
列表列包含我们的lm
对象。
在确认添加新列表列的模式是使用mutate()
并在其中嵌入map()
后,我们可以同样整理lm
对象。这就是在步骤 3中发生的情况。结果给我们带来了以下嵌套数据框:
## cyl data model tidy_model
## <dbl> <list> <list> <list>
## 1 6 <tibble [7 × 10]> <lm> <tibble [2 × 5]>
## 2 4 <tibble [11 × 10]> <lm> <tibble [2 × 5]>
## 3 8 <tibble [14 × 10]> <lm> <tibble [2 × 5]>
步骤 4使用unnest()
函数将所有内容恢复为单个数据框;第二个参数tidy_model
是需要解包的列。
步骤 5将步骤 1到步骤 4的整个过程合并为一个管道,突出显示这些只是常规的tidyverse
函数,并且可以链式调用,无需保存中间步骤。
还有更多内容...
unnest()
函数只有在嵌套列表列成员兼容且可以根据正常规则合理对齐和回收时才有效。在许多情况下,这种情况并不成立,因此你需要手动操作输出。以下示例展示了我们如何做到这一点。工作流程基本上与之前的示例相同,唯一的变化是在早期步骤中,我们使用dplyr::group_by()
来创建nest()
的分组。在mutate()
中,我们传递自定义函数来分析数据,但其他步骤保持不变。最后一步是最大的变化,利用transmute()
来删除不需要的列,并创建一个新的列,它是map_dbl()
和自定义汇总函数的结果。map_dbl()
类似于map()
,但只返回双精度数值向量。其他的map_**
函数也存在。
为dplyr::mutate()
编写函数
dplyr
的mutate()
函数非常有用,可以根据现有列的计算结果向数据框中添加新列。它是一个矢量化函数,但通常被误解为按行工作,实际上它是按列工作的,也就是说,它对整个向量进行操作,并利用 R 内置的回收机制。这种行为往往会让那些想在复杂例子或自定义函数中使用mutate()
的人感到困惑,因此,在本教程中,我们将探讨mutate()
在某些情况下的实际行为,希望能够带来启示。
准备工作
为此,我们需要dplyr
包和内置的iris
数据。
如何做……
为dplyr::mutate()
编写函数可以通过以下步骤完成:
- 使用返回单个值的函数:
return_single_value <- function(x){
sum(x)
}
iris %>% mutate(
result = return_single_value(Petal.Length)
)
- 使用返回与给定值相同数量值的函数:
return_length_values <- function(x){
paste0("result_", 1:length(x))
}
iris %>% mutate(
result = return_length_values(Petal.Length)
)
- 使用返回既不是单个值也不是与给定值相同数量值的函数:
return_three_values <- function(x){
c("A", "b", "C")
}
iris %>% mutate(
result = return_three_values(Petal.Length)
)
- 强制函数的重复以适应向量的长度:
rep_until <- function(x){
rep(c("A", "b", "C"), length.out = length(x))
}
iris %>% mutate(
result = rep_until(Petal.Length)
)
它是如何工作的……
在步骤 1中,我们创建一个函数,给定一个向量,返回一个单一的值(长度为 1 的向量)。然后我们在mutate()
中使用它,添加一个名为result
的列,并得到如下结果:
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species result
## 1 5.1 3.5 1.4 0.2 setosa 563.7
## 2 4.9 3.0 1.4 0.2 setosa 563.7
## 3 4.7 3.2 1.3 0.2 setosa 563.7
## 4 4.6 3.1 1.5 0.2 setosa 563.7
请注意,函数在result
列中返回的单个值是反复出现的。对于length == 1
的向量,R 会回收结果并将其放置到每个位置。
在步骤 2中,我们走到对立面,创建一个函数,给定一个向量,返回一个相同长度的向量(具体来说,它返回一个将result_
与向量中的位置数字拼接在一起的向量)。当我们运行它时,我们得到如下结果:
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species result
## 1 5.1 3.5 1.4 0.2 setosa result_1
## 2 4.9 3.0 1.4 0.2 setosa result_2
## 3 4.7 3.2 1.3 0.2 setosa result_3
## 4 4.6 3.1 1.5 0.2 setosa result_4
由于它的长度与数据框中其余列完全相同,R 会接受它并将其作为新列应用。
在步骤 3中,我们创建一个返回三个元素的向量的函数。由于长度既不是 1,也不是数据框中其他列的长度,代码会失败。
在第 4 步中,我们将探讨如何重复一个不兼容长度的向量,以便在需要时使其适应。rep_until()
函数与length.out
参数会重复其输入,直到向量的长度为length.out
。通过这种方式,我们可以得到以下列,这就是我们在第 3 步中使用该函数时所期望看到的结果:
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species result
## 1 5.1 3.5 1.4 0.2 setosa A
## 2 4.9 3.0 1.4 0.2 setosa b
## 3 4.7 3.2 1.3 0.2 setosa C
## 4 4.6 3.1 1.5 0.2 setosa A
## 5 5.0 3.6 1.4 0.2 setosa b
使用 Bioconductor 类进行编程操作
Bioconductor
的广泛应用意味着有大量的类和方法可以完成几乎任何你想要的生物信息学工作流程。不过,有时候,额外的数据槽或一些工具上的调整会帮助简化我们的工作。在本教程中,我们将探讨如何扩展一个现有的类,以包含一些特定于我们数据的额外信息。我们将扩展SummarizedExperiment
类,以添加假设的条形码信息——一种元数据,表示一些核苷酸标签,这些标签可以标识包含在序列读取中的样本。
准备工作
对于本教程,我们只需要Bioconductor
的SummarizedExperiment
包。
如何操作...
使用以下步骤可以通过编程方式与Bioconductor
类进行交互:
- 创建一个继承自
SummarizedExperiment
的新类:
setClass("BarcodedSummarizedExperiment",
contains = "SummarizedExperiment",
slots = c(barcode_id = "character", barcode_sequence = "character")
)
- 创建构造函数:
BarcodedSummarizedExperiment <- function(assays, rowRanges, colData, barcode_id, barcode_sequence){
new("BarcodedSummarizedExperiment",
SummarizedExperiment(assays=assays, rowRanges=rowRanges, colData=colData),
barcode_id = barcode_id,
barcode_sequence = barcode_sequence
)
}
- 向类中添加必需的方法:
setGeneric("barcode_id", function(x) standardGeneric("barcode_id"))
setMethod("barcode_id", "BarcodedSummarizedExperiment", function(x) x@barcode_id )
- 构建新类的实例:
nrows <- 200
ncols <- 6
counts <- matrix(runif(nrows * ncols, 1, 1e4), nrows)
assays <- list(counts = counts)
rowRanges <- GRanges( rep(c("chr1", "chr2"), c(50, 150)),
IRanges(floor(runif(200, 1e5, 1e6)), width=100),
strand=sample(c("+", "-"), 200, TRUE),
feature_id=sprintf("ID%03d", 1:200)
)
colData <- DataFrame(
Treatment=rep(c("ChIP", "Input"), 3),
row.names=LETTERS[1:6]
)
my_new_barcoded_experiment <- BarcodedSummarizedExperiment(
assays = assays,
rowRanges = rowRanges,
colData = colData,
barcode_id = letters[1:6],
barcode_sequence = c("AT", "GC", "TA", "CG","GA", "TC")
)
- 调用新的方法:
barcode_id(my_new_barcoded_experiment)
它是如何工作的...
在第 1 步中,我们使用setClass()
函数创建一个新的 S4 类。这个函数的第一个参数是新类的名称。contains
参数指定我们希望继承的现有类(这样我们的新类就会包含这个类的所有功能以及我们创建的任何新功能)。slots
参数指定我们希望添加的新数据槽,并要求我们为其指定类型。在这里,我们添加了文本数据槽,用于新的barcode_id
和barcode_sequence
槽,因此我们为这两个槽都使用character
类型。
在第 2 步中,我们创建一个构造函数。该函数的名称必须与类名相同,并且我们在调用function()
时指定创建新对象所需的参数。在函数体内,我们使用new()
函数,其第一个参数是要实例化的类的名称。其余的部分用于填充实例数据;我们调用继承自SummarizedExperiment
的构造函数,以填充新对象的那一部分,然后手动填充新的条形码槽。每次运行BarcodedSummarizedExperiment
时,我们都会获得该类的一个新对象。
在步骤 3中,我们添加了一个新函数(严格来说,在 R 中,它被称为方法)。如果我们选择一个在 R 中尚不存在的Generic
函数名称,我们必须使用setGeneric()
注册该函数名,setGeneric()
的第一个参数是函数名,第二个参数是一个模板函数。Generic
函数设置完毕后,我们可以使用setMethod()
函数添加实际的函数。新函数的名称是第一个参数,它将附加到的类是第二个参数,而代码本身是第三个参数。请注意,我们这里只是在创建一个访问器(getter
)函数,它返回当前对象中barcode_id
槽的数据。
在步骤 4中,我们的准备工作已经完成,可以构建类的实例。在这一步的前六行中,我们仅仅创建了构建对象所需的数据。这部分数据进入一个普通的SummarizedExperiment
对象;你可以在文档中看到更多关于这里具体发生了什么的细节。然后,我们可以通过调用BarcodedSummarizedExperiment
函数,并传入我们创建的数据以及新barcode_id
和barcode_sequence
槽的特定数据,实际创建my_new_barcoded_experiment
。
现在,创建了对象后,在步骤 5中,我们可以像调用其他函数一样使用我们的方法,传入我们新创建的对象作为参数。
开发可重用的工作流程和报告
在生物信息学中,一个非常常见的任务是撰写结果,以便与同事交流,或者仅仅为了在实验记录中留有一份完整的记录(无论是电子版还是纸质版)。一个关键技能是尽可能让工作可重复,以便当我们需要回顾它时,或者有其他人对我们所做的工作感兴趣时,他们可以复制整个过程。一个日益流行的解决方案是使用文献编程技术和可执行笔记本,这些笔记本结合了可读的文本、分析代码和计算输出,打包成一个单一文档。在 R 中,rmarkdown
包使我们能够以这种方式将代码和文本结合在一起,并生成多种格式的输出文档。
在这个食谱中,我们将查看一个可以通过rmarkdown
编译的文档的大规模结构。RStudio 应用程序使得这个过程非常简便,所以我们将通过这个工具来查看编译过程。
准备工作
对于这个食谱,我们需要RStudio 应用程序和rmarkdown
包。这个食谱的示例代码可以在本书的datasets/ch10/
文件夹中的example_rmarkdown.rmd
文件中找到。
如何做到这一点...
开发可重用的工作流程和报告可以通过以下步骤完成:
- 在外部文件中,添加一个
YAML
头部:
---
title: "R Markdown Report"
author: "R Bioinformatics Cookbook"
date: "`r format(Sys.time(), '%d %B, %Y')`"
output:
html_document:
df_print: paged
bookdown::html_document2:
fig_caption: yes
keep_md: yes
toc: yes
---
- 然后,添加一些文本和代码进行解释:
We can include text and create code blocks, the code gets executed and the result passed in
```{r}
x <- iris$Sepal.Width
y <- iris$Sepal.Length
lm(y ~ x, data = iris)
```py
- 文本可以使用最小化标记格式进行格式化:
## We can format text using Markdown
We can create many text formats including *italics* and **bold**,
We can make lists
1\. First item
2\. Second item
- 在块内应用更多选项并传递变量:
The whole document acts as a single R session - so variables created in earlier blocks can still be used later.
Plots are presented within the document. Options for blocks can be set in the header
```{r, fig.width=12 }
plot(x, y)
```py
它是如何工作的…
这里的代码很特殊,因为它必须从外部文档中运行;在 R 控制台中无法运行。运行文档的编译步骤有几种方法。在 RStudio 中,一旦安装了rmarkdown
并且正在编辑一个.Rmd
扩展名的文档,您会看到一个knit
按钮。或者,您可以通过控制台使用rmarkdown::render()
函数编译文档,尽管我建议使用 RStudio IDE 来完成此操作。
在步骤 1中,我们创建了一个YAML
头部,描述了文档的渲染方式,包括输出格式、动态日期插入、作者和标题。这些内容将自动添加到您的文档中。
在步骤 2中,我们实际上创建了一些内容——第一行是纯文本,将作为段落文本传递到最终文档中,不做任何修改。块中的部分由py`
```py is code to be interpreted. Options for the block go inside the curly brackets—here,
` means this should be an R code block (some other languages are supported too). The code in this block is run in a new R session, its output captured; and inserted immediately after the code block.
In Step 3, we create some plaintext with the Markdown
tags. ##
gives us a line with a second-level heading, the **starred**
text gives us different formatting options, and we can also create lists. Valid Markdown
is interpreted and the reformatted text is passed into the eventual document.
In Step 4, we start with some more plaintext and follow with a new code block. The options for the code block are set in the curly brackets again—here, we set a width for figures in the plot. Note that the code in this block refers to variables created in an earlier block. Although the document creates a new R session without access to variables already in the usual console, the document itself is a single session so blocks can access earlier block's variables, allowing the code and text to be mixed up at whatever resolution the author requires. Finally, the resulting figure is inserted into the document just like code.
Making use of the apply family of functions
Programming in R can sometimes seem a bit tricky; the control flow and looping structures it has, are a bit more basic than in other languages. As many R functions are vectorized, the language actually has some features and functions; that mean we don't need to take the same low-level approach we may have learned in Python or other places. Instead, base R provides the apply
functions to do the job of common looping tasks. These functions all have a loop inside them, meaning we don't need to specify the loop manually. In this recipe, we'll look at using some apply
family functions with common data structures to loop over them and get a result. The common thread in all of the apply
functions is that we have an input data structure that we're going to iterate over and some code (often wrapped in a function definition) that we're going to apply to each item of the structure.
Getting ready
We will only need base R functions and data for this recipe, so you are good to go!
How to do it...
Making use of the apply
family of functions can be done using the following steps:
- Create a matrix and use
apply
to work on it:
m <- matrix(rep(1:10, 10, replace = TRUE), nrow = 10)
apply(m, 1, sum)
apply(m, 2, sum)
```py
2. Use `lapply` over the vector:
numbers <- 1:3
number_of_numbers <- function(x){
rnorm(x)
}
my_list <- lapply(numbers, number_of_numbers)
3. Use `lapply` and `sapply` over the list:
summary_function <- function(x){
mean(x)
}
lapply(my_list, summary_function)
sapply(my_list, summary_function)
4. Use `lapply` over a dataframe:
list_from_data_frame <- lapply(iris, mean, trim = 0.1, na.rm = TRUE )
unlist(list_from_data_frame)
# How it works...
*Step 1* begins with the creation of a 10 x 10 matrix, with rows holding the same number and columns running from 1 to 10\. Inspecting it makes it clear, as partly shown in the following output:
> m
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,] 1 1 1 1 1 1 1 1 1 1
[2,] 2 2 2 2 2 2 2 2 2 2
[3,] 3 3 3 3 3 3 3 3 3 3
We then use `apply()`: the first argument is the object to loop over, the second is the direction to loop in (or margin, 1 = rows, and 2 = columns), and the third is the code to apply. Here, it's the name of a built-in function, but it could be a custom one. Note it's the margin argument that affects the amount of data that is taken each time. Contrast the two `apply()` calls:
apply(m, 1, sum)
[1] 10 20 30 40 50 60 70 80 90 100
apply(m, 2, sum)
[1] 55 55 55 55 55 55 55 55 55 55
Clearly, `margin = 1` is taking each row at a time, whereas `margin = 2` is taking the columns. In any case, `apply()` returns a vector of results, meaning the results must be of the same type each time. It is not the same shape as the input data.
With *Step 2*, we move onto using `lapply()`, which can loop over many types of data structures, but always returns a list with one member for each iteration. Because it's a list, each member can be of a different type. We start by creating a simple vector containing the integers 1 to 3 and a custom function that just creates a vector of random numbers of a given length. Then, we use `lapply()` to apply that function over the vector; the first argument to `lapply()` is the thing to iterate over, and the second is the code to apply. Note that the current value of the vector we're looping over is passed automatically to the called function as the argument. Inspecting the resulting list, we see the following:
my_list
[[1]] [1] -0.3069078
[[2]] [1] 0.9207697 1.8198781
[[3]] [1] 0.3801964 -1.3022340 -0.8660626
We get a list of one random number, then two, then three, reflecting the change in the original vector.
In *Step 3*, we see the difference between `lapply()` and `sapply()` when running over the same object. Recall `lapply()` always returns a list but `sapply()` can return a vector (`s` can be thought of as standing for *simplify*). We create a simple summary function to ensure we only get a single value back and `sapply()` can be used. Inspecting the results, we see the following:
lapply(my_list, summary_function)
[[1]] [1] -0.3069078
[[2]] [1] 1.370324
[[3]] [1] -0.5960334
sapply(my_list, summary_function)
[1] -0.3069078 1.3703239 -0.5960334
Finally, in *Step 4*, we use `lapply()` over a dataframe, namely, the built-in `iris` data. By default, it applies to columns on a dataframe, applying the `mean()` function to each one in turn. Note the last two arguments (`trim` and `na.rm`) are not arguments for `lapply()`, though, it does look like it. In all of these functions, the arguments after the vector to iterate over and the code (in other words, argument positions 1 and 2) are all passed to the code being run—here, our `mean()` function. The column names of the dataframe are used as the member names for the list. You may recall that one of the columns in `iris` is categorical, so `mean()` doesn't make much sense. Inspect the result to see what `lapply()` has done in this case:
lapply(iris, mean, trim = 0.1, na.rm = TRUE )
$Sepal.Length [1] 5.808333
$Sepal.Width [1] 3.043333
$Petal.Length [1] 3.76
$Petal.Width [1] 1.184167
$Species [1] NA
It has returned `NA`. Also, it has generated a warning but not failed. This can be a source of bugs in later analyses.
With a simple list like this, we can also use `unlist()` to get a vector of the results:
unlist(list_from_data_frame)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.808333 3.043333 3.760000 1.184167 NA
如果存在名称,向量将被命名。
# 第十一章:为了代码重用构建对象和包
在最后一章中,我们将探讨如何将我们的代码从自己的机器中带出去,并与世界分享。我们最常分享的对象将是我们自己!因此,为了使我们的编程生活更加轻松和流畅,我们将学习如何创建对象和类来简化我们的工作流程,并如何将它们打包成可在其他项目中重用的包。我们将查看如何在 GitHub 等网站上共享代码的工具,以及如何检查代码是否按预期工作。
本章将涵盖以下食谱:
+ 创建简单的 S3 对象来简化代码
+ 利用 S3 类的通用对象函数
+ 使用 S4 系统创建结构化和正式的对象
+ 简单的代码打包方法,用于共享和重用
+ 使用 `devtools` 从 GitHub 托管代码
+ 构建单元测试套件,以确保函数按预期工作
+ 使用 Travis 进行持续集成,以确保代码经过测试并保持最新
# 技术要求
您需要的示例数据可以从本书的 GitHub 仓库获取:[`github.com/PacktPublishing/R-Bioinformatics-Cookbook`](https://github.com/PacktPublishing/R-Bioinformatics-Cookbook)[。](https://github.com/danmaclean/R_Bioinformatics_Cookbook) 如果您想按照书中的代码示例使用,您需要确保这些数据位于您的工作目录的子目录中。
这里是您需要的 R 包。通常,您可以通过 `install.packages("package_name")` 来安装这些包。列在 `Bioconductor` 下的包需要通过专用的安装程序安装。如果您需要做其他安装操作,会在包使用的食谱中描述:
+ `devtools`
+ `usethis`
对于一些后续的食谱,我们还需要安装 `git` 版本控制系统。请访问官方网页以获取适用于您系统的最新版本:[`git-scm.com/downloads`](https://git-scm.com/downloads)。您还可以在 GitHub 网站上找到一个有用的 GitHub 账户。如果您还没有 GitHub 账户,请访问[`github.com/`](https://github.com/)。
通常,在 R 中,用户会加载一个库并直接按名称使用函数。这在交互式会话中非常方便,但当加载了多个包时,可能会引起混乱。为了明确在某一时刻我正在使用哪个包和函数,我偶尔会使用 `packageName::functionName()` 这种惯例。
有时,在食谱的中途,我会中断代码,以便您可以看到一些中间输出或了解对象的结构,这对理解非常重要。每当发生这种情况时,您将看到一个代码块,其中每一行都以`##`(双哈希符号)开头。请考虑以下命令:
`letters[1:5]` 这将给我们以下输出:
`## a b c d e` 请注意,输出行前面有`##`。
# 创建简单的 S3 对象以简化代码
创建你自己的对象可以大大简化代码和工作流,使它们更容易被你复现和重用,并且将程序的内部逻辑抽象掉,减少你作为程序员的认知负担,让你能够更多地集中精力在项目的生物信息学和分析方面。R 实际上有多种方法来创建对象和类。在这个教程中,我们将重点看它最简单、最临时的方法——S3。这是一种相当非正式的创建对象和类的方式,但在很多情况下都足够使用。
# 准备工作
在这个教程中,我们只需要基础的 R 函数,因此无需安装任何额外的工具。
# 如何实现...
创建简单的 S3 对象以简化代码可以通过以下步骤完成:
1. 创建一个构造函数:
```py
SimpleGenome <- function( nchr=NA, lengths = NA){
genome <- list(
chromosome_count = nchr,
chromosome_lengths = lengths
)
class(genome) <- append(class(genome), "SimpleGenome")
return(genome)
}
- 调用构造函数来创建新对象:
ecoli <- SimpleGenome(nchr = 1, lengths = c(4600000) )
bakers_yeast <- SimpleGenome(nchr = 1, lengths=c(12100000))
它是如何工作的...
第 1 步 是所有工作的起点。这就是我们创建 S3 对象所需的全部内容。如你所见,这是非常简洁的代码。我们只是创建了一个生成并返回数据结构的函数。我们的类应该代表一个简化的基因组,并且我们希望它能保存一些关于基因组的基本信息。SimpleGenome()
函数是我们创建对象的构造函数。SimpleGenome
创建的基因组列表是构成最终对象主体的数据结构。这个列表的成员就是对象的槽,因此我们创建了名为 chromosome_count
和 chromosome_length
的成员,以表示基因组的一些特征。完成这一步后,我们进行一个重要的步骤——我们将类名(SimpleGenome
)附加到基因组列表的类属性上。正是这一点使 R 能够识别这个对象属于 SimpleGenome
类。现在我们可以返回创建的 S3 对象。
在第 2 步中,我们只是简单地使用构造函数来创建类的实例。检查得到的对象如下所示:
> ecoli
$chromosome_count
[1] 1
$chromosome_lengths
[1] 4600000
attr(,"class")
[1] "list" "SimpleGenome"
> bakers_yeast
$chromosome_count
[1] 1
$chromosome_lengths
[1] 12100000
attr(,"class")
[1] "list" "SimpleGenome"
我们可以看到对象的槽、对象之间的差异,以及包含新 SimpleGenome
对象的类。这就是我们创建 S3 对象的方式;它是一个简单但有效的做法。与仅仅创建一个普通数据结构(如列表)相比,优势并不是立刻显现出来,但当我们查看如何在下一个教程中创建方法时,原因将会更加明确。
利用 S3 类的通用对象函数
一旦我们有了一个 S3 对象,我们需要创建与之配套的函数。这些函数实际上是使得长期使用这些对象变得更容易的关键。正是在这些函数中,我们可以抽象化对象数据的处理,从而减少每次使用时所需的工作量。R 的对象系统基于通用函数。这些函数是具有相同基础名称但类特定名称扩展的分组函数。每个分组称为方法,R 会根据方法调用的对象的类来决定调用属于该方法的具体哪个函数。这意味着我们可以在A
类的对象上调用plot()
,并得到与在B
类对象上调用时完全不同的图形。在本食谱中,我们将了解它是如何工作的。
准备工作
对于本食谱,我们将使用基础的 R 函数,因此无需安装任何包,但我们将使用内置的iris
数据。
如何实现...
利用通用对象函数与 S3 类一起使用,可以通过以下步骤完成:
- 在
plot()
方法中创建一个通用函数:
plot.SimpleGenome <- function(x){
barplot(x$chromosome_lengths, main = "Chromosome Lengths")
}
- 创建一个对象并在
plot()
中使用它:
athal <- SimpleGenome(nchr = 5, lengths = c(34964571, 22037565, 25499034, 20862711, 31270811 ) )
plot(athal)
- 首先创建一个新方法:
genomeLength <- function(x){
UseMethod("genomeLength", x)
}
genomeLength.SimpleGenome <- function(x){
return(sum(x$chromosome_lengths))
}
genomeLength(athal)
- 修改现有对象的类:
some_data <- iris
summary(some_data)
class(some_data) <- c("my_new_class", class(some_data) )
class(some_data)
- 为新类创建通用函数:
summary.my_new_class <- function(x){
col_types <- sapply(x, class)
return(paste0("object contains ", length(col_types), " columns of classes:", paste (col_types, sep =",", collapse = "," )))
}
summary(some_data)
它是如何工作的...
在第 1 步中,我们创建了一个名为plot.SimpleGenome()
的通用函数。这里的特殊命名约定标志着该函数是属于专门针对SimpleGenome
类对象的通用绘图函数组。约定是method.class
。这就是使通用绘图方法工作的所有必要步骤。
在第 2 步中,我们实际上创建一个SimpleGenome
对象,就像在本章中的创建简单的 S3 对象以简化代码的食谱中所做的那样(你需要确保该食谱的第 1 步在当前会话中已经执行,否则这一步无法正常工作),然后调用plot()
方法。plot
方法查找适用于SimpleGenome
对象的通用函数并运行该对象,从而生成我们预期的条形图,如下图所示:
在第 3 步中,我们深入了一些。在这一步中,我们想使用一个不存在的方法名(你可以使用methods()
函数查看已经存在的方法),因此我们必须首先创建方法组。我们通过创建一个调用UseMethod()
函数的函数来实现这一点,方法的名称作为封闭函数名称和第一个参数。完成后,我们就可以为SimpleGenome
类创建通用函数,并通过简单地调用genomeLength()
在对象上使用它。由于我们的通用函数只是将chromosome_lengths
向量相加,我们得到如下结果:
> genomeLength(athal)
[1] 134634692
第 4 步展示了类查找系统的机制。我们首先复制iris
数据,然后在其上使用summary()
方法,得到数据框的标准结果:
> summary(some_data)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 versicolor:50
接下来,在第 4 步中,我们使用class()
函数为some_data
对象添加了一个新类。注意,我们将其作为向量的第一个元素添加。我们可以看到,data.frame
类仍然存在,但排在我们添加的类之后:
> class(some_data)
[1] "my_new_class" "data.frame"
然后,在第 5 步中,我们为my_new_class
创建了一个通用的summary()
函数,使其返回一个完全不同类型的摘要。我们可以看到,当我们调用它时:
> summary(some_data)
[1] "object contains 5 columns of classes:numeric,numeric,numeric,numeric,factor"
需要注意的一点是,尽管对象有多个类,但默认情况下会选择第一个与类匹配的通用函数。如果你想测试这一点,可以尝试交换class
属性的顺序。
使用 S4 系统创建结构化和正式的对象
S4 是 S3 的更正式的对应版本,特别是因为它具有正式的类定义,所以不能随意使用,但它的工作方式与 S3 非常相似,因此我们已经学到的内容通常适用。在这个教程中,我们将快速介绍如何使用 S4 系统创建一个类似于本章前两节中SimpleGenome
对象的类。了解 S4 会对你扩展Bioconductor
(因为它是用 S4 编写的)有所帮助。
准备工作
再次,我们只使用基础 R,因此无需安装任何东西。
如何做到这一点...
使用 S4 系统创建结构化和正式的对象可以按照以下步骤进行:
- 编写类定义:
S4genome <- setClass("S4genome", slots = list(chromosome_count = "numeric", chromosome_lengths = "numeric" ))
- 创建通用函数:
setGeneric( "chromosome_count",
function(x){ standardGeneric("chromosome_count") }
)
- 创建方法:
setMethod( "chromosome_count", "S4genome", function(x){ slot(x, "chromosome_count")} )
它是如何工作的
这里的大纲与前两个教程非常相似。在第 1 步中,我们使用setClass()
函数创建了类定义;第一个参数是类的名称,slots
参数是一个适当的插槽名称列表以及每个插槽的类型。S4 类需要定义类型。使用中的对象可以像 S3 那样实例化:
## > ecoli <- S4genome(chromosome_count = 1, chromosome_lengths = c(4600000) )
## > ecoli An object of class "S4genome"
## Slot "chromosome_count": [1] 1
## Slot "chromosome_lengths": [1] 4600000
在第 2 步中,我们使用setGeneric()
函数创建了一个通用函数chromosome_count
,传入函数的名称和一个调用standardGeneric()
函数的函数。这基本上是模板代码,因此请按照这个步骤进行,并在需要更多细节时查阅文档。
在第 3 步中,我们创建了方法。我们使用setMethod()
函数创建了一个chromosome_count
方法。第二个参数是这个方法将被调用的类,最后,我们传递了我们想要的代码。匿名函数仅仅调用了传递给它的对象上的slot()
函数。slot()
返回第二个参数中指定的槽的内容。
另请参见
如果你希望进一步了解 S4 来扩展 Bioconductor 类,请查看 Bioconductor 自己提供的教程:www.bioconductor.org/help/course-materials/2017/Zurich/S4-classes-and-methods.html
。
分享和重用代码的简单方法
不可避免地,总会有这么一个时刻,你希望能够重用一些函数或类,而不必每次都重新输入(或者——更糟——复制粘贴)它们。将功能的唯一可靠版本放在一个地方,可以轻松管理,及时发现并解决代码中的错误和变化。因此,在这个教程中,我们将探讨两种简单的封装代码以便重用的方法。我们将讨论包创建的基础知识,尽管我们将创建的包将非常简陋,并且在考虑发布之前,需要进行大量的完善——特别是文档和测试方面。然而,以这种方式创建的包,将在你开发代码时提供帮助。
准备工作
为此,我们需要devtools
和usethis
包,以及本书仓库中datasets/ch11
文件夹下的源代码文件my_source_file.R
。
如何操作...
封装代码以便共享和重用可以通过以下步骤完成:
- 加载现有的源代码文件:
source(file.path(getwd(), "datasets", "ch11", "my_source_file.R"))
my_sourced_function()
- 创建一个包骨架:
usethis::create_package("newpackage")
- 编写代码:
my_package_function <- function(x){
return( c("I come from a package!") )
}
- 将包代码加载到内存中:
devtools::load_all()
- 将包安装到当前的 R 安装环境中:
devtools::install()
library(newpackage)
它是如何工作的...
这段代码的第一步展示了一种非常有效但非常基础的加载外部代码的方法。我们使用 source()
函数将 R 代码文件加载到当前的命名空间中。这里的特定文件包含普通的 R 函数,除此之外没有其他内容。source()
函数仅仅是读取外部文件中的代码并将其执行,就像它是直接在当前控制台中输入的一样。由于文件中仅包含函数,因此你必须将这些函数加载到内存中以便立即使用。
步骤 2 更进一步,使用 usethis::create_package()
函数创建了一个简陋的包。该函数会创建一个你提供名称的新文件夹(在这个例子中是 newpackage
),并将包所需的所有基本文件和文件夹放入其中。现在你可以在包的 R/
子文件夹中填充 R 代码,这些代码将在你加载包时加载。尝试使用 步骤 3 中的函数;将此函数添加到 R/
文件夹中的 my_functions.R
文件中。R/
文件夹中的文件名并不太重要,你可以有很多文件——但一定要确保它们以 .R
结尾。
步骤 4 将会使用 devtools::load_all()
函数加载你的源代码包到内存中。这大致模拟了我们调用 library()
函数时发生的情况,但并没有真正安装包。通过使用 devtools::load_all()
,我们可以快速加载代码进行测试,而不必先安装它,这样如果我们需要更改代码,就不会有破损的版本被安装。我们没有提供任何参数,因此它会加载当前目录中的包(如果提供路径作为第一个参数,它会加载路径中找到的包)。
在 第 5 步 中,我们实际上将代码正确安装到 R 中。我们使用 devtools::install()
函数,它会构建包并将构建后的版本复制到 R 中的常规位置。现在,我们可以像加载任何其他包一样加载该构建版本 (newpackage
)。请注意,这意味着我们有两个包的副本—一个是我们安装的版本,另一个是我们正在开发的版本。在开发更多代码并将其添加到包中时,你需要根据需要重复步骤四和五。
使用 devtools 从 GitHub 托管代码
良好的代码开发实践意味着将代码保存在某种版本控制系统中。Git 和 Git 共享网站 GitHub 是其中一种非常流行的系统。在本教程中,我们将使用 usethis
包添加一些有用的非代码文件,帮助描述其他用户如何重用我们的代码以及当前开发状态,并添加机制确保下游用户拥有与你的代码相关依赖的其他包。接下来,我们将看看如何将包上传到 GitHub 以及如何直接从 GitHub 安装。
准备工作
我们需要 usethis
和 devtools
包。
如何操作...
使用 devtools
从 GitHub 托管代码可以通过以下步骤完成:
- 向包中添加一些有用的元数据和许可证文件:
usethis::use_mit_license(name = "Dan MacLean")
usethis::use_readme_rmd()
usethis::use_lifecycle_badge("Experimental")
usethis::use_version()
- 向依赖项列表中添加将在安装包时自动安装的内容:
usethis::use_package("ggplot2")
- 自动设置本地 Git 仓库并获取 GitHub 凭证:
usethis::use_git()
usethis::browse_github_token()
usethis::use_github()
- 从 GitHub 安装包:
devtools::install_github("user/repo")
工作原理...
第 1 步 中的代码非常简单,但它为包添加了很多内容。usethis::use_mit_license()
函数会添加一个名为 LICENSE
的文本文件,文件中包含 MIT 许可协议的内容。没有许可证文件,其他人很难知道在什么条件下可以使用该软件。MIT 许可协议是一种简单且宽松的许可,非常适合一般的开源软件,但也有其他替代方案;有关如何选择适合你的许可证,参见此网站:choosealicense.com/
。查看 usethis
文档,了解有关许可证的相关函数,允许你添加其他许可证类型。所有这些函数的参数名称允许你指定软件的版权持有者—如果你为公司或机构工作,法律版权可能属于他们,值得检查这一点。
usethis::use_readme_rmd()
函数会添加一个空的 .Rmd
文件,你可以在其中添加代码和文本,该文件将被构建成一个常规的 markdown 文件,并作为 README
文件显示在你仓库的 GitHub 前端页面上。最少在此文件中添加描述你的包的目标、基本用法和安装说明。
向文档中添加一个有用的信息是指明开发的阶段。usethis::use_lifecycle_badge()
函数可以让你创建一个漂亮的小图形徽章,显示你的包的开发进度。你可以作为第一个参数使用的术语可以在这里查看:www.tidyverse.org/lifecycle/
。与此相关的是usethis::use_version()
函数,它将帮助你递增软件的主要、次要或修复版本。
在步骤 2中,我们管理包所需的依赖项。当用户安装你的包时,包管理软件应会自动安装这些依赖项;R 要求它们放置在包元数据描述文件中的特定位置。usethis::use_package()
函数会为你完成这项工作。
在步骤 3中,我们使用usethis::use_git()
函数在当前目录创建一个本地的git
仓库;它还会对当前代码进行初始提交。usethis::browse_github_token()
函数会打开一个浏览器窗口,并导航到 GitHub 页面,允许你获取 GitHub 访问令牌,以便你的 R 会话能够与 GitHub 交互。获得令牌后,usethis::use_github()
将把本地的git
仓库上传至 GitHub,设置远程仓库并推送代码。你只需要执行一次。当git
和 GitHub 仓库存在时,你需要手动管理版本,使用 RStudio 的git
面板或git
的命令行版本。
在步骤 4中,我们看到远程用户如何安装你的包,只需要使用devtools::install_github()
函数,并提供适当的用户名和仓库名。
构建单元测试套件以确保函数按预期工作
大多数程序员会过度测试代码,单元测试的实践应运而生,让我们有了一种可以自动化并帮助减少构建中等复杂代码项目所需时间的正式测试方法。一个设计良好并维护的软件包,会为尽可能多的组件函数提供单元测试套件。在本食谱中,我们将学习如何使用usethis
包添加组件文件和文件夹,以便创建一个使用testthat
包的自动化测试套件。本书的范围不包括详细探讨为什么以及如何编写测试的哲学,但你可以查看testthat
包的文档,网址是:testthat.r-lib.org/
,作为一个很好的入门指南。
准备工作
我们需要usethis
、testthat
和devtools
包。
如何操作...
使用以下步骤构建单元测试套件,以确保函数按预期工作:
- 创建测试文件夹结构:
usethis::use_testthat()
- 添加新的测试:
usethis::use_test("adds")
test_that("addition works", {
expect_equal(1 + 1, 2)
})
- 运行实际的测试:
devtools::test()
它是如何工作的...
第 1 步 是一个典型的 usethis
风格函数,创建一些你的包所需的常见文件系统组件——use_testthat()
只是构建了 testthat
测试引擎所需要的文件夹结构。
第 2 步 中,usethis::use_test()
函数开始工作,创建一个测试文件——它使用函数参数的值作为文件名的后缀,因此在此情况下,使用 adds
作为参数,会在 tests/testthat
文件夹中创建一个名为 test-adds.R
的文件。然后,我们可以将 tests
添加到该文件中。每个测试将遵循此步骤第二行所示的基本模式。调用 test_that()
函数;其第一个参数是打印到控制台的文本,显示当前正在进行的测试。第二个参数是来自 testthat
包的断言块,比较函数的输出与预期值。如果两者匹配,则测试通过;否则,测试失败。testthat
中有许多断言,可以测试多种类型的输出和对象。你可以在文档中查看:testthat.r-lib.org/
。请注意,测试应该在测试文件中完成并保存,而不是直接在控制台中输入。
在 第 3 步 中,我们在控制台使用 devtools::test()
函数自动运行测试套件。测试结果会打印到控制台,你可以根据需要修改函数,然后重新运行此步骤。
使用 Travis 的持续集成来保持代码经过测试并保持最新
持续集成(CI)是一种团队编程实践,旨在帮助大团队在同一个项目中保持代码、依赖关系和测试的最佳协作。为此开发的工具同样可以帮助我们管理自己的软件项目,解决由于自己更新、所依赖包的更新,甚至在某些情况下,R 和操作系统更新引发的问题。Travis.CI
是 devtools
包支持的一个 CI 服务。通过将 Travis.CI
集成到你的项目中,Travis 服务器将构建一个新的虚拟计算机,安装操作系统,安装 R 及你的包所需的所有依赖包,然后安装你的包并运行测试套件。Travis 会将结果发送给你。这个过程会周期性地重复,尤其是每次你向 GitHub 推送代码时,以便你及时了解哪些代码出现了问题,并能及早解决问题。在这个食谱中,我们将介绍如何为你的包项目设置 Travis。
准备工作
对于这个食谱,你需要 usethis
包和一个托管在 GitHub 上的包项目。如果你还没有设置这些,前面章节的食谱将帮助你完成这些步骤。
如何实现...
为了使用 Travis 的持续集成来保持代码经过测试并保持最新,我们需要创建一个 .travis.yml
文件:
usethis::use_travis()
它是如何工作的...
这段代码中的唯一一行会在你的包目录的根目录中创建一个名为.travis.yml
的文件。这个文件在 GitHub 上作为钩子使用,因此,一旦仓库被更新,Travis.CI
服务器将会执行新的虚拟服务器构建和打包,并运行测试,然后将结果通过电子邮件发送到与你的 GitHub 账户关联的邮箱地址。尽管这只有一行代码,但这可能是本书中最具影响力的单行代码!.travis.yml
文件包含了 Travis 构建的配置选项,并且可以添加很多内容来自定义输出。一个常见的添加内容如下:
warnings_are_errors: false
这将告诉 Travis,R 代码中的警告不应视为错误,也不会导致构建失败。
构建可能需要一些时间;即便是简单的代码也可能需要 15 分钟。更复杂的项目将需要更长的时间。