作者:马丁摩根(mtmorgan@fhcrc.org.),Sonali Arora(sarora@fredhutch.org)
日期:2015年6月19日
本节的目标是学习如何编写正确、健壮、简单和高效的R代码。我们通过一些案例研究来做到这一点。
相同的()
那All.Equal()
)NA
价值观,......系统时间()
或者是Microbenchmark.包裹。Rprof ()
功能或包装函数lineprof或者4月份矢量化 - 在向量上运行,而不是明确的循环
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
预先分配内存,然后填写结果
Result [i] <- runif(1): Result [i] <- runif(1) * Result [i - 1] Result
## [1] 0.7386850875 0.3696589691 0.3542664926 0.0510763549 0.0254611460 ## [6] 0.0070750491 0.0038783618 0.0011608073 0.0007160663 0.0006206700
为了
环形lm.fit ()
而不是重复地拟合相同的设计矩阵。表格()
那rowSums ()
和朋友,%, %
,…这是一个明显低效的函数:
f0 < - 函数(n,a = 2){## stopfnot(是.integer(n)&&(长度(n)== 1)&& ##!是.na(n)&&(n> 0))结果< - numeric()for(i在seq_len(n)中)结果[[i]] < - a * log(i)结果}
用系统时间()
调查该算法如何衡量N
,专注于经过的时间。
System.time(F0(10000))
##用户系统经过## 0.119 0.004 0.122
n < - 1000 * seq(1,20,2)t < - sapply(n,function(i)system.time(f0(i))[[3]]图(t〜n,type =“b”)
记住当前的“正确”值,以及近似的时间
n < - 10000 system.time(预期< - f0(n))
##用户系统运行## 0.121 0.000 0.120
头(预期)
## [1] 0.000000 1.386294 2.197225 2.772589 3.218876 3.583519
修改呼吸常见乘法器的功能,一种
,不知情。确保“优化”结果与原始计算结果相同。使用Microbenchmark.包以比较两个版本
f1 < - 函数(n,a = 2){for(i在seq_len(n)中)的结果< - numeric()结果[[i]] < - log(i)a *结果}相同(预期,f1(n)))
## [1]真实
库(MicroBenchmark)Microbenchmark(F0(n),f1(n),times = 5)
##单位:毫秒为MIN LQ均值UQ MAX NEVAL ## F0(n)109.7719 109.9072 130.5134 141.9948 142.5390 148.3542 5 ## F1(n)108.563013939333.2842 13333.2842 139.59794
采用“预先分配和填补”策略
F2 < - 函数(n,a = 2){结果< - numeric(n)for(i在seq_len(n)中)结果[[i]] < - log(i)a *结果}相同(预期,f2(n))
## [1]真实
微基准测试(f0 (n), f2 (n),时间= 5)
##单位:毫秒## EXPR分钟LQ平均中值最大UQ ## F0(n)的121.201141 121.739866 134.761569 143.617813 143.620317 143.628707 ## F2(n)的7.684828 7.849474 8.559228 8.415322 8.888803 9.957714 ## neval ## 5 ## 5
用A.*申请()
函数,以避免必须显式地预分配,并使向量化的机会更加明显。
F3 < - 函数(n,a = 2)a * sapply(seq_len(n),log)相同(预期,f3(n))
## [1]真实
微基准测试(f0(n), f2(n), f3(n), times=10)
## f0(n) 7.65573 7.698159 8.250884 8.177678 8.452503 9.566792 ## f3(n) 3.579709 3.677523 4.083759 4.048497 4.455364 4.736191 ## neval ## 10 ## 10 ## 10
既然代码以单行呈现,那么很明显它可以很容易地矢量化。抓住机会向量化它:
F4 <- function(n, a=2) a * log(seq_len(n)) identical(expected, F4 (n))
## [1]真实
Microbenchmark (f0(n), f3(n), f4(n), times=10)
##单位:微秒## EXPR分钟LQ平均中值最大UQ ## F0(n)的112256.477 145432.576 149337.9525 146027.0340 169576.336 179843.968 ## F3(n)的3583.222 3925.399 4090.4899 3987.5965 4017.998 5427.407 ## F4(N)364.700 378.439 395.9539 402.8765 407.481 422.962## Neval ## 10#10 ## 10
返回我们的显式迭代F2()
在这些情况下,将代码编译为更有效的表示有助于。使用编译器包执行此操作。
f2c <- cmpfun(f2) n <- 10000相同的(f2(n), f2c(n))
## [1]真实
微基准测试(f2 (n), f2c (n),时间= 10)
## f2c(n) 2.000680 2.045304 2.157064 2.164375 2.216226 2.38820 10 ## f2c(n) 2.000680 2.045304
F4()
绝对是赢家。它是如何缩放的N
吗?(重复几次)
n < - 10 ^(5:8)#100x大于f0 t < - sapply(n,function(i)system.time(f4(i))[[3]])绘图(t〜n,log =“xy“,type =”b“)
对不同响应模式的解释?
得到教训:
*申请()
功能有助于避免需要明确的预分配,并为矢量化的机会更加明显。这可能会出现小的性能成本,但通常是值得的在抽象意义上推理算法可能非常有帮助,以了解操作如何规模的理解。这是一个有趣的问题,取自堆栈溢出假设有一个很长的分类向量
VEC < - C(SEQ(-100,-1,length.out.out = 1e6),rep(0,20),seq(1,100,length.out = 1e6))
使用简单目标是识别小于零的值数。原始帖子和许多响应表明,扫描载体的变化对于少于零,然后求和
F0 < - 函数(v)总和(v <0)
该算法比较了每个元素vec
为零,创建逻辑向量,只要原件,长度(v)
。然后扫描该逻辑向量和()
计算满足条件的元素数量。
问题:
V.
需要分配给这个算法吗?基于需要执行的比较数,您如何预期该算法将缩放的长度V.
吗?用一个简单的数字验证这个。
N <- seq(1,11,2) * 1e6 Time <- sapply(N, function(N) {v <- sort(rnorm(N)) system.time(f0(v))[[3]]}) plot(Time ~ N, type="b")
是否有更好的算法,即,一种到达相同答案的方法,但需要更少的时间(和/或空间)?矢量被排序,我们可以通过做一个二进制搜索。算法出奇地简单:创建最小(第一个)元素和最大(最后一个)元素的索引。检查中间的元素是否大于等于零。如果是,将最大索引移动到该点。否则,就把这个点设为新的最小值。重复此过程,直到最小索引不再小于最大索引。
F3 < - 函数(x,阈值= 0){imin < - 1l imax < - 长度(x)(imax> = imin){imid <--as.integer(imin +(imax-imin)/ 2)(imin +(imax-imin)/ 2)(X [IMID]> =阈值)IMAX < - IMID - 1L ELSE IMIN < - IMID + 1L} IMAX}
每次迭代都丢弃大约一半可能的值,因此我们预计平均地将在最后到达log2(长度(v))
迭代 - 算法缩放了长度的日志V.
,而不是与长度V.
,没有创建长向量。这些差异随着长度而越来越重要V.
就长了。
问题:
F3()
和f0 ()
导致相同的()
答案。比较如何定时F3()
向量长度的缩放。
## Identity Stopifnot(相同(f0( - 2):2),f3(( - 2):2)),相同(f0(2:4),f3(2:4)),相同(f0( -(4:2)),F3( - (4:2)))),相同(F0(VEC),F3(VEC)))##刻度N < - 10 ^(1:7)时间< - sapply(n,函数(n){v < - sort(rnorm(n))system.time(f3(v))[[3]])图(时间〜n,type =“b”)
f0 ()
和F3()
使用原始数据,vec
。可以编译R代码,并且编译有助于执行非矢量化操作的最多F3()
。用编译器:: cmpfun()
编译F3()
,并使用Microbenchmark比较结果。
##相对时间库(MicroBenchmark)Microbenchmark(F0(VEC),F3(VEC))
##单位:微秒## expr min lq平均值UQ max ## F0(VEC)15659.97 16503.977013131386 17468.60386 17468.60386 17468.603947.157 23199.554 28.08 30.08 30.08 30.08 30.08 30.08 30.9575 47.23053 47.764 52.141 113.544 52.141 113.544 ## neval ## 100#100
f3c <- cmpfun(f3)微基准测试(f3(vec), f3c(vec))
##单位:微秒针## EXPR MIN LQ平均UQ MAX NEVAL ## F3(VEC)28.470 29.8355 33.27442 34.6335 36.2500 48.503 100 ## F3C(VEC)6.578 7.0270 7.85183 7.85183 7.6945
我们很可能通过用C编写二进制搜索算法来获得额外的速度,但是我们已经很满意性能的提高,所以我们不会再那样做了!
问丢失的是有用的F3()
相比f0 ()
。例如,算法是否在字符向量上工作?当向量包含时呢?NA
价值观?如何治疗联系?
findInterval ()
可能是解决原始问题的一种更好的内置方法,并适用于其他情况。我们的想法是把我们感兴趣的问题,0.
,并找到指定的间隔vec
它发生的地方。
f4 <- function(v, query=0)Eps, v) identical(f0(vec), f4(vec))
## [1]真实
Microbenchmark(F0(VEC),F3(VEC),F4(VEC))
## f3(vec) 28.265 30.392 44.537 43.5655 48.658 97.324 ## f4(vec) 13645.076 13698.410 13946.125 13777.6360 14172.281 15026.055 ## neval ## 100 ## 100 ## 100
事实上,它是灵活的和良好的测试意味着它往往是首选F3()
,即使它不太快速。例如,比较使用10000不同点的查询所需的时间F3
和迭代,与findInterval
和矢量化。
Threshold <- rnorm(10000)相同(sapply(Threshold, f3, x=vec), f4(vec, Threshold))
## [1]真实
微基准测试(sapply(x, f3), f4(vec, x))
##单位:微秒## EXPR MIN LQ平均UQ ## SAPPLED(X,F3)38.121 40.8785 76.45288855 89.028 ## F4(vec,x)13650.604 13695.7095 13811.30469 13728.5565 13830.765/13830.765 ## MAX NEVAL ## 154.216 100 ##100.
一些实现有效算法的功能是sort ()
(包括Radix Sort),匹配()
(哈希表查找),和表格()
;这些可以在您自己的代码中有用。
得到教训:
这是一个先进的运动,谨慎行事
这个扩展的示例说明了如何计算跨几个BAM文件对齐读取的GC内容的分布。我们首先顺序处理一个BAM文件,然后并行处理许多BAM文件。
查找以下示例BAM文件的路径(这些小,但足够大以说明原则。
fls <- RNAseqData.HNRNPC.bam.chr14_BAMFILES . txt
BAM文件很大,所以不能适合内存。此外,我们最终将并行处理多个BAM文件,因此我们需要进一步管理处理每个BAM文件时消耗的内存量。我们采取方法。
第一个是迭代通过BAM文件中的块,足够大,可以从'速度矢量化计算中受益,但不太大,以消耗过多的内存。我们使用使用Bamfilelist()
表示我们想输入大小的大小的块中的对齐读数
库(RSAMTools)BFL < - BamFileList(FLS,ExecionSize = 100000)
每次从BAM文件读取时,我们都会输入下一个100,000条记录。我们将采用我们的第二次管理记忆策略限制除了对准坐标之外,从BAM文件读取从BAM文件读取的数据,特别是每个读取的DNA序列。我们将通过写作函数来完成此操作屈服()
使用GenomicFiles :: Readgalign()
输入所需资料;请参阅帮助页面,了解我们使用但您不理解的函数,例如:?scanbamparam()
。
图书馆(基因组)产量< - 函数(BFL){##输入一个块的对齐库(基因组)ReadgGalignments(BFL,Param = ScanBamparam(什么=“SEQ”))}
接下来,我们将把对齐的读取转换为GC内容。我们将使用Biostrings :: Letterfrequency()
计算每次读取中G或C的比例,将这些数据列成表格,并计算每次读取的累计次数。
图书馆(Biostrings)地图< - 函数(ALN){#gc内容,垃圾箱&cummulate gc < - letterfrequency(mcols(aln)$ seq,“gc”)表格(1 + gc,73)#max。阅读长度:72}
map ()
将应用于返回的每个数据的结果屈服()
;我们写一个函数减少()
结合了两个呼叫的结果map ()
变成一个简单的总结。在我们的例子中,减少
只是对两次连续呼叫的返回值的追求只是map ()
。
减少< - `+`
这基因组夫妇包裹提供了一种方法可以一起缝合这些作品,特别是reduceByYield ()
功能,在以下代码块中示出
图书馆(基因组)BF < - Bamfile(FLS [1],ExectionSize = 100000)CrameyByield(BF,产量,地图,减少)
## [1] 0 0 0 0 0 0 0 0 0 0 0 ## [12] 0 1 1 4 24 41 87 238 490 907 1397 ## [23] 2127 3208 4706 6220 8737 11052 14680 17020 19360 21804 27047 ##[34] 29212 31249 35395 39807 40259 41722 42304 44108 44073 42317 41260 ## [45] 38372 35689 32447 27815 22153 18960 14188 10768 7887 6182 4817 ## [56] 3332 2101 1652 1455 860 459 235 116 73 34 22 ## [67] 6 4 0 0 0 0 0
上面打印出的结果是与0,1,...,73g或C核苷酸的数量对齐。任何时候都没有超过100,000个BAM记录,因此内存消耗是适度的。尽管如此,我们已处理整个文件。
现在我们可以通过单个文件迭代以在适度的内存中生成GC内容,很容易并行处理所有文件:使用bpppply()
调用reduceByYield ()
在每个文件上,传递其他参数收益率
那地图
,减少
。
图书馆(Biocomplallel)GC < - BPLapply(BFL,Dreambyyield,产量,地图,减少)
结果是GC-Count Vectors的列表,每个文件的一个元素。
结果可以转换为adata.frame ()
库(GGPLOT2)DF < - 堆叠(as.data.frame(lapply(gc,cumsum)))df $ gc < - 0:72
和可视化,例如
库(GGPlot2)GGPlot(DF,AES(X = GC,Y =值))+ Geom_line(AES(Color = Ind))+ XLAB(“每个读取的GC核苷酸数”)+ Ylab(“读数数”)