#r #performance #for-loop #data.table #apply
#r #Производительность #для цикла #данные.таблица #применить
Вопрос:
Мне нужно запустить цикл над фреймом данных с ~ 150 Тыс. строк. Однако циклу необходимо проверять каждую строку и проверять условие, которое проверяет каждую другую строку в наборе данных. Мой код отлично работает для игрушечного набора данных, он выдает правильное значение, но работает слишком медленно для моего реального набора данных. Я позволил ему работать в течение нескольких часов, и он все еще не закончился. итак, я надеюсь, что у кого-то есть идея получше, как подойти к этому.
#R version 3.5.1 Windows 64-bit
#Example dataset
my_df <- data.frame("PERSON" = c("A","A","A","B","A","A","B"),
"DATE_START" = c("2019-01-15","2019-01-10","2019-01-20","2019-01-19","2018-12-20","2018-03-03","2019-05-01"),
"DATE_FINISH" = c("2019-01-30","2019-01-18","2019-02-05","2019-01-23","2019-02-10","2018-04-01","2019-06-06")
)
#Each row is a task that the assigned person is working on
my_df
PERSON DATE_START DATE_FINISH
1 A 2019-01-15 2019-01-30
2 A 2019-01-10 2019-01-18
3 A 2019-01-20 2019-02-05
4 B 2019-01-19 2019-01-23
5 A 2018-12-20 2019-02-10
6 A 2018-03-03 2018-04-01
7 B 2019-05-01 2019-06-06
Что я хочу знать ДЛЯ строки 1, сколько других задач у пользователя A перекрывается между датами его начала и окончания? (включая строку, в которой он включен)
Итак, ответ, который я ищу, это
PERSON DATE_START DATE_FINISH NUMBER_OF_TASKS
1 A 2019-01-15 2019-01-30 4
2 A 2019-01-10 2019-01-18 3
3 A 2019-01-20 2019-02-05 3
4 B 2019-01-19 2019-01-23 1
5 A 2018-12-20 2019-02-10 4
6 A 2018-03-03 2018-04-01 1
7 B 2019-05-01 2019-06-06 1
Таким образом, это в основном говорит о том, что для строки 1 у человека A было 4 открытых задания
Я попробовал создать элемент списка для каждой строки, которая включает диапазон дат в виде числовых значений, а затем, чтобы проверить, есть ли перекрытие, я использовал оператор %in% для сравнения не перечисленных диапазонов
Я сделал нечто подобное, используя функцию lapply (здесь не показана), но та же проблема, выполнение которой занимает целую вечность.
##This is what I currently have
temp_list <- list()
num_open_tasks <- c()
open_work_cc <- c()
##Create a list of length = nrow(my_df)
##Each element in the list is a range of dates coerced to numeric
for(i in 1:nrow(my_df)){
temp_list[[i]] <- as.numeric(my_df$DATE_START[i]) :
as.numeric(my_df$DATE_FINISH[i])
}
for(i in 1:nrow(my_df)){
for(j in 1:nrow(my_df)){
##If elements from the temp_list overlap by 5 days, the overlap = 5
##I'm just checking if the overlap is greater than 0 (is there any overlap at all)
##And if the tasks belongs to the same person or not
open_work_cc[j] <- ifelse(sum(unlist(temp_list[[i]]) %in%
unlist(temp_list[[j]])) > 0 amp;
my_df$PERSON[i] == my_df$PERSON[j]
,1,0
)
open_work_cc_total <- sum(open_work_cc)
}
num_open_tasks[i] <- open_work_cc_total
}
my_df <- cbind(my_df, num_open_tasks)
Этот метод возвращает нужный мне столбец, заполненный правильными значениями.
Но я полагаю, что существует более элегантный и значительно более быстрый метод, использующий некоторую форму разделения / применения / объединения. Приветствуется любая помощь, спасибо
Комментарии:
1. Если вы напишете точно такую же логику на языке Julia — у вас не будет этой проблемы со скоростью.
2. … и если вы попробуете
as.numeric(my_df$DATE_START[i])
как есть, у вас возникнет много логических проблем (т.е. множествоNA
).3. Обучение Julia находится на пути к успеху! Спасибо!
Ответ №1:
foverlaps
data.table
вероятно, это будет самый быстрый подход в R. Я думаю, что следующий код делает то, что вы хотите:
library(data.table)
setDT(my_df)
my_df[, DATE_START_N:=as.numeric(as.Date(DATE_START))]
my_df[, DATE_FINISH_N:=as.numeric(as.Date(DATE_FINISH))]
setkey(my_df, PERSON, DATE_START_N,DATE_FINISH_N)
my_df[,NUMBER_OF_TASKS:=foverlaps(my_df,my_df,which=TRUE)[,.N,by=xid]$N]
my_df
Для большей ясности: foverlaps(my_df,my_df,which=TRUE)
выполняется самосоединение для диапазона дат внутри PERSON
(объединение определяется setkey
). Обратите внимание, что аргументом по умолчанию для type
объединения интервалов для foverlaps является "any"
, который является частичным совпадением по интервалам: т. Е. То, что вы хотите здесь.
Указание which=TRUE
просто предоставит индексы совпадений в x и y (а не фактические объединенные данные, которые здесь не нужны). Вызов foverlaps
возвращает объект класса data.table
, который затем немедленно агрегируется для получения количества строк в каждой группе, определенной xid
(которые являются просто строками my_df
), с использованием вызова функции bracket [,.N,by=xid]
. Эти подсчеты извлекаются в вектор с $N
и присваиваются новому столбцу NUMBER_OF_TASKS
в my_df
.
Комментарии:
1.
data.table::foverlaps
это была моя первая мысль, и, похоже, это правильный способ атаковать ее здесь (особенно сas.Date
работающими на нас :-).2. Это сработало как по волшебству! Для выполнения всего этого потребовалось около 2 секунд. Большое вам спасибо, показывает мне, что мне нужно изучить намного больше данных.таблица
3. К сожалению, я недостаточно разбираюсь в магии data.table, но это спасло мне жизнь, спасибо! Мне не нужно было группировать их по персоналиям, и поэтому я просто опустил бит person, а мне это было нужно с точностью до минуты (и мои данные в формате ymd_hms): « setkey(my_df, DATE_START, DATE_FINISH) « Для меня это сработало как чудо, суммируя количество одновременных входов в базу данных 🙂