在R中追踪data.table时间序列数据的非零值变化
高效实现data.table分组追踪上一次非零值的时间差
这是个很常见的分组状态追踪需求,用data.table的原生矢量化操作就能高效搞定,完全不用写循环,性能拉满~
实现思路
核心逻辑拆解成三步:
- 按
id分组后,先标记出每个value列非零位置对应的time,其余位置设为NA - 用**向前填充(last observation carried forward)**把NA替换成最近一次非零值的
time - 最后用当前行的
time减去填充后的时间,得到上一次非零到当前的间隔;如果从未出现非零值,就保留NA
完整代码
library(data.table) # 构造原始数据 df = data.table( id = c(rep(1, 3), rep(2, 4), rep(3, 2)), time = c(seq(1, 3, 1), seq(1, 4, 1), seq(3, 4)), value1 = c(0, 0, 0, 0, 2, 0, 0, 0, 1), value2 = c(0, 1, 0, 1, 0, 0, 0, 0, 1) ) # 定义复用函数,避免重复代码 get_last_change = function(val_col, time_col) { # 标记非零值对应的时间 temp_times = fifelse(val_col != 0, time_col, NA_real_) # 向前填充最近的非零时间 filled_times = nafill(temp_times, type = "locf") # 计算当前时间与最近非零时间的差,无则返回NA fifelse(is.na(filled_times), NA_real_, time_col - filled_times) } # 分组计算新增列 df[, `:=`( last_change1 = get_last_change(value1, time), last_change2 = get_last_change(value2, time) ), by = id] # 调整列顺序匹配期望结果 setcolorder(df, c("id", "time", "value1", "last_change1", "value2", "last_change2")) # 查看结果 print(df)
运行结果
id time value1 last_change1 value2 last_change2 1: 1 1 0 NA 0 NA 2: 1 2 0 NA 1 0 3: 1 3 0 NA 0 1 4: 2 1 0 NA 1 0 5: 2 2 2 0 0 1 6: 2 3 0 1 0 2 7: 2 4 0 2 0 3 8: 3 3 0 NA 0 NA 9: 3 4 1 0 1 0
为什么这个方案高效?
- 全程用data.table的矢量化函数(
fifelse、nafill),底层是C优化的,比循环/apply类方法快几个数量级 - 分组操作是data.table的强项,内存使用更高效,适合处理百万级以上的大表
- 封装函数后,新增更多
valueN列时只需一行代码,扩展性极强
内容的提问来源于stack exchange,提问作者mr.bjerre
相关产品推荐
相关产品推荐

