【问题标题】:R dataframe uses values in current row from previous rowR数据框使用上一行中当前行中的值
【发布时间】:2021-09-01 13:36:14
【问题描述】:

我在 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

我曾尝试使用 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 循环更快,我相信这将帮助我节省计算时间。

谢谢,

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我认为在这种特殊情况下,一旦有一行 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])]
    

    【讨论】:

      【解决方案2】:

      我看不到摆脱循环的明显方法,因为每一行都是确定性的。话虽如此,只要您设置其中的某些部分,data.frames 就会复制整个框架或至少整个列。因此,您可以这样做:

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

      您也可以尝试set 函数,但我不确定它是否会更快,或者速度快多少,因为无论如何数据都来自data.table

      dt<-as.data.table(df)
      for(i in 1:nrow(dt)) {
        i<-as.integer(i)
        set(dt,i,"Intermediate.Balance", ifelse(dt[i,Value]>dt[i,Threshold],0,dt[i,Initial.Balance]))
        set(dt,i,"Final.Balance", dt[i,Initial.Balance-Intermediate.Balance])
        if(i+1<=nrow(dt)) set(dt,i+1L,"Initial.Balance", dt[i,Final.Balance])
      }
      

      【讨论】:

        猜你喜欢
        • 2016-05-01
        • 2015-01-24
        • 1970-01-01
        • 2017-09-20
        • 2020-01-14
        • 1970-01-01
        • 2019-02-01
        • 1970-01-01
        • 2012-06-19
        相关资源
        最近更新 更多