2016-04-06 2 views
11

Diciamo che ho un frame di dati contenente un mucchio di dati e una colonna data/ora che indica quando ogni punto di dati è stato raccolto. Ho un altro frame di dati che elenca gli intervalli temporali, in cui una colonna "Start" indica la data/ora in cui ogni span inizia e una colonna "End" indica la data/ora in cui ogni span termina.Modo efficiente per filtrare un frame di dati per intervalli in un altro

ho creato un esempio fittizio di seguito utilizzando i dati semplificata:

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

Ho giocato in giro con un paio di modi per risolvere questo problema e finito con la seguente soluzione:

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

Questo funziona perfettamente, ma ho notato che può richiedere un po 'di tempo per elaborare se ho un sacco di dati (presumo perché sto eseguendo un confronto riga-saggio). Sto ancora imparando i dettagli di R e mi stavo chiedendo se c'è un modo più efficiente per eseguire questa operazione, preferibilmente usando dplyr/tidyr?

risposta

5

Ecco una funzione che è possibile eseguire in dplyr per trovare le date in un determinato intervallo utilizzando la funzione between (da dplyr). Per ogni valore di Day, mapply eseguito between su ciascuna delle coppie di Start e End date e la funzione utilizza rowSums per restituire TRUE se Day è tra almeno uno di loro. Non sono sicuro che sia l'approccio più efficiente, ma si traduce in un miglioramento di quasi quattro volte in termini di velocità.

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

Se si lavora con date (piuttosto che con la data di volte), può essere ancora più efficace per creare un vettore di date e di prova per l'adesione specifici (questo potrebbe essere un approccio migliore, anche con la data -times):

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

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

Ora confrontare le velocità di esecuzione. Ho accorciato il codice per richiedere solo l'operazione di filtraggio:

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: Qui di seguito è un test con una molto più grande cornice di dati e qualche data in più intervalli per abbinare (grazie a @Frank per aver suggerito questo nel suo commento ora eliminato). Si scopre che i guadagni di velocità sono molto maggiori in questo caso (circa un fattore di 200 per il metodo mapply/between e molto altro ancora per il secondo metodo).

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

Utilizzando Base R:

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

Nel pacchetto data.table partire da v1.9.8, non equi join è stato implementato. Con questo, ho creato una funzione wrapper inrange() esattamente per questo tipo di operazioni, in cui l'attività consiste nel trovare se un punto si trova in uno degli intervalli forniti, e in tal caso restituire TRUE, altrimenti 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 

Vedere ?inrange per ulteriori informazioni.

+5

Questo diventa molto più veloce della risposta accettata man mano che la tabella di ricerca cresce. – eddi

+1

Sono stati apportati miglioramenti di recente ..dovrebbe essere più veloce anche su tabelle di ricerca più piccole. – Arun