现实问题概述
本质上,这是一个线性方程组的情景评估。 我有两个数据表。
- s_dt包含每个观察到的情景(o)的情景、驱动程序(d)和值(v)。
- c_dt包含一系列适用于多个拟合模型基础(b)的项(n)。
驱动程序的各个幂以及相关系数被编码为名称-值对(d和t)。
每个基础(b)本质上都是一个具有n个术语的多项式。
问题
下面的重现案例提供了所需的输出格式。 但对于所需的用例来说太慢了,即使在简化的问题上也是如此。 数字是垃圾,但我不能分享实际数据。 在真实数据上运行需要类似的时间。
在我的系统上(12个线程),“lil”问题大约需要3秒钟。
但“big”问题要大4000倍。 因此预计需要大约3小时。 痛苦!
目标是使“big”问题在子5分钟内运行(或者理想情况下更快!)
那么,厉害的聪明人们,如何让这个过程变得更快?
(而减速的根本原因是什么?)
如果基于base/tidyverse的解决方案能够满足性能需求,我也很乐意接受。我只是认为对于这个问题的规模,data.table是最好的选择。
当前解决方案
在s_dt上运行fun
,按o进行分组。
fun
:将c_dt与每个组数据连接,以填充v,从而使计算每个多项式方程的结果r成为可能。
用data.table
的说法:
s_dt[, fun(.SD), keyby = .(o)]
复现案例
- 创建两个数据表,其组合和字段类型与实际问题相匹配。
但为了说明目的而缩小了规模。 - 定义
fun
,然后运行以填充所有场景的r。
library(data.table)
# problem sizing ----
dims <- list(o = 50000, d = 50, b = 250, n = 200) # "big" problem - real-life size
dims <- list(o = 100, d = 50, b = 25, n = 200) # "lil" problem (make runtime shorter as example)
# build some test data tables ----
build_s <- function() {
o <- seq_len(dims$o)
d <- paste0("d",seq_len(dims$d))
v <- as.double(seq_len(dims$o * dims$d))/10000
CJ(o, d)[, `:=`(v = v)]
}
s_dt <- build_s()
build_c <- function() {
b <- paste0("c", seq_len(dims$b))
n <- seq_len(dims$n)
d <- c("c", paste0("d", seq_len(dims$d)))
t <- as.double(rep_len(0:6, dims$b * dims$n * (dims$d+1)))
dt <- CJ(d, b, n)[, `:=`(t = t)]
dt <- dt[t != 0]
}
c_dt <- build_c()
# define fun and evaluate ----
# (this is what needs optimising)
profvis::profvis({
fun <- function(dt) {
# don't use chaining here, for more useful profvis output
dt <- dt[c_dt, on = .(d)]
dt <- dt[, r := fcase(d == "c", t,
is.na(v), 0,
rep(TRUE, .N), v^t)]
dt <- dt[, .(r = prod(r)), keyby = .(b, n)]
dt <- dt[, .(r = sum(r)), keyby = .(b)]
}
res <- s_dt[, fun(.SD), keyby = .(o)]
})
示例输入和输出
> res
o b r
1: 1 c1 0.000000e+00
2: 1 c10 0.000000e+00
3: 1 c11 0.000000e+00
4: 1 c12 0.000000e+00
5: 1 c13 0.000000e+00
---
2496: 100 c5 6.836792e-43
2497: 100 c6 6.629646e-43
2498: 100 c7 6.840915e-43
2499: 100 c8 6.624668e-43
2500: 100 c9 6.842608e-43
> s_dt
o d v
1: 1 d1 0.0001
2: 1 d10 0.0002
3: 1 d11 0.0003
4: 1 d12 0.0004
5: 1 d13 0.0005
---
4996: 100 d50 0.4996
4997: 100 d6 0.4997
4998: 100 d7 0.4998
4999: 100 d8 0.4999
5000: 100 d9 0.5000
> c_dt
d b n t
1: c c1 2 1
2: c c1 3 2
3: c c1 4 3
4: c c1 5 4
5: c c1 6 5
---
218567: d9 c9 195 5
218568: d9 c9 196 6
218569: d9 c9 198 1
218570: d9 c9 199 2
218571: d9 c9 200 3
collapse
,因为它可以提高性能。另一个选择是使用基于 Rust 的 Python Polars 进行分组,速度很快。 - akrunprofvis
调用中的部分,对吗?前两部分只是数据创建? - Colet!=0
。我们能事先做到这一点吗?由于它是在c_dt
中定义的,这意味着我们可以提前过滤,这样我们就不必每次都进行过滤了。在较小的示例中,该过滤占用了总时间的相当大一部分,因此这种优化非常重要。 - ColeCJ
制作?还是仅用于说明? - Cole