首页 > 解决方案 > R数据框使用上一行中当前行中的值

问题描述

我在 R 中有一个数据框,定义如下:

df <- data.frame('ID'=c(1,1,1,1),
                    'Month' =c('M1','M2','M3','M4'),
                 "Initial.Balance" =c(100,100,100,0),
                    "Value" = c(0.1,0.2,0.2,0.2),
                    "Threshold"=c(0.05,0.18,0.25,0.25),
                    "Intermediate.Balance"=c(0,0,100,0),
                    "Final.Balance"=c(100,100,0,0))

此任务使用上一行的 Final.Balance 中的 Initial.Balance(在当前行中)。

  1. 当 Value >= Threshold 时,Intermediate.Balance=0 且 Final.Balance = Initial.Balance-Intermediate.Balance
  2. 当 Value < Threshold 时,Intermediate.Balance = Initial.Balance 和 Final.Balance = Initial.Balance-Intermediate.Balance

我曾尝试使用 for 循环来完成此任务,但在大型数据集上需要大量时间(对于许多 ID)

这是我的解决方案:

for (i in 1:nrow(df)){
  df$Intermediate.Balance[i] <- ifelse(df$Value[i]>df$Threshold[i],0,df$Initial.balance[i])
  df$Final.Balance[i] <- df$Initial.balance[i]-df$Intermediate.Balance[i]
  if(i+1<=nrow(df)){
  df$Initial.balance[i+1] <- df$Final.Balance[i] }
}

我们可以使用数据表寻找类似的解决方案吗?由于数据表操作比数据帧上的 for 循环更快,我相信这将帮助我节省计算时间。

谢谢,

标签: rdata.table

解决方案


我认为在这种特殊情况下,一旦有一行 Value 小于 Threshold 并且后续余额都变为 0,最终余额就会变为 0。所以你可以使用这个:

ib <- 100
df[, InitBal := ib * 0^shift(cumsum(Value<=Threshold), fill=0L)]
df[, ItmdBal := replace(rep(0, .N), which(Value<=Threshold)[1L], ib)]
df[, FinlBal := InitBal - ItmdBal]

或其中之一[]

df[, c("InitBal", "ItmdBal", "FinlBal") := {
    v <- Value<=Threshold
    InitBal <- ib * 0^shift(cumsum(v), fill=0L)
    ItmdBal <- replace(rep(0, .N), which(v)[1L], ib)
    .(InitBal, ItmdBal, InitBal - ItmdBal)
}]

或者当中间余额不等于初始余额时使用 Rcpp 的更一般的方法:

library(Rcpp)
cppFunction('List calc(NumericVector Value, NumericVector Threshold, double init) {
    int n = Value.size();
    NumericVector InitialBalance(n), IntermediateBalance(n), FinalBalance(n);

    InitialBalance[0] = init;
    for (int i=0; i<n; i++) {
        if (Value[i] <= Threshold[i]) {
            IntermediateBalance[i] = InitialBalance[i];
        } 
        FinalBalance[i] = InitialBalance[i] - IntermediateBalance[i];
        if (i < n-1) {
            InitialBalance[i+1] = FinalBalance[i];
        }
    }
   
    return List::create(Named("InitialBalance") = InitialBalance,
        Named("IntermediateBalance") = IntermediateBalance,
        Named("FinalBalance") = FinalBalance);
}')
setDT(df)[, calc(Value, Threshold, Initial.Balance[1L])]

推荐阅读