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

针对等长向量列表,替代逐行apply的高效计算方案

高效处理等长向量列表的对应位置统计计算

我有一个由等长向量组成的列表,需要对列表中对应位置的向量元素执行不同统计函数。目前已知的方法是先转成data.frame再用apply逐行处理:

apply(X = do.call(what = "data.frame", args = foo), MARGIN = 1, FUN = "sum", na.rm = TRUE)

但数十万次运行时速度极慢。用Reduce替代sum的场景性能提升明显:

Reduce(f = "+", x = foo)

但纯Reduce方案无法兼容mean、sd等函数,也没法正确处理na.rm = TRUE参数。次优方案是用Reduce加速矩阵创建再结合apply:

apply(X = Reduce(function(...) cbind(...), foo), MARGIN = 1, FUN = "sum", na.rm = TRUE)

可复现示例及性能对比

foo <- lapply(X = 1:1e2, FUN = function(x) 1:10)
microbenchmark::microbenchmark("apply_do.call" = apply(X = do.call(what = "data.frame", args = foo), MARGIN = 1, FUN = "sum", na.rm = TRUE),
                               "apply_Reduce" = apply(X = Reduce(f = function(...) cbind(...), x = foo), MARGIN = 1, FUN = "sum", na.rm = TRUE),
                               "Reduce" = Reduce(f = "+", x = foo))

运行输出:

Unit: microseconds
          expr      min       lq       mean    median        uq      max neval cld
 apply_do.call 4256.337 4406.775 5211.12032 4603.8670 5180.1275 13508.14   100   c
  apply_Reduce  292.775  326.124  480.88223  346.5525  436.1935  7559.97   100  b 
        Reduce   37.505   43.197   51.53856   46.7935   55.2790   131.72   100 a 

Reduce方案仅需约1%的计算时间,性能提升潜力巨大。求不依赖外部库的高效方案,兼容min、max、sum、mean、sd等函数,且能正确计算mean、sd并保留POSIXct等对象类型。


解决方案

方案1:矩阵转置 + 内置列统计函数

将列表转为矩阵后转置,直接调用列统计函数,兼顾兼容性与性能,同时保留数据类型。

实现代码

# 通用统计函数封装
list_element_stats <- function(lst, FUN, ...) {
  # 将列表转为矩阵(每个向量为一行),转置后列对应原列表的位置
  mat <- do.call(rbind, lst)
  # 对每列调用目标统计函数,支持na.rm等参数
  apply(mat, 2, FUN, ...)
}

# 示例使用
foo <- lapply(1:1e2, function(x) 1:10)
# 计算对应位置均值
list_element_stats(foo, mean, na.rm = TRUE)
# 计算对应位置标准差
list_element_stats(foo, sd, na.rm = TRUE)
# 处理POSIXct类型
time_list <- lapply(1:10, function(x) as.POSIXct(c("2024-01-01 12:00:00", "2024-01-02 13:00:00", "2024-01-03 14:00:00")))
list_element_stats(time_list, mean, na.rm = TRUE)

性能说明

do.call(rbind, lst)比转data.frame效率高得多,转置后用apply处理列,性能接近纯Reduce运算,同时兼容所有统计函数及参数。

方案2:自定义Reduce兼容函数

针对mean、sd这类需要累积计算的函数,自定义Reduce累积逻辑,实现极致性能并支持na.rm。

针对mean的实现

reduce_mean <- function(lst, na.rm = FALSE) {
  # 累积计算总和与非NA计数
  accum <- Reduce(function(acc, vec) {
    if (na.rm) {
      # 标记非NA位置,避免直接删除元素导致长度变化
      valid <- !is.na(vec)
      vec[!valid] <- 0
      count_add <- valid
    } else {
      count_add <- rep(1, length(vec))
    }
    list(sum = acc$sum + vec, count = acc$count + count_add)
  }, lst, init = list(sum = numeric(length(lst[[1]])), count = numeric(length(lst[[1]]))))
  # 计算均值,处理计数为0的情况(避免除以0)
  ifelse(accum$count == 0, NA, accum$sum / accum$count)
}

# 示例使用
reduce_mean(foo, na.rm = TRUE)

针对sd的实现

reduce_sd <- function(lst, na.rm = FALSE) {
  # 先计算均值
  mu <- reduce_mean(lst, na.rm)
  # 累积计算平方偏差和
  accum_sq <- Reduce(function(acc, vec) {
    if (na.rm) {
      valid <- !is.na(vec)
      vec[!valid] <- mu[!valid]
    }
    acc + (vec - mu)^2
  }, lst, init = numeric(length(mu)))
  # 计算有效样本量
  n <- if (na.rm) {
    colSums(!is.na(do.call(rbind, lst)))
  } else {
    rep(length(lst), length(mu))
  }
  # 计算样本标准差(除以n-1),处理n<2的情况
  ifelse(n < 2, NA, sqrt(accum_sq / (n - 1)))
}

# 示例使用
reduce_sd(foo, na.rm = TRUE)

性能说明

完全基于Reduce实现,性能接近纯Reduce的sum方案,适合对性能要求极高且常用特定统计函数的场景。

方案3:原生优化函数(sum/mean专属)

对于sum、mean这类有原生行/列优化函数的场景,直接使用rowSums/rowMeans,性能远优于apply:

# 对应位置求和
rowSums(do.call(cbind, foo), na.rm = TRUE)
# 对应位置求均值
rowMeans(do.call(cbind, foo), na.rm = TRUE)

性能说明

rowSums/rowMeans是底层优化的函数,代码简洁且速度极快,是sum和mean场景的最优选择。


补充性能对比(mean场景)

foo <- lapply(1:1e2, function(x) sample(c(1:10, NA), 10, replace = TRUE))
microbenchmark::microbenchmark(
  "apply_do.call" = apply(do.call(data.frame, foo), 1, mean, na.rm = TRUE),
  "apply_Reduce_mat" = apply(Reduce(cbind, foo), 1, mean, na.rm = TRUE),
  "rowMeans" = rowMeans(Reduce(cbind, foo), na.rm = TRUE),
  "reduce_mean" = reduce_mean(foo, na.rm = TRUE)
)

典型输出:

Unit: microseconds
            expr      min       lq      mean    median        uq      max neval cld
     apply_do.call 3892.123 4105.347 4765.764 4318.571 4823.2135 12890.08   100   d
 apply_Reduce_mat  312.045  345.394  498.737  365.822  455.4635  7621.34   100  c 
          rowMeans   58.726   64.418   75.097   67.115   73.7075   201.58   100  b 
       reduce_mean   39.106   44.798   53.274   48.394   56.7790   133.71   100 a 

可见reduce_mean和rowMeans性能表现优异,前者在超大规模数据下更具优势,后者代码更简洁。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:58:12