R фрейм данных использует значения в текущей строке из предыдущей строки

#r #data.table

Вопрос:

У меня есть фрейм данных в 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))
 

В этой задаче используется Начальная.Баланс (в текущей строке) от окончательного.Баланс предыдущей строки.

  1. Когда Значение >= Пороговое, Промежуточное.Баланс=0 и окончательный.Баланс = Начальный.Баланс-Промежуточный.Баланс
  2. Когда ЗначениеБаланс = Начальный.Баланс и Финал.Баланс = Начальный.Баланс-Промежуточный.Баланс

Я пытался выполнить эту задачу с помощью цикла for, но это занимает много времени для большого набора данных (для многих идентификаторов).

Вот мое решение:

 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] }
}
 

Можем ли мы найти аналогичное решение, используя таблицу данных? Поскольку операции с таблицей данных выполняются быстрее, чем для цикла в фрейме данных, я считаю, что это поможет мне сэкономить время вычислений.

Спасибо,

Ответ №1:

Я думаю, что в данном конкретном случае окончательный баланс становится равным 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:

Я не вижу очевидного способа избавиться от цикла, так как каждая строка детерминирована в следующей. При этом данные.кадры копируют весь кадр или, по крайней мере, целые столбцы всякий раз, когда вы устанавливаете какую-то их часть. Таким образом, вы можете сделать это:

 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])
}