Ich habe ein Szenario, in dem ein Benutzer mehrere Filter auf ein Pandas DataFrame- oder Serienobjekt 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 (auch bekannt als sollte jeder angewendete Filter die Ergebnisse einschränken).
Ich benutze gerade reindex()
aber dies erstellt jedes Mal ein neues Objekt und kopiert die zugrunde liegenden Daten (wenn ich die Dokumentation richtig verstehe). Dies kann also beim Filtern einer großen Serie oder eines DataFrames sehr ineffizient sein.
Ich denke das mit apply()
, map()
oder etwas Ähnliches könnte besser sein. Ich bin ziemlich neu bei Pandas, versuche aber immer noch, meinen Kopf um alles zu wickeln.
TL; DR
Ich möchte ein Wörterbuch der folgenden Form nehmen und jede Operation auf ein bestimmtes Serienobjekt anwenden und ein 'gefiltertes' Serienobjekt zurückgeben.
relops = {'>=': [1], '<=': [1]}
Langes Beispiel
Ich beginne mit einem Beispiel für das, was ich derzeit habe, und filtere nur ein einzelnes Serienobjekt. Unten ist die Funktion, die ich gerade benutze:
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
Wiederum besteht das "Problem" bei meinem obigen Ansatz darin, dass ich denke, dass es eine Menge möglicherweise unnötiger Kopien der Daten für die Zwischenschritte gibt.
Außerdem möchte ich dies erweitern, damit das übergebene Wörterbuch die zu operierenden Spalten enthalten und einen gesamten DataFrame basierend auf dem Eingabewörterbuch filtern kann. Ich gehe jedoch davon aus, dass alles, was für die Serie funktioniert, problemlos zu einem DataFrame erweitert werden kann.
df.query
undpd.eval
scheinen gut zu Ihrem Anwendungsfall zu passen. Informationen zurpd.eval()
Funktionsfamilie, ihren Funktionen und Anwendungsfällen finden Sie unter Auswertung dynamischer Ausdrücke in Pandas mit pd.eval () .Antworten:
Pandas (und Numpy) ermöglichen eine boolesche Indizierung , die viel effizienter ist:
Wenn Sie dazu Hilfsfunktionen schreiben möchten, sollten Sie Folgendes berücksichtigen:
Update: pandas 0.13 verfügt über eine Abfragemethode für diese Art von Anwendungsfällen. Unter der Annahme, dass Spaltennamen gültige Bezeichner sind, funktioniert dies wie folgt (und kann für große Frames effizienter sein, da hinter den Kulissen numexpr verwendet wird ):
quelle
df[(ge(df['col1'], 1) & le(df['col1'], 1)]
. Das Problem für mich ist wirklich, dass das Wörterbuch mit den Filtern viele Operatoren enthalten kann und das Verketten dieser umständlich ist. Vielleicht könnte ich jedes boolesche Zwischenarray einem großen Array hinzufügen und dann einfachmap
denand
Operator auf sie anwenden ?f()
ergreifen müssen , um*b
gerade stattb
? Ist dies so, dass Benutzer vonf()
weiterhin den optionalenout
Parameter verwenden könnenlogical_and()
? Dies führt zu einer weiteren kleinen Nebenfrage. Was ist der Leistungsvorteil / Kompromiss bei der Übergabe des Arrays über dieout()
Verwendung des zurückgegebenen Arrayslogical_and()
? Danke noch einmal!*b
ist erforderlich, da Sie die beiden Arrays übergebenb1
undb2
sie beim Aufruf entpacken müssenlogical_and
. Die andere Frage bleibt jedoch offen. Gibt es einen Leistungsvorteil bei der Übergabe eines Arrays über einenout
Parameter anlogical_and()
vs , wenn nur der Rückgabewert verwendet wird?Verkettungsbedingungen erzeugen lange Linien, von denen pep8 abhält. Die Verwendung der .query-Methode erzwingt die Verwendung von Zeichenfolgen, die leistungsstark, aber unpythonisch und nicht sehr dynamisch sind.
Sobald jeder der Filter vorhanden ist, ist ein Ansatz
np.logical arbeitet weiter und ist schnell, akzeptiert jedoch nicht mehr als zwei Argumente, die von functools.reduce verarbeitet werden.
Beachten Sie, dass dies immer noch einige Redundanzen aufweist: a) Verknüpfungen treten nicht auf globaler Ebene auf. B) Jede der einzelnen Bedingungen wird für die gesamten Anfangsdaten ausgeführt. Trotzdem erwarte ich, dass dies für viele Anwendungen effizient genug ist und sehr gut lesbar ist.
quelle
c_1
,c_2
,c_3
, ...c_n
in einer Liste, und dann vorbei ,data[conjunction(conditions_list)]
aber bekommen einen FehlerValueError: Item wrong length 5 instead of 37.
auch versucht ,data[conjunction(*conditions_list)]
aber ich habe ein anderes Ergebnis alsdata[conjunction(c_1, c_2, c_3, ... c_n )]
nicht sicher , was los ist.data[conjunction(*conditions_list)]
funktioniert nach dem Packen der Datenrahmen in eine Liste und dem Entpacken der Liste an Ortdf[f_2 & f_3 & f_4 & f_5 ]
mitf_2 = df["a"] >= 0
usw. Keine Notwendigkeit für diese Funktion ... (nette Verwendung der Funktion höherer Ordnung obwohl ...)Einfachste aller Lösungen:
Verwenden:
Ein weiteres Beispiel : Verwenden Sie den folgenden Code, um den Datenrahmen nach Werten zu filtern, die zum Februar 2018 gehören
quelle
Seit dem Update von pandas 0.22 stehen Vergleichsoptionen zur Verfügung:
und viele mehr. Diese Funktionen geben ein boolesches Array zurück. Mal sehen, wie wir sie nutzen können:
quelle
Warum nicht?
Demo:
Ergebnis:
Sie können sehen, dass die Spalte 'a' gefiltert wurde, wobei a> = 2 ist.
Dies ist etwas schneller (Eingabezeit, keine Leistung) als die Verkettung des Bedieners. Sie können den Import natürlich ganz oben in die Datei einfügen.
quelle
e kann auch Zeilen basierend auf Werten einer Spalte auswählen, die nicht in einer Liste enthalten sind oder iterierbar sind. Wir werden wie zuvor eine boolesche Variable erstellen, aber jetzt werden wir die boolesche Variable negieren, indem wir ~ vorne platzieren.
Beispielsweise
quelle