Повышение эффективности выполнения кода с использованием data.table и for-loop - PullRequest
1 голос
/ 19 февраля 2020

Проблема: Как сделать так, чтобы код -l oop в приведенном ниже коде работал более эффективно? Для этого игрушечного примера это работает в разумные сроки. Тем не менее, unique_ids будет вектором приблизительно из 8000 записей, а for-l oop сильно замедляет вычисления. Любые идеи? Огромное спасибо!

Цель: Ретроспективно кластеризовать IID для каждого дня в прыжок и вершину на основе логики расчета c в for-l oop.

Исходные данные :

   IID      ENTRY     FINISH     TARGET max_finish_target_date
1:      1 2020-02-11 2020-02-19 2020-02-15             2020-02-19
2:      2 2020-02-13 2020-02-17 2020-02-19             2020-02-19

Окончательные (целевые) данные:

 IID      Dates    ind_frist
 1:      1 2020-02-10             
 2:      1 2020-02-11 hop
 3:      1 2020-02-12 hop
 4:      1 2020-02-13 hop
 5:      1 2020-02-14 hop
 6:      1 2020-02-15 hop
 7:      1 2020-02-16 top
 8:      1 2020-02-17 top
 9:      1 2020-02-18 top
10:      1 2020-02-19 top
11:      2 2020-02-10             
12:      2 2020-02-11             
13:      2 2020-02-12             
14:      2 2020-02-13 hop
15:      2 2020-02-14 hop
16:      2 2020-02-15 hop
17:      2 2020-02-16 hop
18:      2 2020-02-17 hop
19:      2 2020-02-18             
20:      2 2020-02-19             
21:      3 2020-02-10             
22:      3 2020-02-11             
23:      3 2020-02-12             
24:      3 2020-02-13             
25:      3 2020-02-14             
26:      3 2020-02-15 hop
27:      3 2020-02-16 hop
28:      3 2020-02-17 top
29:      3 2020-02-18 top
30:      3 2020-02-19 top

Код

rm(list = ls())

library(data.table)

# Some sample start data
initial_dt <- data.table(IID = c(1, 2, 3),
                         ENTRY = c("2020-02-11", "2020-02-13", "2020-02-15"),
                         FINISH = c("2020-02-19", "2020-02-17", ""),
                         TARGET = c("2020-02-15", "2020-02-19", "2020-02-16"))

initial_dt[, ":="(ENTRY = ymd(ENTRY),
                  FINISH = ymd(FINISH),
                  TARGET = ymd(TARGET))]

initial_dt[is.na(FINISH), FINISH := as.Date(ymd_hms(Sys.time()), format = "%Y-%m-%d")]


initial_dt[, max_finish_target_date := pmax(FINISH, TARGET)]


# Specify target data shape and output format
unique_ids <- c(1, 2, 3) 

dts <- seq(as.Date("2020-02-10", format = "%Y-%m-%d"), as.Date(ymd_hms(Sys.time()), format = "%Y-%m-%d"), by = "days")

ids <- rep(unique_ids, each = length(dts))
len <- length(unique_ids)

final_dt <- data.table(IID = ids,
                       Dates = rep(dts, times = len))

# Calculation logic
# QUESTION: How can I make this part below run more efficiently and less time costly?
for (d_id in unique_ids){
  final_dt[(IID == d_id) & (Dates %between% c(initial_dt[IID == d_id, ENTRY], initial_dt[IID == d_id, max_finish_target_date])), 
                ind_frist := ifelse((Dates > initial_dt[IID == d_id, TARGET]) & (Dates <= initial_dt[IID == d_id, max_finish_target_date]), 
                                    "hop", 
                                    "top")]
}

Ответы [ 2 ]

2 голосов
/ 19 февраля 2020

Ваш l oop не производит вывод, который вы показываете. Следующие неэквивалентные объединения производят этот вывод, но их можно легко настроить для других правил (например, из ваших for l oop):

final_dt <- CJ(IID = initial_dt[["IID"]], Dates = dts)
final_dt[initial_dt, ind_frist := "hop", on = .(IID, Dates >= ENTRY, Dates <= FINISH)]
final_dt[initial_dt, ind_frist := "top", on = .(IID, Dates > TARGET, Dates <= FINISH)]

Эти объединения должны быть очень быстрыми.

Результат:

#    IID      Dates ind_frist
# 1:   1 2020-02-10      <NA>
# 2:   1 2020-02-11       hop
# 3:   1 2020-02-12       hop
# 4:   1 2020-02-13       hop
# 5:   1 2020-02-14       hop
# 6:   1 2020-02-15       hop
# 7:   1 2020-02-16       top
# 8:   1 2020-02-17       top
# 9:   1 2020-02-18       top
#10:   1 2020-02-19       top
#11:   2 2020-02-10      <NA>
#12:   2 2020-02-11      <NA>
#13:   2 2020-02-12      <NA>
#14:   2 2020-02-13       hop
#15:   2 2020-02-14       hop
#16:   2 2020-02-15       hop
#17:   2 2020-02-16       hop
#18:   2 2020-02-17       hop
#19:   2 2020-02-18      <NA>
#20:   2 2020-02-19      <NA>
#21:   3 2020-02-10      <NA>
#22:   3 2020-02-11      <NA>
#23:   3 2020-02-12      <NA>
#24:   3 2020-02-13      <NA>
#25:   3 2020-02-14      <NA>
#26:   3 2020-02-15       hop
#27:   3 2020-02-16       hop
#28:   3 2020-02-17       top
#29:   3 2020-02-18       top
#30:   3 2020-02-19       top
#    IID      Dates ind_frist
1 голос
/ 19 февраля 2020

Возможная альтернатива с использованием -join:

final_dt[initial_dt
         , on = .(IID)
         , ind_frist := c("", "top","hop")[1L + (Dates > TARGET & Dates <= max_finish_target_date) +
                                             Dates %between% .(ENTRY, max_finish_target_date)]][]

, которая дает:

    IID      Dates ind_frist
 1:   1 2020-02-10          
 2:   1 2020-02-11       top
 3:   1 2020-02-12       top
 4:   1 2020-02-13       top
 5:   1 2020-02-14       top
 6:   1 2020-02-15       top
 7:   1 2020-02-16       hop
 8:   1 2020-02-17       hop
 9:   1 2020-02-18       hop
10:   1 2020-02-19       hop
11:   2 2020-02-10          
12:   2 2020-02-11          
13:   2 2020-02-12          
14:   2 2020-02-13       top
15:   2 2020-02-14       top
16:   2 2020-02-15       top
17:   2 2020-02-16       top
18:   2 2020-02-17       top
19:   2 2020-02-18       top
20:   2 2020-02-19       top
21:   3 2020-02-10          
22:   3 2020-02-11          
23:   3 2020-02-12          
24:   3 2020-02-13          
25:   3 2020-02-14          
26:   3 2020-02-15       top
27:   3 2020-02-16       top
28:   3 2020-02-17       hop
29:   3 2020-02-18       hop
30:   3 2020-02-19       hop

Это аналогично выводу for-l oop.

Некоторое объяснение: часть 1L + (Dates > TARGET & Dates <= max_finish_target_date) + Dates %between% .(ENTRY, max_finish_target_date) создает индексный вектор из единиц, двух и трех равной длины, равный числу строк final_dt; если вы поставите это в квадратных скобках после c("", "top","hop"), для каждого вы получите пустую строку, для каждых двух вы получите "top", а для каждых трех вы получите "hop".

...