R data.table中j与by参数引用同列时结果异常的原因
问题复现
通过以下代码可复现聚合结果不符合预期的问题:
library(data.table) test <- setDT(data.frame(a=c(1,2,2,1,3,3,3,4,4,4),b=c(1:9,9))) # 执行带行筛选的分组聚合 test[a == 4,sum(b),b]
代码运行输出如下:
b V1 1: 8 8 2: 9 9
单独执行行筛选查看原始子集,可见a=4的记录共3条,其中b=9的记录有2条,b列求和应为18,和聚合输出矛盾:
test[a == 4]
a b 1: 4 8 2: 4 9 3: 4 9
异常结果的产生逻辑
这个结果是data.table求值时的变量遮蔽规则导致的,不是计算bug:
- 上述代码中,第三个位置传入的
b被识别为分组变量,整个语句的意图是先筛选a=4的行,再按b列分组,对每组的b列求和。 - data.table在对分组后的j表达式求值时,会优先将分组变量名绑定到当前组的分组值(长度为1的标量),再匹配数据子集的列。
- 本案例中j表达式里求和的对象
b和分组变量b完全同名,因此sum(b)取到的是每个组的单值分组标量,不是分组内所有行的b列向量:- b=8的分组,分组标量值为8,求和结果为8,和预期值巧合一致
- b=9的分组,分组标量值为9,求和结果为9,和预期的18不符
- 输出只返回b=8、b=9两个分组,也证明行筛选逻辑是正常生效的,问题仅出在j表达式的列引用被分组变量遮蔽。平时使用
test[, sum(b), by=a]这类语句不会出问题,是因为分组变量名是a,和j中引用的b不同名,不会触发遮蔽。
正确写法
只要明确j部分的返回格式,或者显式引用数据列即可避免这个问题:
# 写法1:j部分用.()包裹聚合表达式,明确返回数据框格式,避免列解析异常 test[a == 4, .(V1 = sum(b)), by = b] # 写法2:通过.SD明确引用子集内的数据列,彻底规避同名遮蔽 test[a == 4, sum(.SD$b), by = b]
以上两种写法都会返回符合预期的结果:
b V1 1: 8 8 2: 9 18
内容的提问来源于stack exchange,提问作者ghx12
相关产品推荐
相关产品推荐

