2016-04-06 4 views
11

Angenommen, ich habe einen Datenrahmen mit einer Reihe von Daten und einer Datums-/Zeitspalte, die angibt, wann jeder Datenpunkt gesammelt wurde. Ich habe einen anderen Datenrahmen, der die Zeitspannen auflistet, wobei eine "Start" -Spalte das Datum/die Zeit angibt, wann jeder Bereich beginnt und eine "Ende" -Spalte das Datum/die Zeit anzeigt, wann jeder Bereich endet.Effiziente Möglichkeit, einen Datenrahmen nach Bereichen in einem anderen zu filtern

Ich habe ein Dummy-Beispiel unter Verwendung von vereinfachten Daten erstellt:

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

ich um dieses Problem zu lösen mit ein paar Möglichkeiten gespielt und endete mit der folgenden Lösung:

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

Diese funktioniert einwandfrei, aber mir ist aufgefallen, dass es eine Weile dauern kann, bis ich eine Menge Daten verarbeitet habe (ich nehme an, weil ich einen zeilenweisen Vergleich durchführe). Ich lerne immer noch das Hin und Her von R und ich frage mich, ob es eine effizientere Art der Durchführung dieser Operation gibt, vorzugsweise mit dplyr/tidyr?

Antwort

5

Hier ist eine Funktion, die Sie in dplyr ausführen können Daten innerhalb eines bestimmten Bereichs zu finden mit der between Funktion (von dplyr). Für jeden Wert von Day läuft mapplybetween auf jedem der Paare von Start und End Daten und die Funktion verwendet rowSumsTRUE zurückzubringen, wenn Day zwischen mindestens einer von ihnen ist. Ich bin mir nicht sicher, ob es der effizienteste Ansatz ist, aber es führt zu einer fast vierfachen Verbesserung der Geschwindigkeit.

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

Wenn Sie mit Daten arbeiten sind (und nicht mit Datum-mal), kann es sogar effizienter sein, einen Vektor von bestimmten Daten und Test für die Mitgliedschaft zu erstellen (dies könnte ein besserer Ansatz mit Datum sein, auch -mal):

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

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

Vergleichen Sie jetzt die Ausführungsgeschwindigkeiten. Ich verkürzte Code nur den Filtervorgang erfordern:

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: Unten ist ein Test mit einem viel größeren Datenrahmen und ein paar zusätzlichen Datumsbereiche (dank paßt für die Annahme, dies @Frank in seinem jetzt gelöschter Kommentar). Es stellt sich heraus, dass die Geschwindigkeitsgewinne in diesem Fall viel größer sind (etwa ein Faktor von 200 für die mapply/between Methode und noch viel größer für die zweite Methode).

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

Mit Base-R:

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

Im data.table Paket von v1.9.8 beginnend nicht-equi Verknüpfungen implementiert. Damit habe ich eine Wrapper-Funktion inrange() für genau diese Art von Operationen erstellt, wo die Aufgabe beinhaltet zu finden, ob ein Punkt in einem der vorgesehenen Intervalle liegt, und wenn ja, geben Sie TRUE, sonst 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 

Weitere Informationen finden Sie unter ?inrange.

+5

Dies wird viel schneller als die angenommene Antwort, wenn die Nachschlagetabelle wächst. – eddi

+1

Verbesserungen wurden kürzlich vorgenommen ..sollte auch bei kleineren Lookup-Tabellen schneller sein. – Arun

Verwandte Themen