基于data.table按组向前填充缺失值的高效实现需求
高效按组前向填充缺失值的data.table解决方案
我明白你在处理大数据量时遇到的性能折磨——tidyr的fill()在350万行、200万组的规模下耗时6小时确实太不友好了。咱们直接用data.table的原生高效方法来解决这个问题!
先回顾你的示例数据:
library(data.table) DT <- structure(list(CLASS = c("A", "A", "A", "A", "A", "A", "B", "B","B"), VAL = c(NA, 1, NA, NA, 2, NA, 50, NA, 100)), .Names = c("CLASS", "VAL"), row.names = c(NA, -9L), class = c("data.table", "data.frame"))
你提到的两种错误方法的问题很典型:
- 把组内首个非缺失值赋值给所有行:这不是前向填充,而是直接用第一个有效值覆盖全组,不符合逐行继承上一个有效值的需求
- 仅过滤缺失值行赋值:缺失值行本身没有有效值,无法关联到组内的上一个非缺失值,所以完全起不到填充作用
最优data.table解决方案(高效且简洁)
从data.table 1.12.0版本开始,内置了nafill()函数,专门用来处理缺失值填充,其中type="locf"就是实现**Last Observation Carried Forward(前向填充)**的参数,结合按组操作,性能拉满:
# 按CLASS组前向填充VAL的缺失值 DT[, VAL := nafill(VAL, type = "locf"), by = CLASS]
执行后你会得到期望的结果:
> DT CLASS VAL 1: A NA 2: A 1 3: A 1 4: A 1 5: A 2 6: A 2 7: B 50 8: B 50 9: B 100
为什么这个方法更快?
data.table的分组操作和nafill()都是基于底层C语言实现的,完全向量化处理,没有R层面的循环开销,对于百万级别的数据,速度会比tidyr的fill()快几个数量级,应该能把你的处理时间从小时级压缩到分钟甚至秒级。
兼容旧版本data.table的写法
如果你的data.table版本低于1.12.0,可以用以下替代方案,同样是高效的分组处理:
DT[, VAL := { # 找到组内所有非缺失值的位置 non_na_idx <- which(!is.na(VAL)) if (length(non_na_idx) == 0) { # 组内全是NA,保持原样 VAL } else { # 用findInterval匹配每行对应的上一个非缺失值位置 VAL[non_na_idx[findInterval(seq_len(.N), non_na_idx)]] } }, by = CLASS]
内容的提问来源于stack exchange,提问作者Mihail
相关产品推荐
相关产品推荐

