内容

原作者:马丁·摩根,索纳利·阿罗拉
展示作者:马丁•摩根
时间:2019年7月26日

非常高效。R代码

本节的目的是强调编写正确、可理解、健壮和高效的R代码的实践。

优先级

  1. 正确:与手工制作的示例一致(相同的()all.equal ()
  2. 易懂:容易懂下个月;描述它对同事的影响很容易;容易发现逻辑错误;易于增强。
  3. 鲁棒性:支持真实的输入,例如0长度的向量,NA值,…
  4. 快,或者至少考虑到现代计算机的速度是合理的。

策略

  1. 配置文件

    • 在脚本中大致理解它在做什么。
    • 一步通过代码了解它是如何执行的,并了解每一行的速度。
    • 时间对所选代码行或简单代码块的计算system.time ()或者是微基准测试包中。
    • 配置文件带有一个工具的代码,该工具指示每个函数调用或行花费了多少时间——内置的Rprof ()函数或包,例如lineprofaprof
  2. 向量化——操作向量,而不是显式循环

    x <- 1:10 log(x) ## NOT for (i in seq_along) x[i] <- log(x[i])
    ## [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101 ## [8] 2.0794415 2.1972246 2.3025851
  3. 预分配内存,然后填充结果

    Result <- numeric(10) Result [1] <- runif(1) for (i in 2:length(Result)) Result [i] <- runif(1) * Result [i - 1] Result
    ## [1] 0.8909704231 0.4146406038 0.0571932985 0.0284667752 0.0070462630 ## [6] 0.0061630984 0.0024937168 0.0022745184 0.0003264045 0.0001861150
  4. 将公共子表达式提升到重复计算之外,以便子表达式只计算一次

    • 简单,例如,' hoist '从a循环
    • 更高层次的,例如,使用lm.fit ()而不是重复拟合相同的设计矩阵。
  5. 重用现有的、测试过的代码

回到顶部

案例研究:预分配和向量化

这是一个低效率的函数:

F0 <- function(n, a=2) {## stopifnot(is.integer(n) && (length(n) == 1) && ## !is.na(n) && (n > 0)) result <- numeric() for (i in seq_len(n)) result <- c(result, a * log(i)) result}

使用system.time ()研究这个算法是如何扩展的n,主要关注运行时间。

system.time (f0 (10000))
##用户系统运行## 0.142 0.044 0.187
n < - 1000 * seq(2) 1, 20日t < -酸式焦磷酸钠(n,函数(i) system.time (f0(我)[[3]])情节(t ~ n、类型=“b”)

记住当前的“正确”值和一个近似的时间

N <- 10000系统。时间(预期<- f0(n))
##用户系统运行## 0.130 0.027 0.158
(预计)
## [1] 0.000000 1.386294 2.197225 2.772589 3.218876 3.583519

修改提升常用乘法器的功能,一个,在圈子之外。确保“优化”的结果和原来的计算是相同的。使用微基准测试包来比较两个版本

F1 <- function(n, a=2) {result <- numeric() for (i in seq_len(n)) result <- c(result, log(i)) a * result}相同(预期,F1 (n))
##[1]正确
微基准测试(f0(n), f1(n), times=5)
##单位:毫秒## expr min lq mean median uq max neval cld ## f0(n) 106.0858 106.8267 110.0829 108.6577 111.2711 117.5732 5 a ## f1(n) 106.5467 107.5789 109.9853 109.8549 111.0009 114.9453 5 a

采用“预分配和填充”策略

F2 <- function(n, a=2) {result <- numeric(n) for (i in seq_len(n)) result[[i]] <- log(i) a * result}相同(预期,F2 (n))
##[1]正确
微基准测试(f0(n), f2(n), times=5)
##单位:microseconds ## expr min lq mean median uq max ## f0(n) 125407.885 128915.301 158967.1082 156380.107 158970.766 225161.482 ## f2(n) 522.673 578.257 646.8576 678.658 689.675 765.025 ## neval cld ## 5 b# # 5 a

使用一个*应用()函数,以避免必须显式预分配,并使向量化的机会更明显。

F3 <- function(n, a=2) a * sapply(seq_len(n), log)相同(预期,F3 (n))
##[1]正确
微基准测试(f0(n), f2(n), f3(n),乘以=10)
##单位:microseconds ## expr min lq mean median uq max ## f0(n) 116236.001 117543.999 125837.8697 122340.567 128809.586 158061.487 ## f2(n) 535.368 549.919 582.1863 571.667 594.484 688.245 ## f3(n) 2371.288 2467.712 2773.2181 2655.956 2827.480 4202.900 ## neval cld ## 10 b# # 10 a

既然代码是在单行中显示的,显然可以很容易地向量化它。抓住机会向量化它:

F4 <- function(n, a=2) a * log(seq_len(n))相同(期望,F4 (n))
##[1]正确
微基准(f0(n), f3(n), f4(n), times=10)
##单位:microseconds ## expr min lq mean median uq max ## f0(n) 108984.448 112646.525 128658.2867 117566.5805 122747.206 224810.810 ## f3(n) 2319.026 2427.799 2583.1335 2526.5675 2582.082 3334.533 ## f4(n) 61.674 64.410 202.8533 65.2965 70.140 1430.968 ## neval cld ## 10 b# # 10 a

f4 ()看起来绝对是赢家。它是如何缩放的n?(重复几次)

N <- 10 ^ (5:8) # 100x大于f0 t <- sapply(N, function(i) system.time(f4(i))[[3]]) plot(t ~ N, log="xy", type="b")
##警告在xy。Coords (x, y, xlabel, ylabel, log): 1 y值<= 0省略了##对数图

对不同的反应模式有什么解释吗?

经验教训:

  1. 向量化比迭代提供了巨大的改进
  2. 当需要显式迭代时,预分配和填充非常有用。
  3. *应用()函数有助于避免显式预分配的需要,并使向量化的机会更加明显。这可能会带来较小的性能代价,但通常是值得的
  4. 当需要显式迭代时,提升公共子表达式有助于提高性能。

回到顶部

迭代和限制来管理内存

当数据太大而无法装入内存时,我们可以以块的形式遍历文件,或按字段或基因组位置将数据划分为子集。

迭代

限制

练习:计算重叠

遍历文件:GenomicFiles: reduceByYield ()

  1. 让出一块
  2. 从输入块映射到可能转换的表示
  3. 减少映射块
suppressPackageStartupMessages({library(genome icfiles) library(genome icalignments) library(Rsamtools) library(TxDb.Hsapiens.UCSC.hg19.knownGene) library(rnaseqdata . hnrnc电脑.bam.chr14)}) yield <- #如何输入下一个数据块函数(X,…){readGAlignments(X)} map <- #对每个块函数(VALUE,…){olaps <- findOverlaps(VALUE, roi, type="within", ignore.strand=TRUE) count <- tabulate(subjectHits(olaps), subjectLength(olaps)) setNames(count, names(roi))} reduce <- ' + ' #如何合并映射块

改进:“yield factory”跟踪输入了多少记录

yieldFactory <- #返回一个带有局部状态函数的函数(){n_records <- 0L function(X,…){aln <- readGAlignments(X) n_records <<- n_records + length(aln) message(n_records) aln}}

感兴趣的区域,像bam文件中的染色体一样命名。

exByTx <- exonsBy(TxDb.Hsapiens.UCSC.hg19)。knownGene, tx)

迭代bam文件的函数。

count1 <- function(filename, roi) {message(filename) ##创建并打开BAM文件bf <- BamFile(filename, yieldSize=1000000) reduceByYield(bf, yieldFactory(), map, reduce, roi=roi)}

在行动

##实例bam文件库的路径(RNAseqData.HNRNPC.bam.chr14)chr14_BAMFILES count <- count1(bam[[1]], exByTx)
# # /用户/ ma38727 /图书馆/ R / 3.6 / Bioc / 3.10 / RNAseqData.HNRNPC.bam.chr14 / extdata / ERR127306_chr14.bam
## 800484
Hist (log10(count)) #掉0

回到顶部

文件管理

文件类

类型 示例使用 的名字
请全部 范围注释 BedFile () rtracklayer
.wig 报道 WigFile ()BigWigFile () rtracklayer
.gtf 记录模型 GTFFile () rtracklayer
makeTxDbFromGFF () GenomicFeatures
.2bit 基因组序列 TwoBitFile () rtracklayer
.fastq 阅读和品质 FastqFile () ShortRead
本演讲 对齐的读取 BamFile () Rsamtools
.tbx 索引一样 TabixFile () Rsamtools
.vcf 变量调用 VcfFile () VariantAnnotation
## rtracklayer menagerie suppressPackageStartupMessages(library(rtracklayer)) names(getClass("RTLFile")@subclasses)
## [1] "UCSCFile" "GFFFile" "BEDFile" ## [4] "WIGFile" "BigWigFile" "ChainFile" ## [7] "TwoBitFile" "FastaFile" "TabSeparatedFile" ## [10] "CompressedFile" "GFF1File" "GFF2File" "GFF2File" ## [13] "GFF3File" "BEDGraphFile" "BED15File" ## [16] "BEDPEFile" "NarrowPeakFile" "BroadPeakFile" ## [19] "BWFile" "2BitFile" " ggffile " ## [25] "BZ2File" "XZFile" ## [4] "TwoBitFile" "BigWigFile" "TabSeparatedFile" ## [10] "CompressedFile" "GFF1File" "GFF2File" ## [7] "TwoBitFile" "BigWigFile" "TabSeparatedFile" ## [10] "CompressedFile" "GFF1File" "GFF2File" ## [13] "GFF3File" "BEDGraphFile" "BED15File" ## [16] " beff3file " "BEDGraphFile" "BED15File"

笔记

  • 接口不一致
  • open ()close ()进口()/产量()/读* ()
  • 有些:通过索引选择性导入(例如,.bai, bam索引);选择(“列”);限制(“行”)

管理文件集合

*文件列表()

  • reduceByYield ()-遍历单个大文件
  • bplapply ()BiocParallel) -在多个文件上并行执行独立操作

GenomicFiles ()

  • '行'作为基因组范围限制,'列'作为文件
  • 每一行x列是a地图从文件数据到有用的表示R
  • reduceByRange ()reduceByFile ():将映射折叠为摘要表示
  • 参见基因组档案小插图图1

VcfStack ()

  • 将VCF文件拆分为单个染色体的常见做法。
  • 把它当作单一实体来对待很简单
  • 看到VcfStack ?

回到顶部

平行的评价

需要注意的是

迭代/限制技术使内存需求处于控制之下,而并行计算将计算负载分布到各个节点。请记住,并行计算仍然受到每个节点上可用内存量的限制。

设置和拆除集群会产生开销,在分布式内存中进行计算时更是如此。对于小型计算,并行开销可能会超过性能没有提高的好处。

从并行执行中获益最多的作业是cpu密集型作业,它们操作的数据块适合内存。

BiocParallel

BiocParallel为并行评估提供了一个标准化的接口,并支持主要的并行计算风格:单台计算机上的分叉和进程、AD hoc集群、批处理调度器和云计算。默认情况下,BiocParallel选择一个适合操作系统的并行后端,并且在Unix、Mac和Windows上都得到支持。

一般的想法:

  • 使用bplapply ()而不是拉普兰人()
  • 论点BPPARAM影响并行评估的发生方式

    • MulticoreParam ():单个(非windows)机器上的线程
    • SnowParam ():同一或不同机器上的进程
    • BatchJobsParam ():集群的资源调度程序

练习:连续睡眠和平行睡眠

这个小示例鼓励使用并行执行,并演示如何使用并行执行bplapply ()可以顺便来一下吗拉普兰人

使用system.time ()为了探索这需要多长时间来执行n从1增加到10。使用相同的()而且微基准测试比较备选方案f0 ()而且f1 ()为了正确性和性能。

有趣的先睡1秒,然后再回来

library(BiocParallel) fun <- function(i) {Sys.sleep(1) i} ## serial f0 <- function(n) lapply(seq_len(n), fun) ## parallel f1 <- function(n) bplapply(seq_len(n), fun)

回到顶部

练习:错误处理和BPREDO

BiocParallel“捕获并返回”错误以及成功的结果。本练习演示如何访问回溯()以及如何使用“BPREDO”重新运行失败的任务。关于错误处理、日志记录和调试的详细信息见错误,日志和调试装饰图案。

param <- MulticoreParam(workers =3) # Windows: SnowParam(workers=3)

调用sqrt ()函数跨越' X ';第二个元素是一个字符,会抛出和出错。

X <- list(1, "2", 3) res <- bplapply(X, sqrt, BPPARAM = param)
第一个错误:数学函数的非数字参数

它还可以捕获错误和部分计算结果

res <- bptry(bplapply(X, sqrt, BPPARAM=param)
## [[1]] ## [1] ## ## [[2]] ##  ## traceback() available as 'attr(x, "traceback")' ## ## [[3]] ## [1] 1.732051

重复调用,重新运行失败的结果bplapply ()这次的输入数据和部分结果是“BPREDO”。只有失败的值才会重新运行。

X.redo <- list(1,2,3) bplapply(X. redo)redo, sqrt, BPREDO = res)
([1]) # # # # # # # # [1] 1 [[2]] 1.414214 # # # # # # [1] [[3]] # # 1.732051 [1]

或者,切换到aSerialParam ()并调试导致错误的特定元素。

> fun =函数(i){浏览器();sqrt(i)} > bplapply(X, fun, BPREDO=res, BPPARAM=SerialParam())恢复之前的计算…调用自:FUN(…)在# 1:浏览[1]>调试sqrt (i)浏览[2]>我[1]“2”浏览[2]>我= 2浏览[2]c [[1]] > [1] 1 [[2]] [1] 1.414214 1.732051 [[3]] [1]

回到顶部

练习:日志记录

BiocParallel使用futile.logger用于日志记录的包。该包有一个灵活的系统,用于过滤具有不同严重程度阈值的消息,如INFO、DEBUG、ERROR等(有关所有阈值的列表,请参阅?bpthreshold手册页)。BiocParallel捕获用无效语句写的消息。记录器格式以及写入stdout和stderr的消息。

这个函数执行一些参数检查,并具有DEBUG、WARN和info级别的日志消息。

FUN <- function(i) {flg .debug(paste0(" 'i'的值:",i)) if (!length(i)) {flog.debug。warn("'i'缺失")NA} else if (!我s(i, "numeric")) { flog.info("coercing to numeric") as.numeric(i) } else { i } }

在参数中打开日志记录,并将阈值设置为WARN。

bplapply(list(1, "2", integer()), FUN, BPPARAM = param)

将阈值降低到INFO和DEBUG(即使用bpthreshold < -),以了解信息是如何按严重程度筛选的。

回到顶部

练习:工人超时

对于长时间运行的作业或未经测试的代码,设置时间限制是很有用的。的超时字段是允许每个工人完成任务的时间,以秒为单位。如果一项任务花费的时间超过超时worker返回一个错误。

超时可在参数构建时设置,

- SnowParam(timeout = 20
##类:SnowParam ## bpisup: FALSE;bpnworkers: 10;bptasks: 0;bpjobname: BPJOB ## bplog: FALSE;bpthreshold:信息;## bpRNGseed:;bptimeout: 20;bpprogressbar: FALSE ## bpexportglobals: TRUE ## bplogdir: NA ## bpresultdir: NA ## cluster type: SOCK

或者用setter:

Bptimeout (param) <- 2 param
##类:SnowParam ## bpisup: FALSE;bpnworkers: 10;bptasks: 0;bpjobname: BPJOB ## bplog: FALSE;bpthreshold:信息;## bpRNGseed:;bptimeout: 2;bpprogressbar: FALSE ## bpexportglobals: TRUE ## bplogdir: NA ## bpresultdir: NA ## cluster type: SOCK

使用此函数来探索一个数值向量的“X”值的不同_timeout_sbplapply ().' X '值小于超时当这些结束时成功返回阈值返回错误。

fun <- function(i) {Sys.sleep(i) i}

回到顶部

练习:并行计数重叠

向员工分发文件:GenomicFiles: reduceByFile ()

前面的计数示例使用GenomicFiles: reduceByYield ()它对单个文件进行操作,并实现yield, map, reduce范式。在这个练习中我们将使用GenomicFiles: reduceByFile ()它使用bplaply ()在底层并行操作多个文件。

的主要参数reduceByFile ()是一组文件和一组范围。文件被发送到工作者,并根据范围提取数据子集。大部分工作都是在地图函数和一个可选的减少函数组合每个worker的输出。

suppressPackageStartupMessages({library(BiocParallel) library(genome icfiles) library(genome icalignments) library(Rsamtools)})

在Unix或Mac系统上配置MulticoreParam ()有4个工人。打开日志记录并将超时设置为60秒。

param <- MulticoreParam(4, log = TRUE, timeout = 60)

在Windows上执行同样的操作SnowParam ()

- SnowParam(4, log = TRUE, timeout = 60)

指向bam文件的集合。

库(RNAseqData.HNRNPC.bam.chr14) fls <- BamFileList(fls)

定义范围(感兴趣的区域)可以限制工作线程上的数据量,并控制内存需求。

ranges <- GRanges("chr14", IRanges(c(28477797, 29527797, 32448354), c(29477797, 30527797, 33448354)))

地图函数读取记录并计数重叠。readGAlignments ()类中定义的范围的任何部分重叠的bam记录scanBamParam(例如,它们可以重叠开始或结束)。一旦我们拿到了记录R,我们只想计算那些“在”范围内的。

MAP <- function(range, file,…){library(genome icalignments) ## readGAlignments(), ScanBamParam() param= ScanBamParam(which=range) ## restriction gal = readGAlignments(file, param=param) ## overlaps olaps <- findOverlaps(gal, range, type="within",忽略.strand=TRUE) tabulate(subjectHits(olaps), subjectLength(olaps))}

数……

cts <- reduceByFile(ranges, fls, MAP, BPPARAM = param)
## ############### LOG OUTPUT ############### ## Task: 1 ## Node: 1 ##时间戳:2019-07-28 13:49:01 ## Success: TRUE ####任务持续时间:## user system elapsed ## 0.426 0.144 0.573 ####内存已使用:##已使用(Mb) gc trigger (Mb) limit (Mb) max used (Mb) ## Ncells 8253002 440.8 13065045 697.8 NA 13065045 697.8 ## Vcells 17094705 130.5 53213824 406.0 32768 202994621 1548.8 ####日志消息:## INFO [2019-07-28 13:49:00] loading futile。日志包## ## stderr和stdout:
## ############### LOG OUTPUT ############### ## Task: 2 ## Node: 2 ##时间戳:2019-07-28 13:49:02 ## Success: TRUE ####任务持续时间:## user system elapsed ## 0.426 0.147 0.578 ####内存使用:##使用(Mb) gc trigger (Mb) limit (Mb) max used (Mb) ## Ncells 8254739 440.9 13065045 697.8 NA 13065045 697.8 ## Vcells 17098950 130.5 53213824 406.0 32768 202994621 1548.8 ####日志消息:## INFO [2019-07-28 13:49:00] loading futile. .日志包## ## stderr和stdout:
## ############### LOG OUTPUT ############### ## Task: 3 ## Node: 3 ##时间戳:2019-07-28 13:49:02 ## Success: TRUE ####任务持续时间:## user system elapsed ## 0.423 0.144 0.572 ####内存使用:##使用(Mb) gc trigger (Mb) limit (Mb) max used (Mb) ## Ncells 8254772 440.9 13065045 697.8 NA 13065045 697.8 ## Vcells 17099033 130.5 53213824 406.0 32768 202994621 1548.8 ####日志消息:## INFO [2019-07-28 13:49:00] loading futile. # Ncells 8254772 440.9 13065045 697.8 NA 13065045 697.8 ## Vcells 17099033 130.5 53213824 406.0 32768 202994621 1548.8 ####日志包## ## stderr和stdout:
## ############### LOG OUTPUT ############### ## Task: 4 ## Node: 4 ##时间戳:2019-07-28 13:49:02 ## Success: TRUE ####任务持续时间:## user system elapsed ## 0.426 0.145 0.574 ####内存已使用:##已使用(Mb) gc trigger (Mb) limit (Mb) max used (Mb) ## Ncells 8254805 440.9 13065045 697.8 NA 13065045 697.8 ## Vcells 17099114 130.5 53213824 406.0 32768 202994621 1548.8 ####日志消息:## INFO [2019-07-28 13:49:00] loading futile。日志包## ## stderr和stdout:

结果是一个与文件数量相同长度的列表。

长度(cts)
## [1]

每个列表元素都是范围数量的长度。

长度(cts)
## err127306 err127307 err127308 err127309 err127302 err127303 err127304 ## 3 3 3 3 3 ## err127305 ## 3

提取每个范围的计数表[[;使用simplifyToArray ()得到一个计数矩阵

cts ([1])
([1]) # # # # 429 # # # #[1][[2]] 22 # # # # # #[1][[3]] # # 1338年[1]
simplify2array (cts)
## err127306 err127307 err127308 err127309 err127302 err127302 err127303 err127304 ## [1,] 429 477 508 452 516 721 713 ## [2,] 22 18 37 24 14 26 30 ## [3,] 1338 1938 1618 1683 1988 2526 2372 ## err127305 ## [1,] 544 ## [2,] 20 ## [3,] 1785

回到顶部

其他资源

回到顶部

资源

回到顶部