2016-04-06 2 views
11

Предположим, у меня есть кадр данных, содержащий кучу данных и столбец даты/времени, указывающий, когда была собрана каждая точка данных. У меня есть другой фрейм данных, в котором перечислены промежутки времени, где столбец «Старт» указывает дату/время начала каждого промежутка, а столбец «Конец» указывает дату/время окончания каждого интервала.Эффективный способ фильтрации одного кадра данных по диапазонам в другом

Я создал фиктивный пример ниже с использованием упрощенных данных:

main_data = data.frame(Day=c(1:30)) 

spans_to_filter = 
    data.frame(Span_number = c(1:6), 
       Start = c(2,7,1,15,12,23), 
       End = c(5,10,4,18,15,26)) 

Я играл вокруг с несколькими путями решения этой проблемы и в конечном итоге следующим раствором:

require(dplyr)  
filtered.main_data = 
    main_data %>% 
    rowwise() %>% 
    mutate(present = any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)) %>% 
    filter(present) %>% 
    data.frame() 

Этот работает отлично, но я заметил, что может потребоваться некоторое время для обработки, если у меня много данных (я полагаю, потому что я выполняю сравнение по ряду строк). Я все еще изучаю все возможности R, и мне было интересно, есть ли более эффективный способ выполнения этой операции, предпочтительно используя dplyr/tidyr?

ответ

5

Вот функция, которую вы можете запустить в dplyr, чтобы найти даты в пределах заданного диапазона с помощью функции between (от dplyr). Для каждого значения Day, mapply работает between на каждой из пар Start и End даты и функция использует rowSums для возврата TRUE если Day находится между, по меньшей мере, один из них. Я не уверен, что это самый эффективный подход, но это приводит к почти четвертому улучшению скорости.

test.overlap = function(vals) { 
    rowSums(mapply(function(a,b) between(vals, a, b), 
       spans_to_filter$Start, spans_to_filter$End)) > 0 
} 

main_data %>% 
    filter(test.overlap(Day)) 

Если вы работаете с датами (а не с даты-времени), это может быть даже более эффективным, чтобы создать вектор конкретных дат и теста для членства (это может быть лучшим подходом, даже с датой -раз):

filt.vals = as.vector(apply(spans_to_filter, 1, function(a) a["Start"]:a["End"])) 

main_data %>% 
    filter(Day %in% filt.vals) 

Теперь сравните скорости выполнения. Я укоротить ваш код требует только операции фильтрации:

library(microbenchmark) 

microbenchmark(
    OP=main_data %>% 
    rowwise() %>% 
    filter(any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)), 
    eipi10 = main_data %>% 
    filter(test.overlap(Day)), 
    eipi10_2 = main_data %>% 
    filter(Day %in% filt.vals) 
) 

Unit: microseconds 
    expr  min  lq  mean median  uq  max neval cld 
     OP 2496.019 2618.994 2875.0402 2701.8810 2954.774 4741.481 100 c 
    eipi10 658.941 686.933 782.8840 714.4440 770.679 2474.941 100 b 
eipi10_2 579.338 601.355 655.1451 619.2595 672.535 1032.145 100 a 

UPDATE: Ниже приведен тест с гораздо большим кадром данных и несколькими дополнительных диапазонами даты, чтобы соответствовать (спасибо @Frank предложившему это в его теперь удаленный комментарий). Оказывается, что в этом случае прирост скорости намного больше (примерно в 200 раз для метода mapply/between и еще гораздо больше для второго метода).

main_data = data.frame(Day=c(1:100000)) 

spans_to_filter = 
    data.frame(Span_number = c(1:9), 
      Start = c(2,7,1,15,12,23,90,9000,50000), 
      End = c(5,10,4,18,15,26,100,9100,50100)) 

microbenchmark(
    OP=main_data %>% 
    rowwise() %>% 
    filter(any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)), 
    eipi10 = main_data %>% 
    filter(test.overlap(Day)), 
    eipi10_2 = { 
    filt.vals = unlist(apply(spans_to_filter, 1, function(a) a["Start"]:a["End"])) 
    main_data %>% 
     filter(Day %in% filt.vals)}, 
    times=10 
) 

Unit: milliseconds 
    expr   min   lq  mean  median   uq   max neval cld 
     OP 5130.903866 5137.847177 5201.989501 5216.840039 5246.961077 5276.856648 10 b 
    eipi10 24.209111 25.434856 29.526571 26.455813 32.051920 48.277326 10 a 
eipi10_2 2.505509 2.618668 4.037414 2.892234 6.222845 8.266612 10 a 
1

Использование базы R:

main_data[unlist(lapply(main_data$Day, 
    function(x) any(x >= spans_to_filter$Start & x <= spans_to_filter$End))),] 
13

В data.table пакета, начиная с v1.9.8, без оборудов объединений были реализованы. При этом я создал функцию-обертку inrange() для таких операций, где задача заключается в поиске, если точка лежит в любом из предоставленных интервалов, и если так возвращаются TRUE, иначе FALSE.

require(data.table) # v>=1.9.8 
setDT(main_data)[Day %inrange% spans_to_filter[, 2:3]] # inclusive bounds 
#  Day 
# 1: 1 
# 2: 2 
# 3: 3 
# 4: 4 
# 5: 5 
# 6: 7 
# 7: 8 
# 8: 9 
# 9: 10 
# 10: 12 
# 11: 13 
# 12: 14 
# 13: 15 
# 14: 16 
# 15: 17 
# 16: 18 
# 17: 23 
# 18: 24 
# 19: 25 
# 20: 26 

См. ?inrange для получения дополнительной информации.

+5

Это становится быстрее, чем принятый ответ по мере роста таблицы поиска. – eddi

+1

Усовершенствования были сделаны недавно ..должен быть быстрее и на небольших таблицах поиска. – Arun

Смежные вопросы