2017-02-13 15 views
1

Учитывая кадр данных, определяется по формуле:Правильное использование функций dplyr для расчета продаж каждого продукта в скользящем окне без необходимости прохода или присоединения?

set.seed(1) 
date <- sample(seq(as.Date('2016/01/01'), as.Date('2016/12/31'), by="day"), 12) 
vals <- data.frame(x = rep(1:3, 4), date = date, cost = rnorm(12, 100)) 
vals 
# x  date  cost 
# 1 1 2016-04-07 100.48743 
# 2 2 2016-05-15 100.73832 
# 3 3 2016-07-27 100.57578 
# 4 1 2016-11-25 99.69461 
# 5 2 2016-03-14 101.51178 
# 6 3 2016-11-20 100.38984 
# 7 1 2016-12-06 99.37876 
# 8 2 2016-08-25 97.78530 
# 9 3 2016-08-13 101.12493 
# 10 1 2016-01-23 99.95507 
# 11 2 2016-12-27 99.98381 
# 12 3 2016-03-03 100.94384 

Я хочу, чтобы добавить новый столбец, где новое значение I го строки является суммой всех значений стоимости, для которых:

  • дата меньше или равна дате я й и больше, чем я м дата минус 90 дней
  • и значение х равно х значение строки я. (В данном примере комбинации х и даты являются уникальными, но в целом они не могут быть.)

Я могу сделать это двумя различными способами:

tmp <- vals %>% group_by(date, x) %>% 
summarise(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x[1]])) 
vals %>% left_join(tmp) 

и

vals %>% rowwise() %>% 
mutate(total = sum(vals$cost[vals$date <= date[1] & vals$date > (date[1] - 90) & vals$x == x])) 

Оба довольно медленные мои большие данные, по-видимому, из-за всех подмножеств. И я передаю кадр данных обратно в расчет, который для меня немного взломан.

Есть ли способ сделать это «правильно» в пределах dplyr? Под этим я подразумеваю, без необходимости проходить в кадре данных и делать медленное подмножество.

Или, если нет, есть ли, по крайней мере, более эффективный способ сделать это?

ответ

1

В принципе, (при заказе по дате) вы всегда рассчитывать sum(cost[index_start : index_end]) где index_start и index_end скользят по рядам. Это можно сделать более эффективно, используя суммарную сумму стоимости: sum(cost[index_start : index_end]) = cumsum(cost[index_end]) - cumsum(cost[index_start - 1]). Для вашего фрейма данных возможна одна возможная реализация кода.

# arrange by date so all relevant cost come after each other 
vals <- arrange(vals, x, date) 
group_by(vals, x) %>% 
    mutate(
    cumsum_cost = cumsum(cost), 
    index_start = map_dbl(
     date, 
     function(cur_date, date) { 
     min(which(cur_date - days(90) <= date)) 
     }, 
     date = date), 
    cumsum_cost_90_days_ago = map_dbl(
     index_start, 
     function(index_start, cumsum_cost) { 
     if (index_start - 1 <= 0) { 
      return(0) 
     } else { 
      cumsum_cost[index_start - 1] 
     } 
     }, 
     cumsum_cost = cumsum_cost), 
    cost_90_days = cumsum_cost - cumsum_cost_90_days_ago 
) 

Можно ускорить этот процесс дальше, если один будет умнее о получении index_start (например, с использованием знаний о том, что кадр данных заказанный date). Одним из простых способов для индексов было бы скользящее соединение, например. в data.table.

+0

Спасибо! Кажется, единственный способ - сначала заказать по дате. Я надеялся, что не должен этого делать, но тогда это будет намного сложнее, я думаю. – Danny

1

Нравится vals %>% arrange(x, date) %>% group_by(x) %>% mutate(new = cumsum(cost))?

Чтобы решить проблему с несколькими отчетами в день. Думаю, вам нужно сначала сделать расчет за день?

vals %>% 
    arrange(x, date) %>% 
    group_by(x, date) %>% 
    mutate(cost = cumsum(cost)) %>% 
    ungroup() %>% 
    group_by(x) %>% 
    mutate(new = cumsum(cost)) 
+1

Если в течение одного дня предполагается суммирование нескольких значений, этот подход столкнется с проблемами. – alistaire

+0

@alistaire true. Я могу представить себе, что purrr может работать здесь лучше – Hao

+1

Да, это нужно суммировать несколько раз в день. И, к сожалению, раздражать, я думаю, что я слишком упростил свою проблему для примера. В моей реальной проблеме у меня также есть условие, когда дата должна быть в течение последнего, скажем, 3 месяца, поэтому 'cumsum' больше не будет работать. Я не думал, что это так важно, но я отредактирую вопрос, чтобы добавить это. Сожалею! – Danny