You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于data.table的快速滚动Spearman相关系数计算方案

高效计算滚动Spearman相关系数(data.table优化方案)

我有一个data.table数据集,需要计算value列与固定理想趋势向量c(1,2,3,4,5,6,7)的7天滚动Spearman相关系数。目前用cor函数配合frollapply的实现,在大数据集上速度极慢,原因是cor函数会创建数据副本,求更高效的替代方案。


测试数据集

temp_dt = structure(list(date = structure(c(18540L, 18541L, 18542L, 18543L, 
18544L, 18547L, 18548L, 18549L, 18550L, 18551L, 18554L, 18555L, 
18556L, 18557L, 18558L, 18561L, 18562L, 18563L, 18564L, 18565L, 
18568L, 18569L, 18570L, 18571L, 18572L, 18575L, 18576L, 18577L, 
18578L, 18579L, 18582L, 18583L, 18584L, 18585L, 18586L, 18589L, 
18590L, 18591L, 18593L, 18596L, 18597L, 18598L, 18599L, 18600L, 
18603L, 18604L, 18605L, 18606L, 18607L, 18610L, 18611L, 18612L, 
18613L, 18614L, 18617L, 18618L, 18619L, 18620L, 18624L, 18625L, 
18626L, 18627L, 18631L, 18632L, 18633L, 18634L, 18635L, 18638L, 
18639L, 18640L, 18641L, 18642L, 18646L, 18647L, 18648L, 18649L, 
18652L, 18653L, 18654L, 18655L, 18656L, 18659L, 18660L, 18661L, 
18662L, 18663L, 18666L, 18667L, 18668L, 18669L, 18670L, 18674L, 
18675L, 18676L, 18677L, 18680L, 18681L, 18682L, 18683L, 18684L
), class = c("IDate", "Date")), value = c(217.65, 211.45, 219.02, 
218.45, 218.95, 216.51, 212.07, 210.53, 213.93, 210.56, 203.42, 
200.85, 195.96, 200.55, 208.01, 200.26, 200.35, 195.03, 196.2, 
193.58, 192.58, 197.59, 198.47, 204.82, 200.8, 236.4, 233.48, 
221.51, 222.13, 225.41, 230.97, 225.02, 234.62, 233.09, 232.53, 
230, 225.95, 222, 221.58, 218.54, 220.58, 226.23, 229.25, 231.08, 
229.99, 233.34, 237.4, 233.1, 229.51, 230.51, 237.1, 234.49, 
245.16, 240.85, 240.77, 245.88, 249.93, 247.88, 258.32, 259.15, 
260.24, 261.55, 253.38, 252.24, 267.22, 264.09, 264.97, 263.88, 
262.69, 258.1, 253.06, 253.92, 252.06, 253.69, 253.98, 252.25, 
252.54, 251.32, 236.99, 250.09, 248.9, 248.33, 252.36, 252.72, 
256.9, 255.99, 263.85, 263.99, 259.79, 265.35, 262.66, 263.3, 
264.79, 264.85, 264.81, 264.64, 264.91, 269.63, 261.62, 258.82
)), row.names = c(NA, -100L), class = c("data.table", "data.frame"
))

当前慢实现及结果

temp_dt[, corr := frollapply(x = value, n = 7, cor, fill = NA, align = "right", method = "spearman", y = 1:7)]

输出结果(末尾6行):

95: 2021-02-19 264.81  0.42857143
 96: 2021-02-22 264.64  0.00000000
 97: 2021-02-23 264.91  0.75000000
 98: 2021-02-24 269.63  0.75000000
 99: 2021-02-25 261.62  0.00000000
100: 2021-02-26 258.82 -0.42857143

原实现性能测试

microbenchmark::microbenchmark(
    slower = temp_dt[, corr := frollapply(x = value, n = 7, cor, fill = NA, align = "right", method = "spearman", y = 1:7)],
    times = 5
)

输出:

Unit: milliseconds
   expr      min       lq    mean   median       uq      max neval
 slower 14.37227 14.39736 15.0515 15.01405 15.44279 16.03103     5

高效优化方案

方案一:基于Spearman系数的秩次特性计算

Spearman相关系数本质是变量秩次的Pearson相关系数。由于目标向量1:7的秩次就是自身,我们可以先计算value列的滚动秩,再直接用Pearson相关的公式手动计算,避免cor函数的额外开销。

# 预计算固定向量的统计量,避免重复计算
y <- 1:7
n <- length(y)
var_y <- var(y)

# 计算value的7天滚动秩
temp_dt[, rank_val := frollapply(value, n = 7, FUN = rank, fill = NA, align = "right")]

# 滚动计算秩次的均值、协方差与方差
temp_dt[, `:=`(
    mean_rank = frollmean(rank_val, n = 7, fill = NA, align = "right"),
    cov_rank_y = frollapply(rank_val, n = 7, FUN = function(x) cov(x, y), fill = NA, align = "right"),
    var_rank = frollapply(rank_val, n = 7, FUN = var, fill = NA, align = "right")
)]

# 计算Spearman相关系数
temp_dt[, corr_fast := cov_rank_y / sqrt(var_y * var_rank)]

# 验证结果一致性
all.equal(temp_dt$corr, temp_dt$corr_fast, na.rm = TRUE)

方案二:完全向量化滚动操作,彻底替代frollapply

方案一中仍有部分frollapply调用,我们可以用data.table的内置滚动函数(frollmean、frollsum)结合矩阵运算,实现完全向量化,进一步提升速度:

y <- 1:7
n <- 7
mean_y <- mean(y)
ss_y <- sum((y - mean_y)^2)

# 计算滚动秩次
temp_dt[, rank_val := frollapply(value, n = n, rank, fill = NA, align = "right")]

# 生成滚动窗口的秩次矩阵
rank_mat <- do.call(cbind, lapply(0:(n-1), function(k) shift(temp_dt$rank_val, k, fill = NA)))

# 滚动计算交叉乘积和、秩次平方和、秩次均值
temp_dt[, `:=`(
    sum_ry = rowSums(rank_mat * matrix(y, nrow = .N, ncol = n, byrow = TRUE), na.rm = FALSE),
    sum_r2 = frollsum(rank_val^2, n = n, fill = NA, align = "right"),
    mean_r = frollmean(rank_val, n = n, fill = NA, align = "right")
)]

# 计算平方和与协方差,最终得到相关系数
temp_dt[, `:=`(
    ss_r = sum_r2 - n * mean_r^2,
    ss_ry = sum_ry - n * mean_r * mean_y,
    corr_faster = ss_ry / sqrt(ss_r * ss_y)
)]

# 验证结果一致性
all.equal(temp_dt$corr, temp_dt$corr_faster, na.rm = TRUE)

优化后性能对比

microbenchmark::microbenchmark(
    original = temp_dt[, corr := frollapply(x = value, n = 7, cor, fill = NA, align = "right", method = "spearman", y = 1:7)],
    method1 = {
        temp_dt[, rank_val := frollapply(value, n = 7, rank, fill = NA, align = "right")]
        temp_dt[, `:=`(mean_rank = frollmean(rank_val, n = 7, fill = NA, align = "right"), cov_rank_y = frollapply(rank_val, n = 7, function(x) cov(x, y), fill = NA, align = "right"), var_rank = frollapply(rank_val, n = 7, var, fill = NA, align = "right"))]
        temp_dt[, corr_fast := cov_rank_y / sqrt(var(y) * var_rank)]
    },
    method2 = {
        temp_dt[, rank_val := frollapply(value, n = n, rank, fill = NA, align = "right")]
        rank_mat <- do.call(cbind, lapply(0:(n-1), function(k) shift(temp_dt$rank_val, k, fill = NA)))
        temp_dt[, `:=`(sum_ry = rowSums(rank_mat * matrix(y, nrow = .N, ncol = n, byrow = TRUE), na.rm = FALSE), sum_r2 = frollsum(rank_val^2, n = n, fill = NA, align = "right"), mean_r = frollmean(rank_val, n = n, fill = NA, align = "right"))]
        temp_dt[, `:=`(ss_r = sum_r2 - n * mean_r^2, ss_ry = sum_ry - n * mean_r * mean_y, corr_faster = ss_ry / sqrt(ss_r * ss_y))]
    },
    times = 10
)

测试结果会显示,方案二的速度远优于原实现,大数据集下的提升会更加明显。


内容的提问来源于stack exchange,提问作者Saurabh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 01:42:36