内容

作者:Valerie Obenchain (vobencha@fredhutch.org)、马伟民(mtmorgan@fredhutch.org
时间:2015年7月22日

非常高效。R代码

本节的目标是强调如何编写正确、健壮和高效的R代码。

优先级

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

策略

  1. 配置文件
  1. 向量化——操作向量,而不是显式循环

    x <- 1:10 log(x) ## NOT for (i in seq_along) x[i] <- log(x[i])
    ## [8] 2.0794415 2.1972246 2.3025851
  2. 预分配内存,然后填充结果

    Result <- numeric(10) Result [1] <- runif(1) for (i in 2:length(Result)) Result [i] <- runif(1) * Result [i - 1] Result
    [6] 0.610156070 0.326579479 0.319540555 0.182668253 0.078093233 ## [6] 0.056413616 0.021594612 0.017351821 0.006025564 0.004570092
  3. 将公共子表达式提升到重复计算之外,以便子表达式只计算一次
  1. 重用现有的、经过测试的代码

回到顶部

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

这是一个明显低效的函数:

f0 < -函数(n = 2) {# # stopifnot (is.integer (n) & &(长度(n) = = 1) & & # # ! is.na (n) & & (n > 0))结果< -数字()(我在seq_len (n))结果[[我]]< - *日志(i)结果}

使用system.time ()研究这个算法是如何扩展的n,重点关注经过的时间。

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

记住当前的“正确”值和一个大概的时间

N <- 10000系统。时间(期望<- f0(n))
##用户系统运行## 0.141 0.000 0.142
(预计)
## [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[[i]] <- log(i) a * result}相同(期望,F1 (n))
##[1]真
库(微基准)微基准(f0(n), f1(n), times=5)
##单位:毫秒## expr min lq mean median uq max neval ## f0(n) 98.11787 154.91307 145.9875 158.2557 158.9396 159.7114 5 ## f1(n) 96.93034 97.99369 133.8523 156.3321 158.9998 159.0055

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

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)
##单位:毫秒## expr min lq mean median uq max neval ## f0(n) 102.20437 162.75624 152.84236 163.04595 167.98829 168.21692 5 ## f2(n) 11.05322 11.35959 13.71776 13.29358 15.93749 16.94491 5

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

F3 <-函数(n, a=2) a * sapply(seq_len(n), log)相同(预期,F3 (n))
##[1]真
微基准测试(f0(n), f2(n), f3(n), times=10)
##单位:毫秒## expr min lq mean median uq max ## f0(n) 100.830765 107.964489 145.823291 160.782134 164.901310 166.962340 ## f2(n) 9.370014 11.162675 12.739058 11.709209 15.512331 17.017115 ## f3(n) 4.640934 4.703644 5.799298 5.444969 6.617305 8.305891 ## neval ## 10 ## 10 ## 10

现在代码以单行形式呈现,显然可以很容易地向量化。抓住机会将其向量化:

F4 <-函数(n, a=2) a * log(seq_len(n))相同(预期,F4 (n))
##[1]真
微基准(f0(n), f3(n), f4(n), times=10)
##单位:微秒## expr min lq mean median uq max ## f0(n) 101977.842 158804.803 150045.0529 161249.9995 162876.991 165036.864 ## f3(n) 4221.855 4419.104 4925.7432 4651.7185 4885.559 6511.562 ## f4(n) 396.465 401.305 412.4188 411.3425 418.137 441.464 ## neval ## 10 ## 10 ## 10

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")

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

经验教训:

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

回到顶部

迭代和限制来管理内存

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

迭代-块-open (),读取数据块,close ().——例如,yieldSize参数Rsamtools: BamFile ()-框架:GenomicFiles: reduceByYield ()

限制-限制感兴趣的列和/或行-利用领域特定的格式- BAM文件和Rsamtools: ScanBamParam ()- BAM文件和Rsamtools: PileupParam ()—VCF文件和VariantAnnotation: ScanVcfParam ()-使用数据库

练习:计算重叠部分

遍历文件:' GenomicFiles::reduceByYield()

  1. 让步一大块
  2. 从输入块映射到可能转换的表示形式
  3. 减少映射块
suppressPackageStartupMessages({library(GenomicFiles) library(GenomicAlignments) library(Rsamtools) library(txdb . hsapiens . ucc .hg19. knowngene)}) yield <- #如何输入下一个数据块函数(X,…){readGAlignments(X)} map <- #如何对每个块函数(VALUE,…), roi) {olaps <- findOverlaps(VALUE, roi, type="within", ignore.strand=TRUE) count <- tabulate(subjectHits(olaps), subjectLength(olaps)) setNames(count, names(roi))} reduce <- ' + ' #如何组合映射块

改进:“产量工厂”跟踪输入了多少记录

yieldFactory <- #返回一个具有本地状态的函数function() {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. exByTx <- exonsBy)。knownGene, "tx") fl <- "/home/ubuntu/data/vobencha/LargeData/srarchive/hg19_alias. "tab" map0 <- read.delim(fl, header=FALSE, stringsAsFactors=FALSE) seqlevels(exByTx, force=TRUE) <- setNames(map0$V1, map0$V2)

一个遍历bam文件的函数。

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

在行动

bam <- "/home/ubuntu/data/vobencha/LargeData/srarchive/SRR1039508_sorted. "count <- count1(bam, exByTx)

回到顶部

文件管理

文件类

类型 示例使用 的名字
请全部 范围注释 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”##[7]“CompressedFile”“GFF1File”“GFF2File”“GFF2File”##[13]“GFF3File”“BEDGraphFile”“BED15File”##[16]“BWFile”“2BitFile”“GTFFile”##[19]“GVFFile”“GZFile”“BGZFile”“XZFile”##[7]“BZ2File”“XZFile”

笔记

管理文件集合

*文件列表()

GenomicFiles ()

回到顶部

平行的评价

有几个警告-

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

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

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

BiocParallel

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

一般的想法:

练习:连续和平行睡觉

这个小示例激发了并行执行的使用,并演示了如何使用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 '。错误处理、日志记录和调试的详细信息见错误,日志和调试装饰图案。

multicorepam (workers = 3

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

X <- list(1, "2", 3) res <- bplapply(X, sqrt, BPPARAM = param) res

与错误消息一起,回溯信息存储在返回列表元素中,可以使用attr ()

attr (res[[2]],“回溯”)

通过重复调用来重新运行失败的结果bplapply ()这一次用修正的输入数据和部分结果作为“BPREDO”。

X.redo <- list(1,2,3) bplapply(X。redo, sqrt, BPREDO = res)

回到顶部

练习:日志记录

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

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

FUN <- function(i) {flag .debug(paste0(" 'i'的值:",i)) if (!length(i)) {flog.debug(' ', i'))警告("'i' is missing") NA} else if (!我s(i, "numeric")) { flog.info("coercing to numeric") as.numeric(i) } else { i } }

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

- SnowParam(3, log = TRUE, threshold = "WARN") bplapply(list(1, "2", integer()), FUN, BPPARAM = param)

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

回到顶部

练习:工人超时

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

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

- SnowParam(timeout = 20
## bpjobname:BPJOB;bpworkers: 2;bptasks: 0;bptimeout: 20;bpRNGseed:;bpisup:FALSE ## bplog:FALSE;bpthreshold:信息;bplogdir:NA ## bpstopOnError:FALSE;bpprogressbar:FALSE ## bpresultdir:NA ##集群类型:SOCK

或者使用setter:

Bptimeout (param) <- 2个参数
## bpjobname:BPJOB;bpworkers: 2;bptasks: 0;bptimeout: 2;bpRNGseed:;bpisup:FALSE ## bplog:FALSE;bpthreshold:信息;bplogdir:NA ## bpstopOnError:FALSE;bpprogressbar:FALSE ## bpresultdir:NA ##集群类型: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(genomic icfiles) library(genomic icalignments) library(Rsamtools)})

在Unix或Mac上配置aMulticoreParam ()有4名工人。打开日志记录并设置60秒的超时。

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

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

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

指向bam文件的集合。

fls <- dir("/home/ubuntu/data/vobencha/LargeData/copynumber", "。bam$", full=TRUE) names(fls) <- basename(fls) bfl <- BamFileList(fls)

定义范围(感兴趣的区域)可以限制工作线程上的数据量,并控制内存需求。我们将在6号染色体上的主要组织相容性复合体位点上使用一组范围。

range <- GRanges("chr6", IRanges(c(28477797, 29527797, 32448354), c(29477797, 30527797, 33448354)))))

地图函数读取记录并计数重叠。readGAlignments ()类中定义的范围的任何部分重叠的bam记录scanBamParam(也就是说,它们可能在开头或结尾重叠)。一旦我们拿到记录R,我们只想计算“在”范围内的那些。

MAP <- function(range, file,…){library(GenomicAlignments) ## readGAlignments(), ScanBamParam() param= ScanBamParam(which=range) ##限制gal = readGAlignments(file, param=param) ##日志消息flag .info(paste0("file: ", basename(file)))) flag .debug(paste0("records: ", length(gal))) ##重叠olaps <- findOverlaps(gal, range, type="within", ignore.strand=TRUE) tabulate(subjectHits(olaps), subjectLength(olaps))}

数……

cts <- reduceByFile(ranges, fls, MAP, BPPARAM = param)

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

长度(cts)

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

elementLengths (cts)

每个范围的计数表用'[['提取:

cts ([1])

回到顶部

其他资源

回到顶部

资源

回到顶部