R: рассчитать разницу во времени между конкретными событиями

У меня есть следующий набор данных:

df = data.frame(cbind(user_id = c(rep(1, 4), rep(2,4)),
                  complete_order = c(rep(c(1,0,0,1), 2)),
                  order_date = c('2015-01-28', '2015-01-31', '2015-02-08', '2015-02-23', '2015-01-25', '2015-01-28', '2015-02-06', '2015-02-21')))  

library(lubridate)
df$order_date = as_date(df$order_date)

user_id complete_order order_date
      1              1 2015-01-28
      1              0 2015-01-31
      1              0 2015-02-08
      1              1 2015-02-23
      2              1 2015-01-25
      2              0 2015-01-28
      2              0 2015-02-06
      2              1 2015-02-21

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

user_id complete_order order_date complete_order_time_diff
<fctr>         <fctr>     <date>              <time>
   1              1    2015-01-28             NA days
   1              0    2015-01-31              3 days
   1              0    2015-02-08             11 days
   1              1    2015-02-23             26 days
   2              1    2015-01-25             NA days
   2              0    2015-01-28              3 days
   2              0    2015-02-06             12 days
   2              1    2015-02-21             27 days

когда я попробую это решение:

library(dplyr)

df %>% 
group_by(user_id) %>%
mutate(complete_order_time_diff = order_date[complete_order==1]-lag(order_date[complete_order==1))

возвращает ошибку:

Error: incompatible size (3), expecting 4 (the group size) or 1

Любая помощь с этим будет отличной, спасибо!

3 ответа

Решение

Кажется, вы ищете расстояние каждого заказа от последнего выполненного. Имея двоичный вектор, x, c(NA, cummax(x * seq_along(x))[-length(x)]) дает индексы последней "1", видимой перед каждым элементом. Затем вычитание каждого элемента "order_date" из "order_date" по этому соответствующему индексу дает желаемый результат. Например

set.seed(1453); x = sample(0:1, 10, TRUE)
set.seed(1821); y = sample(5, 10, TRUE)
cbind(x, y, 
      last_x = c(NA, cummax(x * seq_along(x))[-length(x)]), 
      y_diff = y - y[c(NA, cummax(x * seq_along(x))[-length(x)])])
#      x y last_x y_diff
# [1,] 1 3     NA     NA
# [2,] 0 3      1      0
# [3,] 1 5      1      2
# [4,] 0 1      3     -4
# [5,] 0 3      3     -2
# [6,] 1 5      3      0
# [7,] 1 1      6     -4
# [8,] 0 3      7      2
# [9,] 0 4      7      3
#[10,] 1 5      7      4

По вашим данным, первый формат df для удобства:

df$order_date = as.Date(df$order_date)
df$complete_order = df$complete_order == "1"  # lose the 'factor'

И затем, либо примените вышеуказанный подход после group_by:

library(dplyr)
df %>% group_by(user_id) %>% 
   mutate(time_diff = order_date - 
order_date[c(NA, cummax(complete_order * seq_along(complete_order))[-length(complete_order)])])

или, возможно, попробовать операции, которые избегают группировки (при условии упорядоченного "user_id") после учета индексов, где "user_id" изменяется:

# save variables to vectors and keep a "logical" of when "id" changes
id = df$user_id
id_change = c(TRUE, id[-1] != id[-length(id)])

compl = df$complete_order
dord = df$order_date

# accounting for changes in "id", locate last completed order
i = c(NA, cummax((compl | id_change) * seq_along(compl))[-length(compl)])
is.na(i) = id_change

dord - dord[i]
#Time differences in days
#[1] NA  3 11 26 NA  3 12 27

Попробуй это

library(dplyr)

df %>% group_by(user_id, complete_order) %>% 
   mutate(c1 = order_date - lag(order_date)) %>% 
   group_by(user_id) %>% mutate(c2 = order_date - lag(order_date)) %>% ungroup %>% 
   mutate(complete_order_time_diff = ifelse(complete_order==0, c2, c1)) %>% 
   select(-c(c1, c2))

Обновить

за несколько отмененных заказов

 df %>% mutate(c3=cumsum( complete_order != "0")) %>% group_by(user_id, complete_order) %>% 
  mutate(c1 = order_date - lag(order_date)) %>% 
  group_by(user_id) %>% mutate(c2 = order_date - lag(order_date)) %>% 
  mutate(c2=as.numeric(c2)) %>% group_by(user_id, c3) %>% 
  mutate(c2=cumsum(ifelse(complete_order==1, 0, c2))) %>% ungroup %>% 
  mutate(complete_order_time_diff = ifelse(complete_order==0, c2, c1)) %>% 
  select(-c(c1, c2, c3))

логика

c3 является id каждый раз, когда есть заказ (т.е. complete_order not 0) увеличить на 1.

c1 рассчитывает разницу дней user_id (но для неполных заказов результат неправильный)

c2 исправляет это несоответствие c1 в отношении незавершенных заказов.

надеюсь, что это проясняет вещи.

Я бы предложил вам работать с комбинациями group_by() а также mutate(cumsum()) чтобы лучше понять результаты наличия более одной сгруппированной переменной.

Я думаю, что вы можете добавить filter функция вместо поднабора с order_date[complete_order == 1] и убедитесь, что order_date (и другие переменные) являются правильными типами данных путем добавления stringsAsFactors = F в data.frame()):

df = data.frame(cbind(user_id = c(rep(1, 4), rep(2,4)),
                      complete_order = c(rep(c(1,1,0,1), 2)),
                      order_date = c('2015-01-28', '2015-01-31', '2015-02-08', '2015-02-23', '2015-01-25', '2015-01-28', '2015-02-06', '2015-02-21')),
                stringsAsFactors = F)  

df$order_date <- lubridate::ymd(df$order_date)

df %>% 
    group_by(user_id) %>% 
    filter(complete_order == 1) %>% 
    mutate(complete_order_time_diff = order_date - lag(order_date))

Это возвращает время до следующего полного заказа (и NA если нет ни одного)

  user_id complete_order order_date complete_order_time_diff
    <chr>          <chr>     <date>                   <time>
1       1              1 2015-01-28                  NA days
2       1              1 2015-01-31                   3 days
3       1              1 2015-02-23                  23 days
4       2              1 2015-01-25                  NA days
5       2              1 2015-01-28                   3 days
6       2              1 2015-02-21                  24 days
Другие вопросы по тегам