在data.table嵌套子集中如何访问外层.SD对象?
问题描述
基础语法疑问
现有如下data.table数据:
dt <- data.table(a = c(1,2,3), b = c(4,5,6), d = c(7,8,9))
希望通过dt[, .SD[<something>], by = a]完成第一次子集化,再在该子集的.SD内部执行二次子集化:dt[, .SD[.SD[<something else>], by = b], by = a]。但二次子集化的<something else>逻辑需要访问第一次子集化的外层.SD对象,请问这是否符合data.table的语法规则?
实际应用场景:均方位移(MSD)计算
假设有一个数据集,AgentID用于标识粒子,displacement和Time记录粒子随时间的位移。需求是针对每个AgentID和Time,计算所有t ≥ Time时displacement的cumsum。
示例数据:
dt <- data.table(Time = c(1,2,3), AgentID = c(1), displacement = c(-2,4,-6))
常规写法如下:
DT <- dt[, .(msd = cumsum(dt[Time >= .BY$Time & AgentID == .BY$AgentID]$displacement)), by = .(Time, AgentID)] DT
输出结果:
Time AgentID msd 1: 1 1 -2 2: 1 1 2 3: 1 1 -4 4: 2 1 4 5: 2 1 -2 6: 3 1 -6
但这种在dt内部重复调用原表的写法存在性能、可读性等弊端,希望改用嵌套子集的方式(如dt[.SD[.SD[<计算msd>], by = Time], by = AgentID]),但需要在最内层.SD中访问外层的.SD对象。
解答
语法合法性说明
data.table中,嵌套的.SD无法直接访问外层的.SD对象——每一层by分组都会生成当前组专属的.SD,内层.SD的作用域仅限定在当前内层分组,无法穿透到外层分组的.SD。因此你设想的嵌套写法不符合data.table的语法预期,无法实现外层.SD的访问。
均方位移计算的优化写法
针对MSD计算场景,无需嵌套.SD,可以利用data.table的分组特性、自连接或反向累积来实现,避免原表重复调用的弊端:
方法1:自连接+分组累积
# 按AgentID分组自连接,保留Time >= 当前Time的所有行 dt_msd <- dt[dt, on = .(AgentID, Time >= Time), allow.cartesian = TRUE] # 按AgentID和原Time分组,计算displacement的累积和 dt_msd <- dt_msd[, .(msd = cumsum(i.displacement)), by = .(AgentID, Time)] print(dt_msd)
该写法通过自连接一次性获取所有符合t ≥ Time的行,再分组计算累积和,避免了原表的重复索引,性能和可读性更优。
方法2:分组内反向累积(大样本友好)
如果数据量较大,自连接可能产生过多中间行,此时可以在每个AgentID分组内,对Time排序后用反向累积再反转的方式实现:
dt_msd <- dt[order(AgentID, Time), .(msd = rev(cumsum(rev(displacement)))[1:.N]), by = .(AgentID, Time)] print(dt_msd)
原理是对每个粒子的displacement序列反向后做cumsum,再反转结果,这样每个位置对应的就是从当前Time到序列末尾的累积和序列,无需自连接,效率更高。
内容的提问来源于stack exchange,提问作者Our

