Как ускорить цикл for, когда порядок выполнения имеет значение? - PullRequest
0 голосов
/ 15 октября 2019

У меня есть таблица, содержащая исторические ежедневные данные о ценах для различных продуктов и NA для будущих цен. У меня есть столбец для ожидаемого повышения цены на данную будущую дату для продукта. Повышение цены основано на цене предыдущего дня.

Я построил цикл for для расчета ожидаемой цены продуктов, но он работает очень медленно для ~ 500 000 записей, через которые он проходит.

Все исторические данные о ценах находятся втаблица, в то время как вся прогнозируемая цена равна NA.

Пример текущей таблицы (old_table):

date        product        price        incr_amt
====================================================
...          ...            ...         ...
10/14/19     prod1          50          1.0
10/15/19     prod1          50          1.0
10/16/19     prod1          NA          1.0
...          ...            ...         ...
04/01/20     prod1          NA          1.05
04/02/20     prod1          NA          1.0
...          ...            ...         ...
...          ...            ...         ...
10/14/19     prod2          35          1.0
10/15/19     prod2          35          1.0
10/16/19     prod2          NA          1.0
...          ...            ...         ...
01/01/20     prod2          NA          1.02
01/02/20     prod2          NA          1.0
...          ...            ...         ...

Мой текущий код группируется по продукту, тогда, если цена равна NA, рассчитайте цену как цену с задержкой *increase_amt. Затем пересчитайте lagged_price для следующей итерации. Циклически перебирайте все строки в таблице.

Пример результата (new_table):

date        product        price        incr_amt
====================================================
...          ...            ...         ...
10/14/19     prod1          50          1.0
10/15/19     prod1          50          1.0
10/16/19     prod1          50          1.0
...          ...            ...         ...
04/01/20     prod1          52.5        1.05
04/02/20     prod1          52.5        1.0
...          ...            ...         ...
...          ...            ...         ...
10/14/19     prod2          35          1.0
10/15/19     prod2          35          1.0
10/16/19     prod2          35          1.0
...          ...            ...         ...
01/01/20     prod2          35.7        1.02
01/02/20     prod2          35.7        1.0
...          ...            ...         ...

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

Текущий код:

library(tidyverse)

old_table <- tribble(
  ~date, ~product, ~price, ~incr_amt,
  "2019-10-14", "prod1", 50, 1.0,
  "2019-10-15", "prod1", 50, 1.0,
  "2019-10-16", "prod1", NA, 1.0,
  "2019-10-17", "prod1", NA, 1.0,
  "2019-10-18", "prod1", NA, 1.0,
  "2019-10-19", "prod1", NA, 1.05,
  "2019-10-20", "prod1", NA, 1.0,
  "2019-10-21", "prod1", NA, 1.0,
  "2019-10-14", "prod2", 35, 1.0,
  "2019-10-15", "prod2", 35, 1.0,
  "2019-10-16", "prod2", NA, 1.0,
  "2019-10-17", "prod2", NA, 1.0,
  "2019-10-18", "prod2", NA, 1.0,
  "2019-10-19", "prod2", NA, 1.0,
  "2019-10-20", "prod2", NA, 1.0,
  "2019-10-21", "prod2", NA, 1.02,
  "2019-10-22", "prod2", NA, 1.0
)

new_table <- old_table %>%
  group_by(product) %>%
  mutate(lag_price = lag(price))

for (i in 1:nrow(new_table)) {
  if (!is.na(new_table$price[[i]]))
    next
  if (is.na(new_table$price[[i]])) {
    new_table$price[[i]] = new_table$lag_price[[i]] * new_table$incr_amt[[i]]
    new_table$lag_price <- lag(new_table$price)
  }

}

Код выполняется, нозанимает около часа, чтобы пройти через ~ 500 000 записей. Как я могу улучшить этот процесс? Благодаря.

1 Ответ

1 голос
/ 15 октября 2019

Вот векторизованное решение, которое, я ожидаю, будет намного быстрее. (Мне было бы любопытно, насколько быстрее ваши реальные данные.) Главное, как замедляет ваш код, это, как отмечает @aocall, 500 000 модификаций таблиц. Это должно быть намного быстрее, если мы сможем применить одни и те же вычисления ко всей таблице одновременно. Здесь мы рассчитываем совокупный рост по каждому отсутствующему разделу в каждом продукте. (Мы также излишне вычисляем рост по непропущенным участкам, но я предполагаю, что накладные расходы будут минимальными.) Затем мы можем применить этот коэффициент роста к последнему доступному числу, чтобы получить заполненное.

library(dplyr)
new_table2 <- old_table %>%
  # Put together strings of missingness & track cumulative growth in them
  group_by(product) %>%
  mutate(missing_streak = cumsum(is.na(price) != is.na(lag(price)))) %>%

  # fill in NA with last value
  mutate(price_new = price) %>%
  tidyr::fill(price_new) %>%

  # gross up based on growth
  group_by(product, missing_streak) %>%
  mutate(cuml_growth = cumprod(incr_amt)) %>%
  mutate(price_new = if_else(is.na(price),
                             price_new * cuml_growth,
                             price)) %>%
  ungroup()

Кажется, работает с вашими данными:

identical(new_table$price, new_table2$price_new)
[1] TRUE
...