Как применить Reduce () к группам на основе столбцов фрейма данных?

Несколько дней назад я опубликовал этот вопрос: Как создать динамический столбец в R? (ссылка, чтобы вы могли лучше понять, о чем я спрашиваю), чтобы создать «динамический» столбец, и решение заключалось в использовании функции Reduce (). Теперь я хочу в основном сделать то же самое (рассчитать изменение баланса, взяв за основу предыдущую строку), но с подмножествами фрейма данных, отфильтрованными по определенному столбцу. Короче говоря, я хочу выполнить тот же расчет, но только для групп, поэтому у меня есть значение X в качестве начального капитала для групп A, B и C, и изменение баланса будет "сброшено" до начального капитала для каждая группа.

Я знаю, что приведенное выше объяснение не так ясно, поэтому вот быстрая упрощенная версия того, чего я хотел бы достичь:

class <- c("a", "a", "b", "b", "c", "c")
profit <- c(10, 15, -5, -6, 20, 5)
change <- profit / 1000
balance <- c(1010, 1025, 1020, 1014, 1036, 1039)

data <- data.frame(class, profit, change, balance)

a <- data %>% filter(class == "a")
b <- data %>% filter(class == "b")
c <- data %>% filter(class == "c")
start_capital = 1000

a_bal <- Reduce(function(x, y) x + x*y, a$change, init = start_capital, accumulate = TRUE)[-1]
a <- mutate(a, balance = a_bal, 
               profit = balance - (balance / (1 + change)))

b_bal <- Reduce(function(x, y) x + x*y, b$change, init = start_capital, accumulate = TRUE)[-1]
b <- mutate(b, balance = b_bal, 
            profit = balance - (balance / (1 + change)))            

c_bal <- Reduce(function(x, y) x + x*y, c$change, init = start_capital, accumulate = TRUE)[-1]
c <- mutate(c, balance = c_bal, 
            profit = balance - (balance / (1 + change)))

data <- bind_rows(a, b, c)

  class profit change balance
1     a  10.00  0.010 1010.00
2     a  15.15  0.015 1025.15
3     b  -5.00 -0.005  995.00
4     b  -5.97 -0.006  989.03
5     c  20.00  0.020 1020.00
6     c   5.10  0.005 1025.10

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


person Fernando Velasco Borea    schedule 08.03.2020    source источник


Ответы (1)


Просто сделайте это групповой операцией:

library(dplyr)
library(purrr)

data %>%
  group_by(class) %>%
  mutate(balance = accumulate(.f = ~ .x + .x * .y, change, .init = start_capital)[-1])

# A tibble: 6 x 5
# Groups:   class [3]
  class profit change balance     y
  <fct>  <dbl>  <dbl>   <dbl> <dbl>
1 a         10  0.01     1010 1010 
2 a         15  0.015    1025 1025.
3 b         -5 -0.005    1020  995 
4 b         -6 -0.006    1014  989.
5 c         20  0.02     1036 1020 
6 c          5  0.005    1039 1025.

Обратите внимание, что вы неправильно поняли и не ограничиваетесь использованием только функций tidyverse с tidyverse. Вы можете использовать любые подходящие функции. Функция, которую вы используете Reduce(), прекрасна, хотя для компактности я заменил ее на accumulate(), который является тидиверсным эквивалентом Reduce() с аргументом накопления как ИСТИНА.

data %>%
  group_by(class) %>%
  mutate(balance = Reduce(function(x, y) x + x * y, change, init = start_capital, accumulate = TRUE)[-1])

Или используя ave() в базе R:

ave(data$change, data$class, FUN = function(v) Reduce(function(x, y) x + x * y, v, init = start_capital, accumulate = TRUE)[-1])
person 27 ϕ 9    schedule 08.03.2020
comment
Большое спасибо! Я заменю Reduce на Накопить по той же причине! :) У меня один вопрос, если я не установлю аргумент init, а передаю просто start_capital, мне бы не потребовалось убирать первый результат функции накопления? - person Fernando Velasco Borea; 09.03.2020
comment
Если бы вы сделали это (где start_capital - константа), тогда функции не было бы ничего, что нужно было бы перебирать - вы бы просто вернули константу обратно, переработанную до соответствующей длины. Начальное значение должно быть откуда-то из ваших данных или передано дополнительно. - person 27 ϕ 9; 09.03.2020
comment
Ох, ладно! Спасибо большое за вашу помощь! Получилось! - person Fernando Velasco Borea; 09.03.2020