背景
我可以使用data.table(见附件)从我的数据中获取多个时刻,但这需要很长时间。我认为对表进行排序以获得特定百分位数的过程对于找到几个更有效。
像中位数这样的一次性统计数据占1.79 ms,而非中位数分位数在122.8 ms时延长68倍。必须有一种方法来缩短计算时间。
问题:
我的示例代码包含很少的合成数据:
#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
更新:
思考:
答案 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
包来执行类似的优化。