Eng*_*ica 6 performance r median zoo data.table
背景:
我可以使用 data.table(见附件)从我的数据中获取多个时刻,但这需要很长时间。我在想,对表格进行排序以获得特定百分位数的过程会更有效地找到几个。
像中值这样的一次性统计需要 1.79 毫秒,而非中值分位数需要 68 倍的时间,为 122.8 毫秒。必须有一种方法来减少计算时间。
问题:
我的带有微小合成数据的示例代码:
#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)
Run Code Online (Sandbox Code Playgroud)
对一次通过统计与多次通过统计进行微基准测试表明分位数很昂贵。
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)
Run Code Online (Sandbox Code Playgroud)
表示平均需要大约 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
Run Code Online (Sandbox Code Playgroud)
但计算分位数需要 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
Run Code Online (Sandbox Code Playgroud)
和
> 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
Run Code Online (Sandbox Code Playgroud)
更新:
想法:
小智 4
你是对的,在这种情况下中位数特别快,这是因为它运行专门的 C 代码,与纯 R 代码的分位数函数不同。
data.table我们可以在文档中阅读有关此优化的信息
?data.table.optimize
Run Code Online (Sandbox Code Playgroud)
在那里我们有:
当 j 中的表达式仅包含这些函数 min、max、mean、median、var、sd、prod,例如 dt[、list(mean(x)、median(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)
Run Code Online (Sandbox Code Playgroud)
在我的系统上,R 内部版本比 data.table 版本慢约 44 倍。
我们仍然可以尝试提高quantileR 中函数的速度,为此我的方法基本上是“使用源代码,Luke”并查看分位数函数。查看源代码,我们得到标准通用函数:
>> quantile
function (x, ...)
UseMethod("quantile")
<bytecode: 0x0000000009154c78>
<environment: namespace:stats>
Run Code Online (Sandbox Code Playgroud)
我们可以进一步追踪这一点:
>> methods(quantile)
[1] quantile.default* quantile.ecdf* quantile.POSIXt* quantile.zoo
see '?methods' for accessing help and source code
Run Code Online (Sandbox Code Playgroud)
并查看默认功能。
>> stats:::quantile.default
function (x, probs = seq(0, 1, 0.25), na.rm = FALSE, names = TRUE,
type = 7, ...)
{
...
}
Run Code Online (Sandbox Code Playgroud)
现在我们有了整个源代码,它很长,我们可以将它与 中的 R 中值源进行比较median.default。有了源代码,我们可以将其复制为用户定义的函数并对其进行分析(包含一小部分提供 的命名空间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)
}
...
}
Run Code Online (Sandbox Code Playgroud)
总而言之,这个修复将运行时间减少了一半,它仍然不是优化的中值,但很可能在不离开 R 的情况下很难获得更好的性能。
有可能,甚至很可能,data.table 中的优化也可以用来帮助分位数计算,因为 data.table 也在 C 中实现排序。然而,人们仍然希望利用仅需要部分排序的优势。Rcpp否则,也可以使用 该包来执行类似的优化。