如何用data.table语法高效迭代更新R数据表条目?
问题:data.table高效实现滞后值迭代增长更新
我偏好data.table的语法和内存/速度特性,现在需要按年份滞后取值,用标量乘数迭代更新部分数值,但lapply结合shift的方式无法满足需求——它会一次性处理整个列表,而非逐个元素更新。
最小可复现示例(MWE)
library(data.table) set.seed(355) dt <- data.table(year = c(2020:2022), mean = runif(3, .25,.75), lower = runif(3, 0, .25), upper = runif(3, .75, 1)) dt[year > 2020, `:=`(mean = NA, lower = NA, upper = NA)] gr <- 1.04 dt
执行后输出:
> dt year mean lower upper <int> <num> <num> <num> 1: 2020 0.3325526 0.04151428 0.848173 2: 2021 NA NA NA 3: 2022 NA NA NA
尝试方法的问题
我尝试用lapply结合shift(.SD)实现:
dt[, c("mean", "lower", "upper") := lapply(.SD, function(x){shift(x)*gr}), .SDcols = c("mean", "lower", "upper")]
但该方法基于初始列表处理,初始dt$mean为[1] 0.3325526 NA NA,执行后2020年的值被置为NA,2022年因NA*1.04仍为NA,结果不符合预期:
> dt year mean lower upper <int> <num> <num> <num> 1: 2020 NA NA NA 2: 2021 0.3458548 0.04317485 0.8820999 3: 2022 NA NA NA
用for循环迭代year可以解决,但速度慢,无法发挥data.table的效率优势,求符合data.table语法的高效实现方式。
解决方案
方法1:向量式累积乘积(最优方案)
直接通过计算增长率的累积乘积批量更新,完全符合data.table的向量操作风格,效率最高:
# 计算每一年相对于基准年的增长倍数 dt[, growth_multiplier := gr^(year - min(year))] # 用基准值乘以对应倍数填充所有NA dt[, c("mean", "lower", "upper") := lapply(.SD, function(x) x[!is.na(x)][1] * growth_multiplier), .SDcols = c("mean", "lower", "upper")]
执行结果:
> dt year mean lower upper growth_multiplier <int> <num> <num> <num> <num> 1: 2020 0.3325526 0.04151428 0.8481730 1.00 2: 2021 0.3458547 0.04317485 0.8820999 1.04 3: 2022 0.3596889 0.04490184 0.9173839 1.0816
方法2:向量化迭代填充
如果需要模拟“逐个元素更新”的逻辑,用shift结合条件赋值实现向量化填充,比普通循环高效:
# 重置初始数据 set.seed(355) dt <- data.table(year = c(2020:2022), mean = runif(3, .25,.75), lower = runif(3, 0, .25), upper = runif(3, .75, 1)) dt[year > 2020, `:=`(mean = NA, lower = NA, upper = NA)] gr <- 1.04 # 循环填充直到无NA(每次都是向量化操作) while (any(is.na(dt[, .SD, .SDcols = c("mean", "lower", "upper")]))){ dt[, c("mean", "lower", "upper") := lapply(.SD, function(x) ifelse(is.na(x), shift(x)*gr, x)), .SDcols = c("mean", "lower", "upper")] }
方法3:set函数高效循环
如果必须用循环,data.table的set函数直接修改内存数据,避免拷贝开销,远快于普通for循环:
# 重置初始数据 set.seed(355) dt <- data.table(year = c(2020:2022), mean = runif(3, .25,.75), lower = runif(3, 0, .25), upper = runif(3, .75, 1)) dt[year > 2020, `:=`(mean = NA, lower = NA, upper = NA)] gr <- 1.04 cols <- c("mean", "lower", "upper") for (i in 2:nrow(dt)){ for (col in cols){ set(dt, i, col, dt[[col]][i-1] * gr) } }
内容的提问来源于stack exchange,提问作者BLP92
相关产品推荐
相关产品推荐

