使用R data.table的which.max时遇缺失值问题排查
which.max踩坑的原因解析 先把你的场景再理一遍哈
你手里是个带时间序列的data.table,需求很明确:按组来拿两种情况的时间戳:
- 要是组里有目标事件(事件列标成
1),就取第一次发生事件的时间 - 要是组里压根没发生事件(事件列全是
NA),就取这个组时间序列的最后一个时间戳
你之前分组调用which.max的时候翻车了,现在已经有解决方案,但就想搞明白当初为啥会失败,对吧?
你踩坑的核心原因:which.max的“隐形坑”
先给你唠唠which.max的脾气:它本来是返回第一个最大值的位置,但如果输入全是NA,它会返回一个长度为0的空向量integer(0)。
而data.table的分组计算有个要求:每个分组返回的结果长度得一致(要么是1个值,要么和分组里的行数一样)。但空向量长度是0,直接拿来取timestamp的话,要么会得到空的时间对象,要么直接报错,完全不符合你要取最后一个时间戳的需求。
给你整个模拟数据举例子:
library(data.table) dt <- data.table( group = rep(c("A", "B"), each = 3), timestamp = as.POSIXct(c("2024-01-01", "2024-01-02", "2024-01-03", "2024-01-01", "2024-01-02", "2024-01-03")), event = c(NA, 1, NA, NA, NA, NA) )
假设你当初写的代码是这样:
dt[, .(target_time = timestamp[which.max(event)]), by = group]
那分组B的event全是NA,which.max(event)返回的就是空向量,取timestamp[integer(0)]得到的是空的时间值,而不是你想要的2024-01-03,这就是问题根源!
不是data.table分组的锅,是which.max的特性
别怀疑data.table的分组逻辑哈,它的分组是正常的——问题出在which.max对全NA输入的处理上。它觉得全是NA就没有“最大值”,所以返回空,但这和你想要的“兜底取最后一行”的逻辑不匹配,才导致了不符合预期的结果。
再看你解决方案的合理性
你后来的解决方案肯定是给which.max加了兜底逻辑,比如用coalesce或者ifelse结合.N(分组的总行数,也就是最后一行的位置):
比如这种写法就很靠谱:
dt[, .(target_time = timestamp[coalesce(which.max(event), .N)]), by = group]
这里coalesce会自动判断:如果which.max(event)是空的,就用.N来替代,完美覆盖了两种场景——有事件时取第一个事件的时间,没事件时取最后一个时间戳,刚好命中你的需求。
内容的提问来源于stack exchange,提问作者snooze_bear

