将数据表中的多个分位数应用于多列

时间:2017-07-24 14:52:09

标签: r performance data.table zoo median

背景
我可以使用data.table(见附件)从我的数据中获取多个时刻,但这需要很长时间。我认为对表进行排序以获得特定百分位数的过程对于找到几个更有效。

像中位数这样的一次性统计数据占1.79 ms,而非中位数分位数在122.8 ms时延长68倍。必须有一种方法来缩短计算时间。

问题:

  • 有没有办法以更有效的方式从同一数据中调用多个分位数?
  • 我可以拉扯" lapply"从data.table出来并像我一样编写名单?

我的示例代码包含很少的合成数据:

#libraries
library(data.table)      #data.table
library(zoo)             #roll apply

#reproducibility
set.seed(45L)

#make data
DT<-data.table(V1=c(1L,2L),
               V2=LETTERS[1:3],
               V3=round(rnorm(300),4),
               V4=round(runif(150),4),
               V5=1:1200)
DT

#get names
my_col_list <- names(DT)[c(3,4)]

#make new variable names
my_name_list1 <- paste0(my_col_list, "_", "33rd_pctile")
my_name_list2 <- paste0(my_col_list, "_", "77rd_pctile")

#compute values
for(i in 1:length(my_col_list)){
     #first 
     DT[, (my_name_list1[i]) := unlist(lapply(.SD,
                                          function(x) rollapply(x,
                                                                7,
                                                                quantile,
                                                                fill = NA,
                                                                probs = 1/3)), 
                                   recursive = F),
        .SDcols = my_col_list[i]]
     #second
     DT[, (my_name_list2[i]) := unlist(lapply(.SD,
                                          function(x) rollapply(x,
                                                                7,
                                                                quantile,
                                                                fill = NA,
                                                                probs = 7/9)), 
                                   recursive = F),
        .SDcols = my_col_list[i]]
}

#display it
head(DT,10)

微观标记一次性统计数据与多次统计数据表明分位数是昂贵的。

res2 <- microbenchmark(          DT[, (my_name_list1[i]) := unlist(lapply(.SD,
                                                                          function(x) rollapply(x,
                                                                                                7,
                                                                                                mean,
                                                                                                fill = NA)), 
                                                                   recursive = F),
                                    .SDcols = my_col_list[i]],
                                 times = 5)

表示平均值约为1.75毫秒(中位数为1.79秒)

> res2
Unit: milliseconds
                                                                                                                                            expr
 DT[, `:=`((my_name_list1[i]), unlist(lapply(.SD, function(x) rollapply(x,      7, mean, fill = NA)), recursive = F)), .SDcols = my_col_list[i]]
      min       lq     mean   median       uq     max neval
 1.465779 1.509114 1.754145 1.618591 1.712103 2.46514     5

但计算分位数需要100倍

res3 <- microbenchmark(          DT[, (my_name_list1[i]) := unlist(lapply(.SD,
                                                                          function(x) rollapply(x,
                                                                                                7,
                                                                                                quantile,
                                                                                                fill = NA,
                                                                                                probs = 1/3)), 
                                                                   recursive = F),
                                    .SDcols = my_col_list[i]],
                                 times = 5)

res3

> res3
Unit: milliseconds
                                                                                                                                                             expr
 DT[, `:=`((my_name_list1[i]), unlist(lapply(.SD, function(x) rollapply(x,      7, quantile, fill = NA, probs = 1/3)), recursive = F)), .SDcols = my_col_list[i]]
      min       lq     mean   median       uq      max neval
 118.5833 119.2896 122.8432 124.0168 124.4183 127.9082     5

更新:

  • 来自&#34;分位数&#34;的中位数需要~128秒而#34;中位数&#34; 要少得多。它们不是一回事。
  • 迭代&#34;类型&#34;的9个选项&#34;分位数&#34;给出了意思 时间在129ms到157ms之间。没有&#34;轻松获胜&#34;这里。
  • 包裹&#34; WGCNA&#34;需要&#34; GO.db&#34;来自bioconductor,不是 安装了&#34; install.packages&#34;命令。还需要包装 &#34;插补&#34;没有安装&#34; WGCNA&#34;或&#34; GO.db&#34;。也 &#34; preprocessCore&#34;
  • 使用(最终工作)WGCNA包减少了平均时间 滚动分位数到68.1毫秒。这大约是一半的时间,但确实如此 不是大约1/70的时间。
  • 使用&#34; RollingMedian&#34;来自&#34; RollingWindow&#34;包我得169.8 微秒(又名0.1698毫秒),这是一个很快,但是 不是任意的分位数。
  • 使用&#34; perccal&#34;似乎将分位数计算下降到~145 微秒。在rollapply中,这将计算时间降至15.3 毫秒,这是8.5倍的提升。我不知道还有多少 血液就会挤出这块石头。

思考:

  • &#34; perccal&#34;方法似乎只使用单个核心。那里 可能是某些&#34; parallel&#34;允许我拆分摘要的过程 针对不同核心的不同变量。这可能会给一些 加速。
  • 随着我们向数据添加更多术语,加速开始减少。 增加到9600行可将速度从约8.5倍降低到低于1倍。 这表明rollapply功能也可能存在一些问题。

1 个答案:

答案 0 :(得分:2)

数据表优化

在这种情况下,中位数特别快,这是正确的,这是因为它运行的是专门的C代码,而不像纯粹的R代码分位数函数。

我们可以在

中的data.table文档中了解此优化
?data.table.optimize

我们有:

  

当j中的表达式只包含这些函数min,max时,   mean,median,var,sd,prod,例如,dt [,list(mean(x),   中位数(x),min(y),max(y)),by = z],它们被非常有效地优化   使用,我们称之为GForce。这些功能被gmean取代,   gmedian,gmin,gmax而不是

他们举例说明了中间案例的速度提升:

# Generate a big data.table with a relatively many columns
set.seed(1L)
dt = lapply(1:20, function(x) sample(c(-100:100), 5e6L, TRUE))
setDT(dt)[, id := sample(1e5, 5e6, TRUE)]
print(object.size(dt), units="Mb") # 400MB, not huge, but will do

# GForce
options(datatable.optimize = 2L) # optimisation 'on'
system.time(ans1 <- dt[, lapply(.SD, median), by=id])
system.time(ans2 <- dt[, lapply(.SD, function(x) as.numeric(stats::median(x))), by=id])
identical(ans1, ans2)

在我的系统上,R内部版本比data.table版本慢约44倍。

加速分位数

我们仍然可以尝试提高R中quantile函数的速度,因为我的方法基本上是“使用源,Luke”并查看分位数函数。查看源代码,我们得到标准泛型函数:

>> quantile
function (x, ...) 
UseMethod("quantile")
<bytecode: 0x0000000009154c78>
<environment: namespace:stats>

我们可以追溯一点:

>> methods(quantile)
[1] quantile.default* quantile.ecdf*    quantile.POSIXt*  quantile.zoo     
see '?methods' for accessing help and source code

并查看默认函数。

>> stats:::quantile.default
function (x, probs = seq(0, 1, 0.25), na.rm = FALSE, names = TRUE, 
    type = 7, ...) 
{
...
}

现在我们有了整个来源,这很长,我们可以将它与median.default中的R中值来源进行比较。使用源代码,我们可以将其复制为用户定义的函数并对其进行概要分析(包含为format_perc提供命名空间的一小部分),从中可以看出只有两行是相关的,即排序和输出格式化,排序与中值函数非常相似,可能难以改进。然而,可以通过注释来完全跳过格式化。

fast.quant <- function (x, probs = seq(0, 1, 0.25), na.rm = FALSE, names = TRUE, 
                        type = 7, ...) 
{
  if (is.factor(x)) {
    ...

    ...
    if (names && np > 0L) {
      #names(qs) <- stats:::format_perc(probs)
    }
    ...
}

总而言之,这个修复程序将运行时间缩短了一半,但它仍然不是优化的中位数,但很可能在不离开R的情况下很难获得更好的性能。

有可能,甚至可能,data.table中的优化也可用于帮助分位数计算,因为data.table也实现了C中的排序。然而,人们仍然希望利用仅需要部分分类。否则,也可以使用Rcpp包来执行类似的优化。