Рекурсивная задержка() во время мутации?

#r #dplyr

Вопрос:

Альтернативным названием может быть «Использовать задержку в мутации для ссылки на мутацию предыдущих строк».

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

 mydiamonds <- diamonds %>%
  mutate(Ideal = ifelse(cut == 'Ideal', 1, 0)) %>% 
  group_by(Ideal) %>% 
  mutate(rn = row_number()) %>% 
  arrange(Ideal, rn) %>% 
  mutate(CumPrice = cumsum(price)) %>% 
  mutate(InitialPrice = min(price)) %>% 
  select(Ideal, rn, CumPrice, InitialPrice)
 

Выглядит так:

 mydiamonds %>% head
# A tibble: 6 x 4
# Groups:   Ideal [1]
  Ideal    rn CumPrice InitialPrice
  <dbl> <int>    <int>        <int>
1     0     1      326          326
2     0     2      653          326
3     0     3      987          326
4     0     4     1322          326
5     0     5     1658          326
6     0     6     1994          326
 

Модель:

 mod.diamonds = glm(CumPrice ~ log(lag(CumPrice))  log(rn)   Ideal , family = "poisson", data = mydiamonds)
 

Протестируйте модель:

 # new data, pretend we don't know CumPrice but want to use predictions to predict subsequent predictions
mydiamonds.testdata <- mydiamonds %>% select(-CumPrice)
# manual prediction based on lag(prediction), for the first row in each group use InitialPrice
## add coefficients as fields
coeffs <- mod.diamonds$coefficients
mydiamonds.testdata <- mydiamonds.testdata %>% 
  mutate(CoefIntercept = coeffs['(Intercept)'],
         CoefLogLagCumPrice = coeffs['log(lag(CumPrice))'],
         CoefLogRn = coeffs['log(rn)'],
         CoefIdeal = coeffs['Ideal']
         )
 

Вот как выглядят мои тестовые данные:

  mydiamonds.testdata %>% head
# A tibble: 6 x 7
# Groups:   Ideal [1]
  Ideal    rn InitialPrice CoefIntercept CoefLogLagCumPrice CoefLogRn CoefIdeal
  <dbl> <int>        <int>         <dbl>              <dbl>     <dbl>     <dbl>
1     0     1          326        0.0931              0.987    0.0154 -0.000715
2     0     2          326        0.0931              0.987    0.0154 -0.000715
3     0     3          326        0.0931              0.987    0.0154 -0.000715
4     0     4          326        0.0931              0.987    0.0154 -0.000715
5     0     5          326        0.0931              0.987    0.0154 -0.000715
6     0     6          326        0.0931              0.987    0.0154 -0.000715
 

Не могу использовать функцию predict(), так как мне нужно рекурсивно предсказывать, где прогнозы для предыдущего дня/строки вводятся в текущий день. Вместо этого попробуйте спрогнозировать вручную, используя коэффициенты:

 # prediction
mydiamonds.testdata <- mydiamonds.testdata %>% 
  mutate(
    Prediction = CoefIntercept   
      
      # here's the hard bit. If it's the first row in the group, use InitialPrice, else use the value of the previous prediction
      (CoefLogLagCumPrice * ifelse(rn == 1, InitialPrice, lag(Prediction)))   
      
      (CoefLogRn * log(rn))   
      (CoefIdeal * Ideal)
    )
 

Ошибка: Проблема с mutate() вводом Prediction . x объект
«Прогноз» не найден ℹ Ввод Prediction есть ... . ℹ Ошибка
произошла в группе 1: Идеал = 0.

Как я могу мутировать таким образом, где я хотел бы сослаться на мутацию предыдущих строк? (Если только это не самая первая строка, в этом случае используйте InitialPrice)

[РЕДАКТИРОВАТЬ] следуя за комментатором, я попробовал использовать функцию накопления, с которой я не так хорошо знаком:

 mydiamonds.testdata <- mydiamonds.testdata %>% 
  mutate(
    Prediction = accumulate(.f = function(.) {
      
    .$CoefIntercept   
      
      # here's the hard bit. If it's the first row in the group, use InitialPrice, else use the value of the previous prediction
      (.$CoefLogLagCumPrice * ifelse(.$rn == 1, .$InitialPrice, lag(.$Prediction)))   
      
      (.$CoefLogRn * log(.$rn))   
      (.$CoefIdeal * .$Ideal)
      
      }))
Error: Problem with `mutate()` input `Prediction`.
x argument ".x" is missing, with no default
ℹ Input `Prediction` is `accumulate(...)`.
ℹ The error occurred in group 1: Ideal = 0.
 

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

1. возможно, вам придется использовать некоторую адаптацию purrr s reduce() или accumulate()

2. @GuedesBF Я попробовал, смотрите мою обновленную попытку под редакцией

3. @user14328853 вам следовало отредактировать свой другой вопрос, а не создавать новый. Этот, кстати, намного приятнее, хорошая работа по его улучшению!

Ответ №1:

Как вы сказали, вы не привыкли к этой довольно сложной функции, вот небольшое объяснение.

purrr::accumulate() используется для вычисления рекурсивных операций по строкам. Его первым аргументом .x является переменная, на которой вы хотите накапливать. Его вторым аргументом .f является функция , которая должна иметь 2 аргумента: текущий результат cur и следующее вычисленное значение val . При первом .f вызове cur значение равно .x[1] (по умолчанию), затем оно равно предыдущему результату, возвращенному .f .

purrr::accumulate2() позволяет нам использовать вторую переменную .y для итерации. Первое значение .y всегда игнорируется, так как .f уже известно, что возвращать в это время. Следовательно, .y должно быть на один пункт меньше, чем .x .

К сожалению, есть только accumulate() и accumulate2() там, где вам нужно accumulate3() было бы или paccumulate() накопить на rn, Идеал и цена.

Однако, используя row_number() и cur_data() , вы можете обмануть accumulate2() , чтобы вести себя так, как вы хотите:

 CoefIntercept = coeffs['(Intercept)']
CoefLogLagCumPrice = coeffs['log(lag(CumPrice))']
CoefLogRn = coeffs['log(rn)']
CoefIdeal = coeffs['Ideal']

mydiamonds.testdata <- mydiamonds %>% 
  ungroup() %>% 
  select(-CumPrice) %>% 
  mutate(
    Prediction = accumulate2(.x=InitialPrice, .y=row_number()[-1], 
                             .f=function(acc, nxt, row) {
      db=cur_data_all()
      rn = db$rn[row]
      Ideal = db$Ideal[row]
      CoefIntercept  
        (CoefLogLagCumPrice * acc)  
        (CoefLogRn * log(rn))  
        (CoefIdeal * Ideal)
      
    }) %>% unlist()
  )
mydiamonds.testdata

# A tibble: 53,940 x 4
#     Ideal    rn InitialPrice Prediction
#     <dbl> <int>        <int>      <dbl>
# 1       0     1          326       326 
# 2       0     2          326       322.
# 3       0     3          326       318.
# 4       0     4          326       313.
# 5       0     5          326       309.
# 6       0     6          326       305.
# 7       0     7          326       301.
# 8       0     8          326       297.
# 9       0     9          326       294.
# 10      0    10          326       290.
 

ИЗМЕНИТЬ: Существует другой, более чистый способ использования .init аргумента, так как InitialPrice столбец никогда не используется по-настоящему, за исключением его первого значения. Это позволяет напрямую использовать аргументы, но это не будет работать для более сложных моделей с большим количеством ковариат.

 mydiamonds.testdata <- mydiamonds %>% 
  ungroup() %>% 
  select(-CumPrice) %>% 
  mutate(
    Prediction = accumulate2(.x=Ideal[-1], .y=rn[-1], 
                             .init=InitialPrice[1],
                             .f=function(rslt, Ideal, rn) {
      CoefIntercept  
        (CoefLogLagCumPrice * rslt)  
        (CoefLogRn * log(rn))  
        (CoefIdeal * Ideal)
      
    }) %>% unlist()
  )
 

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

1. ПОТРЯСАЮЩЕ, @Dan Chaltiel!

2. Это фантастика, спасибо! Я только что открыл свой рабочий ноутбук и увидел это, с нетерпением ожидая, чтобы попробовать это сегодня с таким подходом, расскажу, как у меня дела.

3. Это действительно сработало! Спасибо. В первом методе, который я использую, поскольку реальные данные действительно имеют больше коэффициентов, почему вы разгруппировали() тестовые данные? Номер строки указан для каждой группы, так как идея заключается в том, что совокупная сумма находится в пределах совокупной суммы группы. Я просто собирался удалить строку ungroup (), но хотел убедиться, что я здесь чего-то не пропустил?

4. @user14328853 Я разогруппировался по привычкам, потому что не был уверен, что моя функция будет хорошо работать с группами. С другой стороны, это должно сработать.

5. @user14328853 accumulate2() всегда производит такое впечатление 🙂 Рад, что смог помочь!