#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