内容

原位作者:瓦莱丽·奥根特,马丁摩根
主持作者:Martin Morgan (martin.morgan@roswellpark.org)
日期:2016年7月15日

高效的R代码

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

优先级

  1. 正确:与手工实施例一致(完全相同的(),All.Equal())
  2. 健壮的:支持真实的输入,例如,0长度的向量,NA价值观,......
  3. 简单:易于理解下个月;容易描述它对同事做了什么;易于发现逻辑错误;易于增强。
  4. 鉴于现代计算机的速度,快速,或至少合理。

策略

  1. 轮廓
  1. 矢量化 - 在向量上运行,而不是明确的循环

    x <- 1 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
  2. 预先分配内存,然后填写结果

    Result [i] <- runif(1): Result [i] <- runif(1) * Result [i - 1] Result
    ## [6] 0.9063429518 0.1066930705 0.086125086 0.0767101194 0.0599125034 ## [6] 0.0131052207 0.0060250919 0.0045694322 0.0008398694 0.0006845054
  3. 在重复计算之外的升值常见子表达式,使子表达式仅计算一次
  1. 重新使用现有,测试代码

回到顶部

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

这是一个明显低效的功能:

f0 < - 函数(n,a = 2){## stopfnot(是.integer(n)&&(长度(n)== 1)&& ##!是.na(n)&&(n> 0))结果< -  numeric()for(i在seq_len(n)中)结果[[i]] < -  a * log(i)结果}

system.time ()来研究这个算法是如何与n,专注于经过的时间。

system.time (f0 (10000))
##用户系统经过## 0.088 0.091
n < -  1000 * seq(1,20,2)t < -  sapply(n,function(i)system.time(f0(i))[[3]]图(t〜n,type =“b”)

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

N <- 10000系统。预计时间(< - f0 (n))
##用户系统经过## 0.116 0.000 0.114
头(预期)
## [1] 0.000000 1.386294 2.197225 2.772589 3.218876 3.583519

修改呼吸常见乘法器的功能,一个, 跳出循环。确保“优化”和原始计算的结果相同。使用Microbenchmark.包以比较两个版本

F1 <- function(n, a=2) {result <- numeric() for (i in seq_len(n)) result[[i]] <- log(i) a * result} identical(expected, F1 (n))
# # [1]
库(MicroBenchmark)Microbenchmark(F0(n),f1(n),times = 5)
## f0(n) 93.75949 125.43283 120.7678 126.8942 127.3390 130.4134 5 ## f1(n) 97.80559 99.46675 116.5440 126.8709 129.2406 129.3359 5

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

F2 <- function(n, a=2) {result <- numeric(n) for (i in seq_len(n)) result[[i]] <- log(i) a * result} identical(expected, F2 (n))
# # [1]
微基准测试(f0 (n), f2 (n),时间= 5)
## f2(n) 6.887973 6.981819 7.291664 7.08013 7.471796 8.03666 5 ## f0(n) 6.887973 6.981819 7.291664 7.08013 7.471796 8.03666

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

F3 < - 函数(n,a = 2)a * sapply(seq_len(n),log)相同(预期,f3(n))
# # [1]
Microbenchmark(F0(N),F2(N),F3(N),时间= 10)
## f0(n) 6.911975 7.036002 7.523884 7.272184 8.193247 8.391159 ## f3(n) 3.120228 3.167543 3.436611 3.281130 3.726726 4.354170 ## neval ## ## ## ## ## ## ## ## ## ##

现在代码显示在一行中,很明显它可以很容易地向量化。抓住机会将其矢量化:

F4 < - 函数(n,a = 2)a * log(seq_len(n))相同(预期,f4(n))
# # [1]
Microbenchmark(F0(n),f3(n),f4(n),时间= 10)
##单位:微秒## EXPR分钟LQ平均中值最大UQ ## F0(n)的98247.257 100166.209 120943.9480 130044.098 130644.027 131510.762 ## F3(n)的2848.699 3077.010 3101.3840 3093.171 3190.727 3231.083 ## F4(N)204.107 206.843 294.2586 211.896 213.417 1034.029## Neval ## 10#10 ## 10

f4 ()绝对似乎是胜利者。它如何扩展n?(重复几次)

t <- sapply(N, function(i) system.time(f4(i))[[3]]) plot(t ~ N, log="xy", type="b")

对不同响应模式的解释?

得到教训:

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

回到顶部

管理内存的迭代和限制

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

迭代 - 块明智 -打开(),阅读块(s),关闭()。——例如,屈服参数RSAMTOOLS :: BAMFILE()- 框架:GenomicFiles: reduceByYield ()

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

练习:计算重叠

迭代文件:`genomicfiles :: dreambybyyield()

  1. 产生一大块
  2. 从输入块映射到可能转换的表示
  3. 减少映射块
suppressPackageStartupMessages({库(GenomicFiles)图书馆(GenomicAlignments)图书馆(Rsamtools)库(TxDb.Hsapiens.UCSC.hg19.knownGene)})收益率< - #如何输入的下一块数据函数(X,…){readGAlignments (X)} < - #如何映射到每个块功能(价值,…{olaps <- findoverlap (VALUE, roi, type="within", ignore.strand=TRUE) count <- tabulate(subjectHits(olaps), subjectLength(olaps)) setNames(count, names(roi))}减少<- ' + ' #如何组合映射块

改进:“产量厂”跟踪输入有多少录

exitingFactory < - #返回具有本地状态函数(){n_records < -  0l函数(x,...){aln < -  readgalignments(x)n_records <<  -  n_records + length(aln)消息(n_records)aln}}

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

Exbytx < -  Exonsby(txdb.hsapiens.ucsc.hg19.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 < - 函数(filename,roi){message(文件名)##创建和打开BAM文件BF < -  BAMFILE(FILENAME,ExuctionSize = 1000000)DreambyByield(BF,EucketFactory(),Map,Defile,ROI = ROI)}

在行动中

BAM < - “/home/ubuntu/data/vobencha/largedata/srarchive/srr1039508_sorted.bam”count < -  count1(bam,exbytx)

回到顶部

文件管理

文件类

类型 示例使用 的名字 包裹
。床 范围注释 BedFile () rtracklayer.
。假发 覆盖范围 wigfile(),bigwigfile() rtracklayer.
.gtf 记录模型 gtffile() rtracklayer.
makeTxDbFromGFF () GenomicFeatures
.2bit 基因组序列 twobitfile() rtracklayer.
.fastq 读&品质 fastqfile() ShortRead
.BAM. 对齐的阅读 BamFile () Rsamtools
.tbx. 索引标签分隔 TabixFile () Rsamtools
.vcf 变量调用 VcfFile () VariantAnnotation
## rtracklayer menagerie suppresspackagestartupmessages(库(rtracklayer))名称(getClass(“rtlfile”)@子类)
## [1]“ucscfile”“gfffile”“bedfile”## [4]“wigfile”“bigwigfile”“chabfile”## [7]“twobitfile”“fastafile”“tabseparatedfile”## [10]“compressurefile”“gff2file”“gff2file”## [13]“gff3file”“bedgraphfile”“bed15file”## [16]“bedpefile”“bwfile”“2bitfile”## [19]“gtffile”“gvffile”“gzfile”##[22]“bgzfile”“bz2file”“xzfile”

笔记

管理文件集合

*文件列表()班级

GenomicFiles ()

vcfstack()

回到顶部

平行的评价

几个警告 -

迭代/限制性技术在控制下保持内存要求,而并行评估在节点上分配计算负载。请记住,并行计算仍然受到每个节点上可用的内存量的限制。

在分布式存储器中计算时,在群集中设置和撕毁群集时,有开销。对于小的计算,并行开销可能超过性能没有提高的好处。

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

BiocParallel

BiocParallel提供标准化接口,用于并行评估,并支持主要并行计算样式:叉子和过程在单个计算机上,ad hoc集群,批量调度程序和云计算。默认,BiocParallel选择适合操作系统的并行后端,并在UNIX,MAC和Windows中支持。

一般的想法:

锻炼:连续和平行睡眠

这个小示例激发了并行执行的使用,并演示了如何使用bplapply ()可以顺便去看看吗Lapply.

system.time ()探索这需要多长时间执行n从1到10增加完全相同的()Microbenchmark.比较选择F0()F1()为了正确性和性能。

乐趣睡眠1秒,然后返回

图书馆(Biocparallel)有趣< - 函数(i){sys.sleep(1)i} ##串行f0 < - 函数(n)lapply(seq_len(n),fun)##并行f1 < -  function(n)bppppply(SEQ_LEN(N),乐趣)

回到顶部

练习:错误处理和BPREDO

BiocParallel“捕获并返回”错误以及成功的结果。本练习演示了如何访问回溯()如何使用' BPREDO '重新运行失败的任务。文档中提供了错误处理、日志记录和调试的详细信息错误,日志和调试小插图。

param <- MulticoreParam(workers = 3)

打电话给sqrt ()在“X”功能;第二个元素是一个字符,将抛出和错误。

X <- list(1, "2", 3) res <- bplapply(X, sqrt, BPPARAM = param)
## Error: BiocParallel errors ##元素索引:2 ##第一个错误:数学函数的非数字参数

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

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

通过重复呼叫重新运行失败的结果bplapply ()这次通过校正的输入数据和部分结果为“BPREDO”。只重新运行失败的值。

X.redo <- list(1, 2, 3)/ /返回BPREDO = res)
##恢复以前的计算......
([1]) # # # # # # # # [1] 1 [[2]] 1.414214 # # # # # # [1] [[3]] # # 1.732051 [1]

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

> Fun =函数(i){browser();sqrt(i)}> bpppply(x,fun,bpredo = res,bpparam = serialparam())恢复以前的计算...从:fun(...)浏览[1]>调试#1:sqrt(i)浏览[2]> i [1]“2”浏览[2]> i = 2浏览[2]> c [1]] [1] 1 [[2]] [1] 1.414214 [[3]][1] 1.732051

回到顶部

练习:日志记录

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

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

有趣< - 函数(i){flog.debug(Paste0(“I'的值:”,i))如果(!长度(i)){flog.warn(“'我缺少”)na}别的如果(!是(i,'numeric“)){flog.info(”强制到数字“)as.numeric(i)} else {i}}

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

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

降低信息和调试的阈值(即,使用bpthreshold < -)查看如何在严重性上过滤消息。

回到顶部

练习:工人超时

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

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

SnowParam <- SnowParam(timeout = 20
##类:SnowParam ## BPISUP:FALSE;BPWorkers:6;BPTASKS:0;bpjobname:bpjob ## bplog:false;BPThreshold:信息;BPStoponError:True ## Bptimout:20;bpprogressbar:false ## bprgseed:## bplogdir:na ## bpresultdir:na ##群集类型:袜子

或者与setter:

Bptimeout (param) <- 2参数
##类:SnowParam ## BPISUP:FALSE;BPWorkers:6;BPTASKS:0;bpjobname:bpjob ## bplog:false;BPThreshold:信息;BPStoponError:True ## Bptimout:2;bpprogressbar:false ## bprgseed:## bplogdir:na ## bpresultdir:na ##群集类型:袜子

使用此函数在“x”值的数字向量上探索不同的_timeout_sbplapply ()。'x'值小于超时当那些结束时返回成功临界点返回一个错误。

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

回到顶部

练习:单独计数重叠

在工人中分发文件:GenomicFiles: reduceByFile ()

使用之前的计数示例GenomicFiles: reduceByYield ()它对单个文件进行操作,并实现了yield、map、reduce范式。在这个练习中,我们将使用GenomicFiles: reduceByFile ()它使用bplaply()在引擎盖下,并行地在多个文件上运行。

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

suppressPackageStartUpMessages({图书馆(生物竞争)库(GenomicFiles)库(基因组)库(RsamTools)})

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

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

在Windows上做同样的事情SnowParam ():

Param < -  SnowParam(4,log = true,超时= 60)

指向bam文件集合。

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

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

范围< -  Granges(“Chr6”,绞油(C(28477797,29527797,32448354),C(29477797,30527797,33448354)))

地图功能在记录中读取并计数重叠。ReadGalignments()对象中定义的范围的任何部分重叠的bam记录Scanbamparam.(即,它们可以重叠开始或结束)。一旦我们有记录R,我们只想计算那些在范围内的。

地图< - 函数(范围,文件,......){库(基因组)## ReadGalignments(),ScanBamparam()Param = ScanBamparam(哪个=范围)##限制Gal = Readgalignments(文件,Param = Param)##日志消息flog.info(Paste0(“文件:”,baseName(文件)))flog.debug(Paste0(“记录:”,长度(gal)))##重叠OLAP < -  footoverlaps(GAL,Range,Type =“在”中,icionore.strand = true)Tabulate(主题(OLAPS),主管长度(OLAP))}

数数 …

CTS < -  DreambyByFile(范围,杂志,地图,BPParam = Param)

结果是列出与文件数相同的长度。

长度(cts)

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

ElementLengths(CTS)

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

cts ([1])

回到顶部

其他资源

回到顶部

资源

回到顶部