#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))
В этой задаче используется Начальная.Баланс (в текущей строке) от окончательного.Баланс предыдущей строки.
- Когда Значение >= Пороговое, Промежуточное.Баланс=0 и окончательный.Баланс = Начальный.Баланс-Промежуточный.Баланс
- Когда ЗначениеБаланс = Начальный.Баланс и Финал.Баланс = Начальный.Баланс-Промежуточный.Баланс
Я пытался выполнить эту задачу с помощью цикла 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])
}