Ausgabe
Ich habe ein Szenario, in dem ein Benutzer mehrere Filter auf ein Pandas DataFrame- oder Series-Objekt anwenden möchte . Im Wesentlichen möchte ich eine Reihe von Filtern (Vergleichsoperationen) effizient miteinander verketten, die zur Laufzeit vom Benutzer angegeben werden.
- Die Filter sollten additiv sein (d. h. jeder angewendete sollte die Ergebnisse eingrenzen).
- Ich verwende derzeit
reindex()
(wie unten), aber dies erstellt jedes Mal ein neues Objekt und kopiert die zugrunde liegenden Daten (wenn ich die Dokumentation richtig verstehe). Ich möchte dieses unnötige Kopieren vermeiden, da es beim Filtern einer großen Serie oder eines großen Datenrahmens wirklich ineffizient ist. - Ich denke, dass die Verwendung von
apply()
,map()
, oder etwas Ähnlichem besser sein könnte. Ich bin ziemlich neu bei Pandas, also versuche ich immer noch, meinen Kopf um alles zu wickeln. - Außerdem möchte ich dies erweitern, sodass das übergebene Wörterbuch die zu bearbeitenden Spalten enthalten und einen gesamten DataFrame basierend auf dem Eingabewörterbuch filtern kann. Ich gehe jedoch davon aus, dass alles, was für eine Serie funktioniert, problemlos auf einen DataFrame erweitert werden kann.
TL;DR
Ich möchte ein Wörterbuch der folgenden Form nehmen und jede Operation auf ein bestimmtes Series-Objekt anwenden und ein “gefiltertes” Series-Objekt zurückgeben.
relops = {'>=': [1], '<=': [1]}
Langes Beispiel
Ich beginne mit einem Beispiel dessen, was ich derzeit habe, und filtere nur ein einzelnes Series-Objekt. Unten ist die Funktion, die ich derzeit verwende:
def apply_relops(series, relops):
"""
Pass dictionary of relational operators to perform on given series object
"""
for op, vals in relops.iteritems():
op_func = ops[op]
for val in vals:
filtered = op_func(series, val)
series = series.reindex(series[filtered])
return series
Der Benutzer stellt ein Wörterbuch mit den Operationen bereit, die er ausführen möchte:
>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
col1 col2
0 0 10
1 1 11
2 2 12
>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1 1
2 2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1 1
Name: col1
Auch hier besteht das „Problem“ bei meinem obigen Ansatz darin, dass meiner Meinung nach viele möglicherweise unnötige Daten für die Zwischenschritte kopiert werden.
Lösung
Pandas (und numpy) ermöglichen eine boolesche Indizierung , die viel effizienter ist:
In [11]: df.loc[df['col1'] >= 1, 'col1']
Out[11]:
1 1
2 2
Name: col1
In [12]: df[df['col1'] >= 1]
Out[12]:
col1 col2
1 1 11
2 2 12
In [13]: df[(df['col1'] >= 1) & (df['col1'] <=1 )]
Out[13]:
col1 col2
1 1 11
Wenn Sie dafür Hilfsfunktionen schreiben möchten, sollten Sie Folgendes in Betracht ziehen:
In [14]: def b(x, col, op, n):
return op(x
,n)
In [15]: def f(x, *b):
return x[(np.logical_and(*b))]
In [16]: b1 = b(df, 'col1', ge, 1)
In [17]: b2 = b(df, 'col1', le, 1)
In [18]: f(df, b1, b2)
Out[18]:
col1 col2
1 1 11
Update: Pandas 0.13 hat eine Abfragemethode für diese Art von Anwendungsfällen, vorausgesetzt, Spaltennamen sind gültige Bezeichner, die folgenden Arbeiten (und können für große Frames effizienter sein, da sie numexpr hinter den Kulissen verwenden):
In [21]: df.query('col1 <= 1 & 1 <= col1')
Out[21]:
col1 col2
1 1 11
Beantwortet von – Andy Hayden
Antwort geprüft von – Candace Johnson (FixError Volunteer)