首页 > 解决方案 > data.table:子集并查找每行的累积乘积

问题描述

我有一个包含三列的简单数据框:ID、日期和值。现在,我想在此过程之后根据这三列计算一个新值 newValue:

  1. 对于每一行(即,对于每一对 (id, date))
  2. 对于范围内的所有日期(日期,日期 + 2),我想找到该 id 值的累积乘积(然后减去 1)

下面带有虚构数字的简单示例进行计算:

df <- data.frame("id"=rep(1:10, 5),
                 "date"=c(rep(2000, 10), rep(2001, 10), rep(2002, 10), rep(2003, 10), rep(2004, 10)),
                 "value"=c(rep(1, 10), rep(2, 10), rep(3, 10), rep(4, 10), rep(5, 10)))

df$newValue <- 1 #initialize

for(idx in 1:dim(df)[1]) {
  id <- df[idx, "id"]
  lower <- df[idx, "date"]
  upper <- lower + 3
  
  df[idx, "newValue"] <- prod(df[(df$id == id) & (df$date >= lower) & (df$date < upper), ]$value + 1) - 1
}

这给了我输出(为简单起见,我对其进行了注释):

   id date value newValue
1   1 2000     1       23 (= (1+1) * (2+1) * (3+1) - 1 = 23)
2   2 2000     1       23 (= (1+1) * (2+1) * (3+1) - 1 = 23)
....
12  2 2001     2       59 (= (2+1) * (3+1) * (4+1) - 1 = 59)
....
22  2 2002     3      119 (= (3+1) * (4+1) * (5+1) - 1 = 119)
....

但是,我的最终数据帧有 +100 万行,因此上面的代码非常耗时且效率低下。

有没有办法加快速度,也许使用 data.table?请注意,每个 id 可能有不同的行数,所以我为什么要明确设置子集。

标签: rdata.table

解决方案


library(data.table)
library(purrr)

setDT(df)[, newValue := map_dbl(date, ~prod(value[between(date, .x, .x + 2)] + 1) - 1), by = id]

给出(仅显示id = 1):

     id date value newValue
 1:  1 2000     1       23
 2:  1 2001     2       59
 3:  1 2002     3      119
 4:  1 2003     4       29
 5:  1 2004     5        5

更新

因为每个date最多一次,id所以这应该更有效:

df <- setDT(df)[order(id, date)]

df[, 
  newValue := map2_dbl(
    date, map(seq_len(.N), ~.x:min(.x+2, .N)), 
    ~prod(value[.y][between(date[.y], .x, .x + 2)] + 1) - 1
  ), 
  by = id
]

如果您想要其他数字2,则可以创建一些变量date_range并替换2date_range


推荐阅读