2016-04-06 18 views
11

Giả sử tôi có một khung dữ liệu chứa một loạt dữ liệu và cột ngày/giờ cho biết khi nào mỗi điểm dữ liệu được thu thập. Tôi có một khung dữ liệu khác liệt kê khoảng thời gian, trong đó cột "Bắt đầu" cho biết ngày/giờ khi mỗi khoảng thời gian bắt đầu và cột "Kết thúc" cho biết ngày/giờ khi mỗi khoảng thời gian kết thúc.Cách hiệu quả để lọc một khung dữ liệu theo các phạm vi khác

tôi đã tạo ra một ví dụ giả dưới đây sử dụng dữ liệu đơn giản:

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)) 

tôi đùa giỡn xung quanh với một số cách giải quyết vấn đề này và đã kết thúc với các giải pháp sau đây:

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() 

này hoạt động hoàn toàn tốt, nhưng tôi nhận thấy nó có thể mất một thời gian để xử lý nếu tôi có nhiều dữ liệu (tôi giả sử vì tôi đang thực hiện so sánh hàng khôn ngoan). Tôi vẫn đang học hỏi về R-and-outs của R và tôi đã tự hỏi nếu có một cách hiệu quả hơn để thực hiện thao tác này, tốt nhất là sử dụng dplyr/tidyr?

Trả lời

5

Dưới đây là một chức năng mà bạn có thể chạy trong dplyr để tìm số ngày trong một phạm vi nhất định bằng cách sử dụng chức năng between (từ dplyr). Đối với mỗi giá trị của Day, mapply chạy between trên mỗi cặp StartEnd ngày và chức năng sử dụng rowSums trở TRUE nếu Day là giữa ít nhất một trong số họ. Tôi không chắc đó có phải là cách tiếp cận hiệu quả nhất hay không, nhưng nó cho kết quả gần như là một yếu tố cải thiện tốc độ.

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)) 

Nếu bạn đang làm việc với các ngày (chứ không phải với ngày-times), nó có thể thậm chí hiệu quả hơn để tạo ra một vector của ngày và thử nghiệm cho các thành viên cụ thể (điều này có thể là một cách tiếp cận tốt hơn ngay cả với ngày -times):

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

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

Bây giờ so sánh tốc độ thực hiện. Tôi rút ngắn mã của bạn để yêu cầu chỉ hoạt động lọc:

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: Dưới đây là một thử nghiệm với một khung dữ liệu lớn hơn nhiều và một vài ngày thêm khoảng để phù hợp (nhờ @Frank cho gợi ý này trong mình nhận xét đã bị xóa). Nó chỉ ra rằng tốc độ tăng là lớn hơn nhiều trong trường hợp này (khoảng một yếu tố là 200 cho phương pháp mapply/between, và vẫn còn lớn hơn cho phương pháp thứ hai).

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

Sử dụng cơ sở R:

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

Trong gói data.table bắt đầu từ v1.9.8, phi equi tham gia đã được triển khai. Với điều này, tôi đã tạo ra một hàm bao bọc inrange() cho chính xác các loại hoạt động này, trong đó nhiệm vụ liên quan đến việc tìm kiếm một điểm nằm trong bất kỳ khoảng thời gian nào được cung cấp hay không và nếu trả lại TRUE, khác 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 

Xem ?inrange để biết thêm.

+5

Điều này trở nên nhanh hơn câu trả lời được chấp nhận khi bảng tra cứu tăng lên. – eddi

+1

Các cải tiến đã được thực hiện gần đây ..nên nhanh hơn trên các bảng tra cứu nhỏ hơn. – Arun

Các vấn đề liên quan