Более эффективный метод подсчета открытых дел по времени создания каждого дела
Я пытаюсь найти более эффективный способ подсчета количества дел, открытых на момент создания каждого дела. Дело "открыто" между его датой / временем создания и датой / временем цензуры. Вы можете скопировать и вставить приведенный ниже код для просмотра простого функционального примера:
# Create a bunch of date/time stamps for our example
two_thousand <- as.POSIXct("2000-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_one <- as.POSIXct("2001-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_two <- as.POSIXct("2002-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_three <- as.POSIXct("2003-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_four <- as.POSIXct("2004-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_five <- as.POSIXct("2005-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_six <- as.POSIXct("2006-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_seven <- as.POSIXct("2007-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_eight <- as.POSIXct("2008-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_nine <- as.POSIXct("2009-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_ten <- as.POSIXct("2010-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
two_thousand_eleven <- as.POSIXct("2011-01-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand <- as.POSIXct("2000-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_one <- as.POSIXct("2001-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_mid_two <- as.POSIXct("2002-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_three <- as.POSIXct("2003-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_four <- as.POSIXct("2004-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_five <- as.POSIXct("2005-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_six <- as.POSIXct("2006-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_seven <- as.POSIXct("2007-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_eight <- as.POSIXct("2008-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_nine <- as.POSIXct("2009-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_ten <- as.POSIXct("2010-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
mid_two_thousand_eleven <- as.POSIXct("2011-06-01 00:00:00", format="%Y-%m-%d %H:%M:%S", tz="UTC", origin="1970-01-01");
# Create a table that has pairs of created & censored date/time stamps for cases, indicating the range during which each case is "open"
comparison_table <- data.table(id = 1:10,
created = c(two_thousand, two_thousand_two, two_thousand_four, two_thousand_six, two_thousand_eight, two_thousand_ten, two_thousand, two_thousand_six, two_thousand_three, two_thousand_one),
censored = c(two_thousand_one, two_thousand_three, two_thousand_five, two_thousand_seven, two_thousand_nine, two_thousand_eleven, two_thousand_five, two_thousand_ten, two_thousand_eight, two_thousand_four));
# Create a table that has the creation date/time stamps at which we want to count all the open cases
check_table <- data.table(id = 1:12,
creation = c(mid_two_thousand, mid_two_thousand_one, mid_two_thousand_mid_two, mid_two_thousand_three, mid_two_thousand_four, mid_two_thousand_five, mid_two_thousand_six, mid_two_thousand_seven, mid_two_thousand_eight, mid_two_thousand_nine, mid_two_thousand_ten, mid_two_thousand_eleven));
# I use the DPLYR library as the group_by() + summarize() functions make this operation simple
library(dplyr);
# Group by id to set parameter for summarize() function
check_table_grouped <- group_by(check_table, id);
# For each id in the table, sum the number of times that its creation date/time stamp is greater than the creation date/time and less than the censor date/time of all cases in the comparison table
# EDIT: Also added timing to compare with method below
system.time(check_table_summary <- summarize(check_table_grouped, other_open_values_at_creation_count = sum((comparison_table$created < creation & comparison_table$censored > creation))));
# Result is as desired
check_table_summary;
# EDIT: Added @David-arenburg's solution with timing
library(data.table);
setDT(check_table)[, creation2 := creation];
setkey(comparison_table, created, censored);
system.time(foverlaps_table <- foverlaps(check_table, comparison_table, by.x = c("creation", "creation2"))[, sum(!is.na(id)), by = i.id]);
# Same results as above
foverlaps_table;
Этот подход прекрасно работает для небольших наборов данных, таких как в этом примере. Однако, несмотря на то, что я использую векторизованные операции, время вычислений растет в геометрической прогрессии, потому что количество операций составляет: (3 * сравнение nrow) * (вычисления nrow sum(nrow)). При nrow=10000 время составляет около 14 секунд, при nrow=100000 время составляет> 20 минут. Мой фактический nrow ~ 1 000 000.
Есть ли более эффективный способ сделать этот расчет? В настоящее время я изучаю многоядерные варианты, но даже они будут только линейно сокращать время выполнения. Ваша помощь ценится. Спасибо!
РЕДАКТИРОВАТЬ: Добавлено @ Дэвид-Аренбург data.table::foverlaps
решение, которое также работает и быстрее для nrow < 1000. Однако оно медленнее, чем summarize
решение для большего количества строк. На 10000 строк это было в два раза длиннее. На 50 000 строк я перестал ждать после 10 раз дольше. Интересно, что foverlaps
Похоже, что решение не запускает автоматическую сборку мусора, поэтому постоянно использует максимальный объем ОЗУ (64 ГБ в моей системе), тогда как summarize
Решение периодически запускает автоматический сбор мусора, поэтому никогда не превышает ~ 40 ГБ ОЗУ. Я не уверен, связано ли это с разницей в скорости.
ЗАКЛЮЧИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ: я переписал вопрос таким образом, чтобы респондентам было намного проще создавать большие таблицы с подходящими созданными / подвергнутыми цензуре датами времени. Я также упростил и объяснил проблему более четко, пояснив, что таблица поиска очень большая (нарушает data.table::foverlaps
предположения). Я даже встроил сравнение по времени, чтобы сделать его очень простым для тестирования с большими случаями! Подробности здесь: Эффективный метод подсчета открытых случаев во время подачи каждого случая в большом наборе данных
Еще раз спасибо за вашу помощь всем!:)
1 ответ
Еще один foverlaps
решение. При условии, что comparison_table
не слишком большой
library(data.table);
setkey(comparison_table, created, censored);
times <- sort(unique(c(comparison_table$created, comparison_table$censored)))
dt <- data.table(creation=times+1)[, creation2 := creation];
setkey(dt, creation, creation2)
x <- foverlaps(comparison_table, dt, by.x = c("created", "censored"))[,.N,creation]$N
check_table$newcol <- x[findInterval(check_table$creation, times)]