Эффективная реализация переменной нумерованного окна вокруг заданных событий

#r #data.table #tidyverse

Вопрос:

У меня есть набор данных с непрерывными и векторными датами «событий». Я хочу создать пронумерованную переменную windows для окон заданной длины до и после каждого события. У меня есть рабочий код, но он смехотворно медленный, и я задавался вопросом о лучшем способе повышения его эффективности.

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

 data <- data.frame(day = seq(as.Date("2000-01-01"), as.Date("2001-01-01"), by = "day"))

dates <- sample(seq(as.Date("2000-01-01"), as.Date("2001-01-01"), by = "day"), 30)

pre <- 3
post <- 3

create_date_vector <- function(dates, pre, post){
  
  t_dates_dif <- diff(dates)
  selected_dates <- c()
  
  for(i in 1:(length(t_dates_dif) - 1)){
    selected_dates <- c(selected_dates, (t_dates_dif[i] > pre   post)   (t_dates_dif[i 1] > pre   post))
  }
  return(dates[which(selected_dates == 2)   1])
}

dates_chosen <- sort(create_date_vector(dates, pre, post))

 

Бит, который действительно нуждается в оптимизации, — это следующий код, который создает окна:

 data$event <- NA
for(i in 1:length(dates_chosen)){
  data <- data %>%
    mutate(
      event = ifelse(day >= dates_chosen[i] - pre amp; day <= dates_chosen[i]   post, i, event)
    )
}
 

Спасибо за вашу помощь.

Ответ №1:

Окна вокруг дат событий могут быть созданы путем обновления в неэквивалентном соединении с помощью вспомогательной таблицы

 library(data.table)
# create helper table
events <- data.table(dates_chosen)[
  , `:=`(rn = .I, from = dates_chosen - pre, to = dates_chosen   post)]
# update in a non-equi join 
setDT(data)[events, on = .(day >= from, day <= to), event := rn][]
 
             day event
  1: 2000-01-01    NA
  2: 2000-01-02    NA
  3: 2000-01-03    NA
  4: 2000-01-04    NA
  5: 2000-01-05    NA
 ---                 
363: 2000-12-28    NA
364: 2000-12-29    NA
365: 2000-12-30    NA
366: 2000-12-31    NA
367: 2001-01-01    NA
 
 # show only updated rows
data[!is.na(event)]
 
            day event
 1: 2000-05-16     1
 2: 2000-05-17     1
 3: 2000-05-18     1
 4: 2000-05-19     1
 5: 2000-05-20     1
 6: 2000-05-21     1
 7: 2000-05-22     1
 8: 2000-06-17     2
 9: 2000-06-18     2
10: 2000-06-19     2
11: 2000-06-20     2
12: 2000-06-21     2
13: 2000-06-22     2
14: 2000-06-23     2
15: 2000-10-26     3
16: 2000-10-27     3
17: 2000-10-28     3
18: 2000-10-29     3
19: 2000-10-30     3
20: 2000-10-31     3
21: 2000-11-01     3
           day event
 

Вспомогательной таблицей является

 events[]
 
    dates_chosen rn       from         to
1:   2000-05-19  1 2000-05-16 2000-05-22
2:   2000-06-20  2 2000-06-17 2000-06-23
3:   2000-10-29  3 2000-10-26 2000-11-01
 

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

1. Очень умно и супер элегантно! И в 33 раза быстрее! данные.таблица для выигрыша, как всегда! Большое спасибо!

Ответ №2:

Это может быть проще с lead

 library(dplyr)
create_date_vector2 <- function(dates, pre, post) {
      t1 <- diff(dates)      
      pre_post <- pre   post
      dates[which(((t1 > pre_post)   (dplyr::lead(t1) > pre_post)) == 2)   1]
}
 

-тестирование

 > create_date_vector2(dates, 3, 3)
[1] "2011-06-17" "2008-07-30" "2002-02-19"
 

-вывод из функции OP

 > create_date_vector(dates, pre, post)
[1] "2011-06-17" "2008-07-30" "2002-02-19"
 

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

1. Спасибо! Это, кажется, работает примерно так же быстро, иногда с некоторыми улучшениями. Если быть честным, то это скорее вторая часть, которая нуждается в оптимизации … Тем не менее, спасибо вам за ваш ответ!