Ich habe viele Antworten auf Fragen zum Stapelüberlauf gesehen, die die Verwendung der Pandas-Methode betreffen apply
. Ich habe auch Benutzer gesehen, die unter ihnen kommentierten und sagten: "apply
langsam ist und vermieden werden sollte".
Ich habe viele Artikel zum Thema Leistung gelesen, die erklären, dass apply
es langsam ist. Ich habe auch einen Haftungsausschluss in den Dokumenten darüber gesehen, wie apply
einfach eine praktische Funktion zum Übergeben von UDFs ist (kann das jetzt anscheinend nicht finden). Der allgemeine Konsens ist also, dass dies apply
nach Möglichkeit vermieden werden sollte. Dies wirft jedoch folgende Fragen auf:
- Wenn
apply
es so schlecht ist, warum ist es dann in der API? - Wie und wann soll ich meinen Code machen
apply
? - Gibt es jemals Situationen, in denen
apply
es gut ist (besser als andere mögliche Lösungen)?
python
pandas
performance
apply
cs95
quelle
quelle
returns.add(1).apply(np.log)
vs.np.log(returns.add(1)
ist ein Fall, in dem dieapply
Geschwindigkeit im Allgemeinen geringfügig schneller ist. Dies ist das grüne Feld unten rechts im Diagramm von jpp unten.Antworten:
apply
, die Komfortfunktion, die Sie nie gebraucht habenWir beginnen damit, die Fragen im OP einzeln zu beantworten.
DataFrame.apply
undSeries.apply
sind Komfortfunktionen, die für DataFrame- bzw. Serienobjekte definiert sind.apply
Akzeptiert alle benutzerdefinierten Funktionen, die eine Transformation / Aggregation auf einen DataFrame anwenden.apply
ist praktisch eine Silberkugel, die alles tut, was eine bestehende Pandas-Funktion nicht kann.Einige der Dinge
apply
können tun:axis=1
) oder spaltenweise (axis=0
) auf einen DataFrame anagg
odertransform
in diesen Fällen)result_type
Argument)....Unter anderen. Weitere Informationen finden Sie in der Dokumentation unter Zeilen- oder spaltenweise Funktionsanwendung .
Warum ist es bei all diesen Funktionen
apply
schlecht? Es ist, weilapply
es langsam ist . Pandas macht keine Annahmen über die Art Ihrer Funktion und wendet Ihre Funktion daher bei Bedarf iterativ auf jede Zeile / Spalte an. Darüber hinaus bedeutet die Behandlung aller oben genannten Situationen, dassapply
bei jeder Iteration ein erheblicher Aufwand entsteht. Fernerapply
verbraucht Speicher viel mehr, was eine Herausforderung für Speicher begrenzt Anwendungen ist.Es gibt nur sehr wenige Situationen, in denen
apply
die Verwendung angemessen ist (mehr dazu weiter unten). Wenn Sie nicht sicher sind, ob Sie verwenden solltenapply
, sollten Sie wahrscheinlich nicht.Lassen Sie uns die nächste Frage beantworten.
Um es neu zu formulieren, hier sind einige häufige Situationen, in denen Sie alle Anrufe an loswerden möchten
apply
.Numerische Daten
Wenn Sie mit numerischen Daten arbeiten, gibt es wahrscheinlich bereits eine vektorisierte Cython-Funktion, die genau das tut, was Sie versuchen (wenn nicht, stellen Sie entweder eine Frage zum Stapelüberlauf oder öffnen Sie eine Funktionsanforderung auf GitHub).
Vergleichen Sie die Leistung
apply
für eine einfache Additionsoperation.In Bezug auf die Leistung gibt es keinen Vergleich, das cythonisierte Äquivalent ist viel schneller. Es ist kein Diagramm erforderlich, da der Unterschied selbst für Spielzeugdaten offensichtlich ist.
Selbst wenn Sie das Übergeben von Raw-Arrays mit dem
raw
Argument aktivieren , ist es immer noch doppelt so langsam.Ein anderes Beispiel:
Suchen Sie im Allgemeinen nach vektorisierten Alternativen, wenn dies möglich ist.
String / Regex
Pandas bietet in den meisten Situationen "vektorisierte" Zeichenfolgenfunktionen, aber es gibt seltene Fälle, in denen diese Funktionen sozusagen nicht ... "zutreffen".
Ein häufiges Problem besteht darin, zu überprüfen, ob ein Wert in einer Spalte in einer anderen Spalte derselben Zeile vorhanden ist.
Dies sollte die zweite und dritte Zeile der Zeile zurückgeben, da "Donald" und "Minnie" in ihren jeweiligen "Titel" -Spalten vorhanden sind.
Mit apply würde dies mit using erfolgen
Es gibt jedoch eine bessere Lösung, wenn Listenverständnisse verwendet werden.
Hierbei ist zu beachten, dass iterative Routinen
apply
aufgrund des geringeren Overheads schneller sind als . Wenn Sie mit NaNs und ungültigen dtypes umgehen müssen, können Sie darauf mit einer benutzerdefinierten Funktion aufbauen, die Sie dann mit Argumenten innerhalb des Listenverständnisses aufrufen können.Weitere Informationen darüber, wann Listenverständnisse als gute Option angesehen werden sollten, finden Sie in meinem Artikel: Für Schleifen mit Pandas - Wann sollte es mich interessieren? .
Eine häufige Gefahr: Explodierende Spalten von Listen
Menschen sind versucht zu benutzen
apply(pd.Series)
. Das ist schrecklich in Bezug auf die Leistung.Eine bessere Option besteht darin, die Spalte aufzulisten und an pd.DataFrame zu übergeben.
Zuletzt,
Anwenden ist eine praktische Funktion, daher gibt es Situationen, in denen der Overhead vernachlässigbar genug ist, um zu vergeben. Es hängt wirklich davon ab, wie oft die Funktion aufgerufen wird.
Funktionen, die für Serien vektorisiert sind, jedoch keine DataFrames
Was ist, wenn Sie eine Zeichenfolgenoperation auf mehrere Spalten anwenden möchten? Was ist, wenn Sie mehrere Spalten in datetime konvertieren möchten? Diese Funktionen sind nur für Serien vektorisiert, daher müssen sie auf jede Spalte angewendet werden, die Sie konvertieren / bearbeiten möchten.
Dies ist ein zulässiger Fall für
apply
:Beachten Sie, dass es auch sinnvoll wäre,
stack
eine explizite Schleife zu verwenden oder einfach nur zu verwenden. Alle diese Optionen sind etwas schneller als die Verwendungapply
, aber der Unterschied ist klein genug, um zu vergeben.Sie können einen ähnlichen Fall für andere Operationen wie Zeichenfolgenoperationen oder die Konvertierung in eine Kategorie festlegen.
v / s
Und so weiter...
Konvertieren von Serien in
str
:astype
versusapply
Dies scheint eine Eigenart der API zu sein. Die Verwendung
apply
zum Konvertieren von Ganzzahlen in einer Serie in eine Zeichenfolge ist vergleichbar (und manchmal schneller) als die Verwendungastype
.Das Diagramm wurde unter Verwendung der
perfplot
Bibliothek aufgezeichnet .Bei Schwimmern sehe ich, dass das
astype
durchweg so schnell oder etwas schneller ist alsapply
. Dies hat also damit zu tun, dass die Daten im Test vom Typ Integer sind.GroupBy
Operationen mit verketteten TransformationenGroupBy.apply
wurde bisher noch nicht besprochen, ist aberGroupBy.apply
auch eine iterative Komfortfunktion, um alles zu handhaben, was die vorhandenenGroupBy
Funktionen nicht tun.Eine häufige Anforderung besteht darin, einen GroupBy und dann zwei Hauptoperationen durchzuführen, z. B. einen "verzögerten Cumsum":
Sie benötigen hier zwei aufeinanderfolgende Gruppenanrufe:
Mit
apply
können Sie dies auf einen einzelnen Anruf verkürzen.Es ist sehr schwierig, die Leistung zu quantifizieren, da dies von den Daten abhängt. Aber im Allgemeinen
apply
ist eine akzeptable Lösung, wenn das Ziel darin besteht, einengroupby
Anruf zu reduzieren (weilgroupby
es auch ziemlich teuer ist).Andere Vorsichtsmaßnahmen
Abgesehen von den oben genannten Einschränkungen ist es auch erwähnenswert, dass
apply
die erste Zeile (oder Spalte) zweimal ausgeführt wird. Dies wird durchgeführt, um festzustellen, ob die Funktion irgendwelche Nebenwirkungen hat. Wenn nicht,apply
kann möglicherweise ein schneller Pfad zur Auswertung des Ergebnisses verwendet werden, andernfalls wird auf eine langsame Implementierung zurückgegriffen.Dieses Verhalten tritt auch bei
GroupBy.apply
Pandas-Versionen <0,25 auf (es wurde für 0,25 behoben, siehe hier für weitere Informationen .)quelle
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')
Sicherheit wird es nach der ersten Iteration viel schneller gehen, da Siedatetime
zu ... konvertierendatetime
.to_datetime
von Zeichenfolgen ist so schnell wie das Aufrufen von ...datetime
Objekten" .. wirklich? Ich enthalten Dataframe - Erstellung (Fixkosten) inapply
vsfor
Schleife Timings und der Unterschied ist viel kleiner.Nicht alle
apply
sind gleichDie folgende Tabelle zeigt, wann
apply
1 zu berücksichtigen ist . Grün bedeutet möglicherweise effizient; rot vermeiden.Einiges davon ist intuitiv: Es
pd.Series.apply
handelt sich um eine zeilenweise Schleife auf Python-Ebene, ebensopd.DataFrame.apply
zeilenweise (axis=1
). Die Missbräuche sind vielfältig und weitreichend. Der andere Beitrag befasst sich eingehender mit ihnen. Beliebte Lösungen sind die Verwendung vektorisierter Methoden, Listenverständnisse (setzt saubere Daten voraus) oder effiziente Tools wie derpd.DataFrame
Konstruktor (zapply(pd.Series)
. B. zur Vermeidung ).Wenn Sie
pd.DataFrame.apply
zeilenweise verwenden, ist die Angaberaw=True
(soweit möglich) häufig von Vorteil. In diesem Stadiumnumba
ist in der Regel eine bessere Wahl.GroupBy.apply
: allgemein bevorzugtWiederholte
groupby
Vorgänge zur Vermeidungapply
beeinträchtigen die Leistung.GroupBy.apply
ist hier normalerweise in Ordnung, vorausgesetzt, die Methoden, die Sie in Ihrer benutzerdefinierten Funktion verwenden, sind selbst vektorisiert. Manchmal gibt es keine native Pandas-Methode für eine gruppenweise Aggregation, die Sie anwenden möchten. In diesem Fall bietet eine kleine Anzahl von Gruppenapply
mit einer benutzerdefinierten Funktion möglicherweise immer noch eine angemessene Leistung.pd.DataFrame.apply
säulenweise: ein gemischter Beutelpd.DataFrame.apply
Spaltenweise (axis=0
) ist ein interessanter Fall. Für eine kleine Anzahl von Zeilen gegenüber einer großen Anzahl von Spalten ist es fast immer teuer. Bei einer großen Anzahl von Zeilen im Verhältnis zu Spalten, dem häufigeren Fall, können manchmal signifikante Leistungsverbesserungen auftreten , wennapply
:1 Es gibt Ausnahmen, aber diese sind normalerweise marginal oder ungewöhnlich. Einige Beispiele:
df['col'].apply(str)
kann leicht übertreffendf['col'].astype(str)
.df.apply(pd.to_datetime)
Das Arbeiten an Zeichenfolgen lässt sich mit Zeilen im Vergleich zu einer regulärenfor
Schleife nicht gut skalieren .quelle
apply
deutlich schneller ist als meine Lösung mitany
. Irgendwelche Gedanken dazu?any
ist etwa 100-mal schneller alsapply
. Es machte meine ersten Tests mit 2000 Zeilen x 1000 Spalten und hierapply
war doppelt so schnell wieany
Für
axis=1
(dh zeilenweise Funktionen) können Sie anstelle von einfach die folgende Funktion verwendenapply
. Ich frage mich, warum das nicht daspandas
Verhalten ist. (Ungetestet mit zusammengesetzten Indizes, aber es scheint viel schneller zu sein alsapply
)quelle
zip(df, row[1:])
ist hier ausreichend; Überlegen Sie in diesem Stadium wirklich,numba
ob func eine numerische Berechnung ist. In dieser Antwort finden Sie eine Erklärung.numba
ist schneller,faster_df_apply
ist für Leute gedacht, die nur etwas wollen, das dem entspricht, aber schneller als dasDataFrame.apply
(was seltsam langsam ist).Gibt es jemals Situationen, in denen
apply
es gut ist? Ja manchmal.Aufgabe: Unicode-Strings dekodieren.
Update
Ich habe mich keineswegs für die Verwendung von ausgesprochen
apply
, nur gedacht, da dasNumPy
mit der oben genannten Situation nicht umgehen kann, hätte es ein guter Kandidat sein könnenpandas apply
. Aber ich habe dank der Erinnerung von @jpp das einfache Verständnis der alten Liste vergessen.quelle
[unidecode.unidecode(x) for x in s]
oderlist(map(unidecode.unidecode, s))
?apply
, dachte nur , das eine gute gewesen sein könnte Anwendungsfall.