Вычислить значение, используя строку выше в data.frame и другой столбец

#r #dplyr

#r #dplyr

Вопрос:

Привет, я хочу сделать что-то в R, что кажется, что это должно быть просто, но у меня, кажется, замирает мозг.

Для каждой строки в data.frame я хочу получить значение Vol в строке выше, добавить значение In для этой строки и вычесть значение, зависящее от этого значения.

Вот моя попытка, но задержка заключается в том, что я просто просматриваю начальные значения на одну строку назад, не продолжая просмотр после вычисления следующего значения

 library(dplyr)
df <- data.frame(In = c(1,4,0,0,1,2,3,0,0), Vol = c(1,rep(NA,8)))

df %>% mutate(Vol = (lag(Vol)   In) -  (lag(Vol)   In)*0.01)
  

желаемый результат =

   In     Vol
1  1  1.00
2  4  4.95
3  0  4.90
4  0  4.85
5  1  5.79
6  2  7.72
7  3 10.61
8  0 10.50
9  0 10.40
  

Комментарии:

1. Было бы полезно, если бы вы предоставили образец test и ожидаемый результат из этого образца данных.

2. Упс, хорошая мысль @r2evans — теперь обновлено.

Ответ №1:

Вот решение, использующее accumulate из purrr пакета. accumulate Функция может применить функцию с двумя аргументами, такими как x и y , к последовательности векторов. Возвращаемое значение станет входным значением следующего раунда.

В следующем примере я попросил accumulate функцию начинать со второго номера In столбца до конца. Я также предоставил 1 для .init аргумента, который будет первым x для функции.

 library(dplyr)
library(purrr)

df <- data.frame(In = c(1,4,0,0,1,2,3,0,0), Vol = c(1,rep(NA,8)))

df %>% 
  mutate(Vol = accumulate(In[2:n()], function(x, y) (x   y) * 0.99, .init = 1))
#   In       Vol
# 1  1  1.000000
# 2  4  4.950000
# 3  0  4.900500
# 4  0  4.851495
# 5  1  5.792980
# 6  2  7.715050
# 7  3 10.607900
# 8  0 10.501821
# 9  0 10.396803
  

Кроме того, похоже, что первое значение из Vol столбца совпадает с первым значением In столбца. Если то, что вы пытаетесь сделать, это accumulate обработать просто In столбец, следующий код будет более кратким, и вам даже не нужно копировать первое значение в Vol столбец.

 df %>% 
  mutate(Vol = accumulate(In, function(x, y) (x   y) * 0.99))
#   In       Vol
# 1  1  1.000000
# 2  4  4.950000
# 3  0  4.900500
# 4  0  4.851495
# 5  1  5.792980
# 6  2  7.715050
# 7  3 10.607900
# 8  0 10.501821
# 9  0 10.396803
  

Комментарии:

1. Кажется, что это становится действительно близко к stats::filter функции — filter(df$In, c(1,-0.01), sides=1, method="rec") , например.

2. @thelatemail Спасибо, что поделились. Результат вашего кода является 1.00000 5.00000 4.99000 4.94000 5.89010 7.84070 10.78180 10.70339 10.59557 , который отличается от ожидаемого результата от OP.

3. Я знаю, именно поэтому я сказал » приближаюсь к … » — я подумал, что это может быть полезно при попытке преобразовать это в векторизованную функцию.

4. О, я думаю, это то, что я хочу, я посмотрю, как это сочетается с моими реальными данными, и посмотрю, не упускаю ли я чего-нибудь. Просто совпадение, что In и первое значение Vol были одинаковыми

5. @www — конечно, базовая R-версия вашего ответа (которая на самом деле представляет собой просто набор скрытых циклов), это Reduce(function(x, y) (x y) * 0.99, df$In, accumulate=TRUE)

Ответ №2:

Вы также можете сделать это, используя sapply from base R для замены for цикла @Ronak. invisible это не необходимая функция, а просто обернутая вокруг sapply , чтобы заставить ее работать бесшумно.

 invisible(
  sapply(2:nrow(df), function(i) {
    df$Vol[i] <<- (df$Vol[i-1]   df$In[i]) -  (df$Vol[i-1]   df$In[i])*0.01
  }
         )
)

> df
  In       Vol
1  1  1.000000
2  4  4.950000
3  0  4.900500
4  0  4.851495
5  1  5.792980
6  2  7.715050
7  3 10.607900
8  0 10.501821
9  0 10.396803
  

микробенчмарка:

 Unit: microseconds
            expr      min        lq      mean    median        uq       max neval
           tidy1  578.614  602.3825  736.8518  647.7345  792.1560  3409.963   100
           tidy2  566.256  601.1450 1524.3789  646.5240  801.3490 80219.732   100
        for.loop 4936.829 5288.2650 6007.9584 5635.4895 6540.4290  8982.346   100
          sapply  198.919  218.8710  305.8182  226.3600  243.1750  4489.870   100
 trans.db.reduce  127.456  149.8150  175.4649  172.6280  195.9935   292.835   100
        trans.db  217.416  236.1150  328.3348  255.2275  285.5560  5805.963   100
  

Комментарии:

1. Вам следует повторить свой тест и включить решение @d.b, оно самое быстрое.

2. @jay.sf, похоже, функция @d.b с Reduce работает быстрее всего.

Ответ №3:

Получение значений из предыдущей строки и обновление значения в текущей строке кажется тривиальной задачей. Однако mutate не имеет «знаний» о предыдущем Vol вычисленном им значении, поскольку оно вычисляет значение всего столбца вместе.

В таких случаях мы можем использовать простой for цикл

 for (i in 2:nrow(df)) {
   df$Vol[i] = (df$Vol[i-1]   df$In[i]) -  (df$Vol[i-1]   df$In[i])*0.01
}

df
#  In       Vol
#1  1  1.000000
#2  4  4.950000
#3  0  4.900500
#4  0  4.851495
#5  1  5.792980
#6  2  7.715050
#7  3 10.607900
#8  0 10.501821
#9  0 10.396803
  

данные

 test = c(1, 4, 0, 0, 1, 2, 3, 0, 0)
df <- data.frame(In = test, Vol = c(1,rep(NA,8)))
  

Комментарии:

1. Хм, спасибо за ваш ответ, я надеялся избежать цикла for, поскольку в реальной жизни у меня очень большой объем данных, и они будут сгруппированы по другим столбцам, и я просто обычно стараюсь их избегать. Возможно, у меня нет выбора в этом случае, хотя

2. @user2738526 да, я сам проходил через такие случаи, и меня беспокоит, что я не могу выполнить какие-либо подобные вычисления без for цикла. Вы можете подождать некоторое время и посмотреть, есть ли у кого-нибудь еще лучшее / интеллектуальное решение для этого. Было бы интересно узнать.

Ответ №4:

В этом конкретном случае вы можете использовать некоторые алгебраические манипуляции, чтобы выразить все Vol в терминах первого Vol

 transform(df, Vol = c(df$Vol[1], sapply(2:NROW(df), function(n){
    0.99^(n-1) * df$Vol[1]   sum(0.99^((n-1):1) * df$In[2:n])
})))
#  In       Vol
#1  1  1.000000
#2  4  4.950000
#3  0  4.900500
#4  0  4.851495
#5  1  5.792980
#6  2  7.715050
#7  3 10.607900
#8  0 10.501821
#9  0 10.396803
  

Ответ №5:

Другой вариант с Reduce

 transform(df, 
          Vol = Reduce(function(x, y){
              x   y - 0.01 * (x   y)    
          },
          c(df$Vol[1], df$In[-1]),
          accumulate = TRUE))
#  In       Vol
#1  1  1.000000
#2  4  4.950000
#3  0  4.900500
#4  0  4.851495
#5  1  5.792980
#6  2  7.715050
#7  3 10.607900
#8  0 10.501821
#9  0 10.396803