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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:49:16