data.table按组运算使用变量列名时如何避免get()导致的性能下降
问题解答
1. 双点语法的适用性
双点语法完全适用于该场景,你之前报错是因为使用姿势错误:双点前缀的作用是从父环境取出变量的值,你直接写mean(..colName)等价于mean("x"),当然无法得到正确结果。
正确的双点用法参考:
# 方法A:结合.SD取列 get(dataName)[, mean(.SD[[..colName]]), by = grp] # 方法B:配合官方推荐的env参数(data.table 1.14.2及以上版本支持,性能和静态写法完全一致) get(dataName)[, mean(target_col), by = grp, env = list(target_col = colName)]
这两种写法的性能都和静态列名写法处于同一水平。
2. 第4种写法性能极差的原因
你构造的cl表达式存在冗余的重解析逻辑:
你写的substitute(mean(eval(parse(text = colName))), list(colName = as.name(colName)))最终生成的表达式是mean(eval(parse(text = x))),每个分组计算时都会重复执行parse+eval操作,parse本身是开销极高的函数,1e5个分组就会执行1e5次重解析,所以性能出现数量级的下降。
你之前几个慢写法的核心原因都是:j位置的逻辑没有提前被解析为静态表达式,会在每个分组内重复执行,产生了大量冗余开销。
最优实现方案
推荐你用表达式预构造+单次eval的方案,兼容所有主流data.table版本,性能和静态写法完全一致:
FOO <- function(dataName = "d", colName = "x"){ dt <- get(dataName) # 预构造j位置的计算表达式,仅执行一次 j_expr <- substitute(mean(col), list(col = as.name(colName))) # j位置eval顶层表达式仅执行一次解析,不会每个分组重复计算 dt[, eval(j_expr), by = grp] }
该方案的性能测试结果和静态写法t1基本持平,不会出现分组重复求值的开销。
内容的提问来源于stack exchange,提问作者Samuel Allain
相关产品推荐
相关产品推荐

