2016-07-05 13 views
4

ich zwei Pandas Dataframe-Objekte haben:Effizientes überprüfen, ob Wert in einem der angegebenen Bereiche ist

  • A enthält 'start' und 'finish' Spalten

  • B hat Spalte 'date'

Das Ziel besteht darin, effizient eine boolesche Maske zu erstellen, die angibt, ob date ist in [start, finish] Intervall

Naive Iterieren zu viel Zeit, ich denke, es gibt eine Methode ist, dass schneller

UPDATE zu tun: A und B haben unterschiedliche Anzahl von Zeilen

UPDATE2: Beispiel:

A 
    | start  | finish | 
    |------- |-------- | 
    | 1   | 3   | 
    | 50  | 83  | 
    | 30  | 42  | 

B 
    | date  | 
    |------- | 
    | 31  | 
    | 20  | 
    | 2.5  | 
    | 84  | 
    | 1000  | 

Output: 
      | in_interval | 
      |------- | 
      | True  | 
      | False  | 
      | True  | 
      | False  | 
      | False  | 

PS Ich habe meine Daten im Datetime-Format, aber ich denke, dass die Lösung nicht von einem für Zahlen unterscheidet.

+0

so fordern Sie für 'df [(df [ 'date']> df [‘ start ']) & (df [' date '] EdChum

+1

Möchten Sie es elementweise machen (dh Sie benötigen Datenrahmen gleicher Größe)? –

+0

nein, das sind zwei separate Datenrahmen mit unterschiedlicher Anzahl von Zeilen, so dass dieser nicht funktioniert Ich denke – MaxPY

Antwort

4

Sie können es mit einem O (n) Komplexität. Die Idee ist, die Darstellung zu transformieren. In A speichern Sie eine Zeile pro Intervall. Ich würde einen Datenrahmen vorschlagen, der eine Zeile pro Übergang speichert (dh ein Intervall eingeben, ein Intervall lassen).

A = pd.DataFrame(
    data={ 
     'start': [1, 50, 30], 
     'finish': [3, 83, 42]  
    } 
) 

starts = pd.DataFrame(data={'start': 1}, index=A.start.tolist()) 
finishs = pd.DataFrame(data={'finish': -1}, index=A.finish.tolist()) 
transitions = pd.merge(starts, finishs, how='outer', left_index=True, right_index=True).fillna(0) 
transitions 

    start finish 
1  1  0 
3  0  -1 
30  1  0 
42  0  -1 
50  1  0 
83  0  -1 

Dieser Datenrahmen speichert nach Datum die Art der Übergänge. Jetzt müssen wir zu jedem Zeitpunkt wissen, ob wir in einem Intervall sind oder nicht. Es sieht so aus, als würde man die öffnende & schließende Klammer zählen. Sie tun können:

transitions['transition'] = (transitions.pop('finish') + transitions.pop('start')).cumsum() 
transitions 

    transition 
1   1 
3   0 
30   1 
42   0 
50   1 
83   0 

Hier heißt es:

  • Bei 1, i in einem Intervall bin
  • Bei 3, ich bin nicht
  • Im Allgemeinen, wenn der Wert streng größer als 0, ist es in einem Intervall.Hinweis
  • , dass diese Griffe überlappende Intervall

Und jetzt Sie verschmelzen mit Ihrem B Datenrahmen:

B = pd.DataFrame(
    index=[31, 20, 2.5, 84, 1000] 
) 

pd.merge(transitions, B, how='outer', left_index=True, right_index=True).fillna(method='ffill').loc[B.index].astype(bool) 

     transition 
31.0   True 
20.0  False 
2.5   True 
84.0  False 
1000.0  False 
+0

wow! Das ist schlau! – MaxU

+0

Ich mag deine Lösung wirklich! Gibt es eine Möglichkeit, 'Finish' als eine geschlossene Intervallgrenze zu betrachten? I.e. wenn wir 'B' den Wert' 83' hinzufügen - wird es nicht als zum '[50, 83]' Bereich gehörend betrachtet - wissen Sie, wie man damit umgeht? – MaxU

+0

Ja, in der ersten Zusammenführung wird der Index in aufsteigender Reihenfolge sortiert. Wenden Sie dieselbe Logik mit einer Nachkommenfolge an. Am Ende haben Sie einen Übergang für geschlossen-geöffnete Intervalle und einen Übergang für geöffnet-geschlossene Intervalle. geschlossen-geschlossen bedeutet "jeder von ihnen". –

1

IIUC wollen Sie die Ausgabe True, wenn es mindestens ein Intervall gibt, in dem das Datum ist?

Ist ein apply(lambda) effizient genug für Sie? (Es kann ein wenig lang für einen großen Datenrahmen sein, da es über die Zeilen von B iteriert). Wenn ja, können Sie dies versuchen:

def in_range(date,start,finish): 
    return (True in ((start < date) & (date < finish)).unique()) 

B.date.apply(lambda x: in_range(x,A.start,A.finish)) 

Ausgang:

0  True 
1 False 
2  True 
3 False 
4 False 

EDIT: MAXU Antwort funktioniert besser in der Tat. Hier sind die Zeitgeber für 10 000 Zeilen Datenrahmen (A und B):

%timeit B2.date.apply(lambda x: in_range(x,A2.start,A2.finish)) 
1 loop, best of 3: 9.82 s per loop 

%timeit B2.date.apply(lambda x: ((x >= A2.start) & (x <= A2.finish)).any()) 
1 loop, best of 3: 7.31 s per loop 
+0

ich denke, Sie können es ein wenig vereinfachen: 'B.date.apply (Lambda x: ((x> = a.start) &) (x <= a.finish)). any()) ' – MaxU

+0

Vielen Dank, ich habe diesen Ansatz benutzt, der zu viel Zeit in meinem 100mb Datensatz benötigt – MaxPY

Verwandte Themen