R语言:基于相对子集计算为data.table添加新列
解决data.table中按组基于相对子集计算新列的问题
看起来你想给data.table按userId分组,为每一行计算基于该用户当前行之前的子集数据的统计值对吧?这在data.table里用分组操作就能轻松实现,我结合你的示例数据给你演示几种常见的场景:
示例数据回顾
先把你的示例数据完整列出来方便参考:
library(data.table) DT <- data.table( userId = c("user1", "user1", "user1", "user1", "user2", "user2", "user2", "user2", "user2"), topicId = rep("topic1", 9), attempt = c(1, 2, 3, 4, 1, 2, 3, 4, 5), score = c(50, 0, 50, 20, 20, 100, 100, 100, 100) )
输出的数据集:
userId topicId attempt score 1: user1 topic1 1 50 2: user1 topic1 2 0 3: user1 topic1 3 50 4: user1 topic1 4 20 5: user2 topic1 1 20 6: user2 topic1 2 100 7: user2 topic1 3 100 8: user2 topic1 4 100 9: user2 topic1 5 100
场景1:计算当前尝试之前的最高得分
比如想知道每个用户在当前这一次尝试之前,历史上拿到的最高分,代码如下:
DT[, prev_max_score := c(NA, cummax(score)[-.N]), by = userId]
解释一下:
by = userId:按用户分组处理cummax(score):计算累计最大值,会得到从第一个attempt到当前的最高分[-.N]:去掉每组的最后一个值,这样就得到了“到上一次尝试为止”的最大值c(NA, ...):给每组的第一行(第一次尝试)补NA,因为第一次尝试没有之前的历史
运行后结果:
userId topicId attempt score prev_max_score 1: user1 topic1 1 50 NA 2: user1 topic1 2 0 50 3: user1 topic1 3 50 50 4: user1 topic1 4 20 50 5: user2 topic1 1 20 NA 6: user2 topic1 2 100 20 7: user2 topic1 3 100 100 8: user2 topic1 4 100 100 9: user2 topic1 5 100 100
场景2:计算当前尝试之前的平均分
如果想算之前所有尝试的平均分,用类似的逻辑:
DT[, prev_avg_score := c(NA, (cumsum(score) - score)/seq_len(.N-1)), by = userId]
解释:
cumsum(score) - score:得到当前行之前所有分数的总和seq_len(.N-1):生成1到N-1的序列,作为分母(之前尝试的次数)- 同样第一行补NA
结果:
userId topicId attempt score prev_max_score prev_avg_score 1: user1 topic1 1 50 NA NA 2: user1 topic1 2 0 50 50.0 3: user1 topic1 3 50 50 25.0 4: user1 topic1 4 20 50 33.33333 5: user2 topic1 1 20 NA NA 6: user2 topic1 2 100 20 20.0 7: user2 topic1 3 100 100 60.0 8: user2 topic1 4 100 100 73.33333 9: user2 topic1 5 100 100 80.0
场景3:标记是否首次获得满分(假设满分100)
如果想标记当前行是否是该用户第一次拿到满分:
DT[, first_perfect := (score == 100) & (cumsum(score == 100) == 1), by = userId]
解释:
score == 100:判断当前分数是否是满分cumsum(score == 100):累计当前用户拿到满分的次数- 两者同时满足时,就是第一次拿到满分的行
结果:
userId topicId attempt score prev_max_score prev_avg_score first_perfect 1: user1 topic1 1 50 NA NA FALSE 2: user1 topic1 2 0 50 50.0 FALSE 3: user1 topic1 3 50 50 25.0 FALSE 4: user1 topic1 4 20 50 33.33333 FALSE 5: user2 topic1 1 20 NA NA FALSE 6: user2 topic1 2 100 20 20.0 TRUE 7: user2 topic1 3 100 100 60.0 FALSE 8: user2 topic1 4 100 100 73.33333 FALSE 9: user2 topic1 5 100 100 80.0 FALSE
通用思路
其实核心就是利用data.table的分组(by参数),结合累计函数(cumsum、cummax、cummin等)或者手动截取子集(比如用.I索引)来处理“当前行之前的子集”。如果你的需求是更复杂的自定义计算,还可以用shift()函数,比如:
# 用shift获取上一次尝试的分数 DT[, last_score := shift(score), by = userId]
shift()函数可以轻松获取组内的前n行数据,非常灵活。
内容的提问来源于stack exchange,提问作者U W
相关产品推荐
相关产品推荐

