Вычислить дни с момента последнего события в R

Мой вопрос заключается в том, как рассчитать количество дней с момента последнего события, произошедшего в R. Ниже приведен минимальный пример данных:

df <- data.frame(date=as.Date(c("06/07/2000","15/09/2000","15/10/2000","03/01/2001","17/03/2001","23/05/2001","26/08/2001"), "%d/%m/%Y"), 
event=c(0,0,1,0,1,1,0))
        date event
1 2000-07-06     0
2 2000-09-15     0
3 2000-10-15     1
4 2001-01-03     0
5 2001-03-17     1
6 2001-05-23     1
7 2001-08-26     0

Двоичная переменная (событие) имеет значения 1, указывающие, что произошло событие, и 0 в противном случае. Повторные наблюдения выполняются в разное время (date) Ожидаемый результат следующий с днями после последнего события (tae):

 date        event       tae
1 2000-07-06     0        NA
2 2000-09-15     0        NA
3 2000-10-15     1         0
4 2001-01-03     0        80
5 2001-03-17     1       153
6 2001-05-23     1        67
7 2001-08-26     0        95

Я искал ответы на подобные проблемы, но они не затрагивают мою конкретную проблему. Я попытался реализовать идеи из из аналогичного сообщения (Рассчитать прошедшее время с момента последнего события), а ниже - ближайший я добрались до решения:

library(dplyr)
df %>%
  mutate(tmp_a = c(0, diff(date)) * !event,
         tae = cumsum(tmp_a))

Что дает результат, показанный ниже, который не совсем ожидаемый:

        date event tmp_a tae
1 2000-07-06     0     0   0
2 2000-09-15     0    71  71
3 2000-10-15     1     0  71
4 2001-01-03     0    80 151
5 2001-03-17     1     0 151
6 2001-05-23     1     0 151
7 2001-08-26     0    95 246

Приветствуется всякая помощь в том, как правильно настроить этот или другой подход.

Ответы

Ответ 1

Вы можете попробовать что-то вроде этого:

# make an index of the latest events
last_event_index <- cumsum(df$event) + 1

# shift it by one to the right
last_event_index <- c(1, last_event_index[1:length(last_event_index) - 1])

# get the dates of the events and index the vector with the last_event_index, 
# added an NA as the first date because there was no event
last_event_date <- c(as.Date(NA), df[which(df$event==1), "date"])[last_event_index]

# substract the event date with the date of the last event
df$tae <- df$date - last_event_date
df

#        date event      tae
#1 2000-07-06     0  NA days
#2 2000-09-15     0  NA days
#3 2000-10-15     1  NA days
#4 2001-01-03     0  80 days
#5 2001-03-17     1 153 days
#6 2001-05-23     1  67 days
#7 2001-08-26     0  95 days

Ответ 2

Это больно, и вы теряете производительность, но можете сделать это с помощью цикла for:

datas <- read.table(text = "date event
2000-07-06     0
2000-09-15     0
2000-10-15     1
2001-01-03     0
2001-03-17     1
2001-05-23     1
2001-08-26     0", header = TRUE, stringsAsFactors = FALSE)


datas <- transform(datas, date = as.Date(date))

lastEvent <- NA
tae <- rep(NA, length(datas$event))
for (i in 2:length(datas$event)) {
  if (datas$event[i-1] == 1) {
    lastEvent <- datas$date[i-1]
  }
  tae[i] <- datas$date[i] - lastEvent

  # To set the first occuring event as 0 and not NA
  if (datas$event[i] == 1 && sum(datas$event[1:i-1] == 1) == 0) {
    tae[i] <- 0
  }
}

cbind(datas, tae)

date event tae
1 2000-07-06     0  NA
2 2000-09-15     0  NA
3 2000-10-15     1   0
4 2001-01-03     0  80
5 2001-03-17     1 153
6 2001-05-23     1  67
7 2001-08-26     0  95

Ответ 3

Старый вопрос, но я экспериментировал с катящимися соединениями и нашел это интересным.

library(data.table)
setDT(df)
setkey(df, date)

# rolling self-join to attach last event time
df = df[event == 1, .(lastevent = date), key = date][df, roll = TRUE]

# find difference between record and previous event == 1 record
df[, tae := difftime(lastevent, shift(lastevent, 1L, "lag"), unit = "days")]

# difftime for simple case between date and joint on previous event
df[event == 0, tae:= difftime(date, lastevent, unit = "days")]

> df
         date  lastevent event      tae
1: 2000-07-06       <NA>     0  NA days
2: 2000-09-15       <NA>     0  NA days
3: 2000-10-15 2000-10-15     1  NA days
4: 2001-01-03 2000-10-15     0  80 days
5: 2001-03-17 2001-03-17     1 153 days
6: 2001-05-23 2001-05-23     1  67 days
7: 2001-08-26 2001-05-23     0  95 days

Ответ 4

Я опаздываю на вечеринку, но я использовал tidyr::fill, чтобы сделать это проще. По сути, вы преобразуете не-события в пропущенные значения, затем используете fill, чтобы заполнить NA последним событием, а затем вычтите текущую дату из последнего события.

Я проверил это с целочисленным столбцом даты, так что может потребоваться некоторая настройка для столбца даты Date -type (особенно при использовании NA_integer_. Я не уверен, какой базовый тип используется для Date объекты; я думаю, NA_real_.)

df %>%
  mutate(
    event = as.logical(event),
    last_event = if_else(event, true = date, false = NA_integer_)) %>%
  fill(last_event) %>%
  mutate(event_age = date - last_event)

Ответ 5

У меня была похожая проблема, и я смог решить ее, сочетая некоторые идеи, изложенные выше. Основное различие, которое я имел с моим, состояло бы в том, что клиенты будут иметь разное событие (для меня это покупки). Я хотел знать совокупные итоги всех этих покупок, а также дату последней активности. Основным способом решения этой проблемы было создание индексного фрейма данных для соединения с основным фреймом данных. Похоже на вопрос с самым высоким рейтингом выше. Смотрите повторяемый код ниже.

library(tidyverse)
rm(list=ls())

#generate repeatable code sample dataframe
df <- as.data.frame(sample(rep(sample(seq(as.Date('1999/01/01'), as.Date('2000/01/01'), by="day"), 12), each = 4),36))
df$subtotal <- sample(1:100, 36)
df$cust <- sample(rep(c("a", "b", "c", "d", "e", "f"), each=12), 36)

colnames(df) <- c("dates", "subtotal", "cust")

#add a "key" based on date and event
df$datekey <- paste0(df$dates, df$cust)

#The following 2 lines are specific to my own analysis but added to show depth
df_total_visits <- df %>% select(dates, cust) %>% distinct() %>% group_by(cust) %>% tally(n= "total_visits") %>% mutate(variable = 1)
df_order_bydate <-   df %>% select(dates, cust) %>% group_by(dates, cust) %>% tally(n= "day_orders") 


df <- left_join(df, df_total_visits)
df <- left_join(df, df_order_bydate) %>% arrange(dates)

# Now we will add the index, the arrange from the previous line is super important if your data is not already ordered by date
cummulative_groupping <- df %>% select(datekey, cust, variable, subtotal) %>% group_by(datekey) %>% mutate(spending = sum(subtotal)) %>% distinct(datekey, .keep_all = T) %>% select(-subtotal)
cummulative_groupping <- cummulative_groupping %>% group_by(cust) %>% mutate(cumulative_visits = cumsum(variable),
                                                                                    cumulative_spend = cumsum(spending))

df <- left_join(df, cummulative_groupping) %>% select(-variable)

#using the cumulative visits as the index, if we add one to this number we can then join it again on our dataframe
last_date_index <- df %>% select(dates, cust, cumulative_visits)
last_date_index$cumulative_visits <- last_date_index$cumulative_visits + 1 
colnames(last_date_index) <- c("last_visit_date", "cust", "cumulative_visits")
df <- left_join(df, last_date_index, by = c("cust", "cumulative_visits"))


#the difference between the date and last visit answers the original posters question.  NAs will return as NA
df$toa <- df$dates - df$last_visit_date

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