Schnell, flexibel, einfach und intuitiv: So beschleunigen Sie Ihre Pandas-Projekte

Schnell, flexibel, einfach und intuitiv: So beschleunigen Sie Ihre Pandas-Projekte

Wenn Sie mit großen Datenmengen arbeiten, erinnern Sie sich wahrscheinlich an den „Aha“ -Moment auf Ihrer Python-Reise, als Sie diePandas-Bibliothek entdeckt haben. Pandas ist ein Game-Changer fürdata science and analytics, besonders wenn Sie zu Python gekommen sind, weil Sie nach etwas Stärkerem als Excel und VBA gesucht haben.

Was ist also mit Pandas, das Datenwissenschaftler, Analysten und Ingenieure wie mich begeistert? In der Pandas-Dokumentation heißt es:

"fast,flexible und ausdrucksstarke Datenstrukturen, mit denen das Arbeiten mit" relationalen "oder" beschrifteten "Daten sowohleasy als auchintuitive ermöglicht."

Schnell, flexibel, einfach und intuitiv? Das klingt gut! Wenn Sie komplizierte Datenmodelle erstellen müssen, möchten Sie nicht die Hälfte Ihrer Entwicklungsstunden damit verbringen, darauf zu warten, dass Module große Datenmengen durchlaufen. Sie möchten Ihre Zeit und Ihr Können der Interpretation Ihrer Daten widmen, anstatt mühsam mit weniger leistungsstarken Tools herumzufummeln.

Aber ich habe gehört, dass Pandas langsam sind ...

Als ich anfing, Pandas zu verwenden, wurde mir geraten, dass Pandas zwar ein großartiges Werkzeug zum Zerlegen von Daten ist, aber zu langsam, um als statistisches Modellierungswerkzeug verwendet zu werden. Zunächst erwies sich dies als wahr. Ich verbrachte mehr als ein paar Minuten damit, mit den Daumen zu drehen und darauf zu warten, dass Pandas Daten durchsuchen.

Aber dann habe ich erfahren, dass Pandas auf der NumPy-Array-Struktur aufbaut und so viele seiner Operationen in C ausgeführt werden, entweder über NumPy oder über Pandas 'eigenelibrary von Python-Erweiterungsmodulen, die in Cython geschrieben sind und zusammengestellt zu C. Sollten Pandas nicht auch schnell sein?

Es sollte unbedingt sein, wenn Sie es so verwenden, wie es beabsichtigt war!

Das Paradoxe ist, dass der Code vonPythonicin Pandas in Bezug auf die Effizienz suboptimal sein kann. Pandas is designed for vectorized operations, die wie NumPy ganze Spalten oder Datensätze in einem Durchgang bearbeiten. Das individuelle Nachdenken über jede „Zelle“ oder Zeile sollte im Allgemeinen ein letzter Ausweg sein, kein erster.

Dieses Tutorial

Dies ist keine Anleitung zur Überoptimierung Ihres Pandas-Codes. Pandas ist bereits so konzipiert, dass es bei korrekter Verwendung schnell ausgeführt werden kann. Außerdem gibt es einen großen Unterschied zwischen der Optimierung und dem Schreiben von sauberem Code.

Dies ist eine Anleitung zur Verwendung von Pandas Pythonically, um die leistungsstarken und benutzerfreundlichen integrierten Funktionen optimal zu nutzen. Darüber hinaus erhalten Sie einige praktische Tipps zur Zeitersparnis, damit Sie nicht jedes Mal, wenn Sie mit Ihren Daten arbeiten, mit den Daumen drehen.

In diesem Tutorial werden Sie Folgendes behandeln:

  • Vorteile der Verwendung vondatetime-Daten mit Zeitreihen

  • Der effizienteste Weg zur Durchführung von Chargenberechnungen

  • Zeit sparen durch Speichern von Daten mit HDFStore

Um diese Themen zu demonstrieren, nehme ich ein Beispiel aus meiner täglichen Arbeit, das sich mit einer Zeitreihe des Stromverbrauchs befasst. Nach dem Laden der Daten werden Sie nacheinander effizientere Wege beschreiten, um zum Endergebnis zu gelangen. Ein Sprichwort, das für die meisten Pandas gilt, ist, dass es mehr als einen Weg gibt, von A nach B zu gelangen. Dies bedeutet jedoch nicht, dass alle verfügbaren Optionen gleich gut auf größere, anspruchsvollere Datensätze skaliert werden können.

Angenommen, Sie wissen bereits, wie einige grundlegendedata selection in Pandasausgeführt werden, beginnen wir.

Die momentane Aufgabe

Ziel dieses Beispiels ist es, Energietarife für die Nutzungsdauer anzuwenden, um die Gesamtkosten des Energieverbrauchs für ein Jahr zu ermitteln. Das heißt, zu verschiedenen Tageszeiten variiert der Strompreis. Daher besteht die Aufgabe darin, den für jede Stunde verbrauchten Strom mit dem korrekten Preis für die Stunde zu multiplizieren, in der er verbraucht wurde.

Lesen wir unsere Daten ausCSV file mit zwei Spalten: eine für Datum plus Uhrzeit und eine für die in Kilowattstunden (kWh) verbrauchte elektrische Energie:

CSV data

Die Zeilen enthalten den Stromverbrauch pro Stunde, sodass für das ganze Jahr365 x 24 = 8760Zeilen vorhanden sind. Jede Zeile gibt die Nutzung für die zu diesem Zeitpunkt beginnende „Stunde“ an, sodass der 01.01.13 0:00 die Nutzung für die erste Stunde des 1. Januar angibt.

Zeit sparen mit Datetime-Daten

Als erstes müssen Sie Ihre Daten mit einer der E / A-Funktionen von Pandas aus der CSV-Datei lesen:

>>>

>>> import pandas as pd
>>> pd.__version__
'0.23.1'

# Make sure that `demand_profile.csv` is in your
# current working directory.
>>> df = pd.read_csv('demand_profile.csv')
>>> df.head()
     date_time  energy_kwh
0  1/1/13 0:00       0.586
1  1/1/13 1:00       0.580
2  1/1/13 2:00       0.572
3  1/1/13 3:00       0.596
4  1/1/13 4:00       0.592

Das sieht auf den ersten Blick in Ordnung aus, aber es gibt ein kleines Problem. Pandas und NumPy haben ein Konzept vondtypes (Datentypen). Wenn keine Argumente angegeben werden, nimmtdate_time denobject-Typ an:

>>>

>>> df.dtypes
date_time      object
energy_kwh    float64
dtype: object

>>> type(df.iat[0, 0])
str

Das ist nicht ideal. object ist ein Container nicht nur fürstr, sondern für jede Spalte, die nicht genau in einen Datentyp passt. Es wäre mühsam und ineffizient, mit Daten als Zeichenfolgen zu arbeiten. (Es wäre auch speichereffizient.)

Für die Arbeit mit Zeitreihendaten möchten Sie, dass die Spaltedate_timeals Array von Datums- / Uhrzeitobjekten formatiert wird. (Pandas nennt dies aTimestamp.) Pandas macht jeden Schritt hier ziemlich einfach:

>>>

>>> df['date_time'] = pd.to_datetime(df['date_time'])
>>> df['date_time'].dtype
datetime64[ns]

(Beachten Sie, dass Sie in diesem Fall alternativ einen PandasPeriodIndex verwenden können.)

Sie haben jetzt einen DataFrame namensdf, der unserer CSV-Datei sehr ähnlich sieht. Es hat zwei Spalten und einen numerischen Index zum Verweisen auf die Zeilen.

>>>

>>> df.head()
               date_time    energy_kwh
0    2013-01-01 00:00:00         0.586
1    2013-01-01 01:00:00         0.580
2    2013-01-01 02:00:00         0.572
3    2013-01-01 03:00:00         0.596
4    2013-01-01 04:00:00         0.592

Der obige Code ist einfach und leicht, aber wie schnell? Testen wir es mit einemtiming decorator, das ich ursprünglich@timeit genannt habe. Dieser Dekorator ahmt weitgehendtimeit.repeat() aus der Python-Standardbibliothek nach, ermöglicht es Ihnen jedoch, das Ergebnis der Funktion selbst zurückzugeben und die durchschnittliche Laufzeit aus mehreren Versuchen zu drucken. (Pythonstimeit.repeat() gibt die Timing-Ergebnisse zurück, nicht das Funktionsergebnis.)

Wenn Sie eine Funktion erstellen und den Dekorator@timeitdirekt darüber platzieren, wird die Funktion bei jedem Aufruf der Funktion zeitlich festgelegt. Der Dekorateur führt eine äußere und eine innere Schleife aus:

>>>

>>> @timeit(repeat=3, number=10)
... def convert(df, column_name):
...     return pd.to_datetime(df[column_name])

>>> # Read in again so that we have `object` dtype to start
>>> df['date_time'] = convert(df, 'date_time')
Best of 3 trials with 10 function calls per trial:
Function `convert` ran in average of 1.610 seconds.

Das Ergebnis? 1.6 seconds for 8760 rows of data. "Großartig", könnte man sagen, "das ist überhaupt keine Zeit." Was aber, wenn Sie auf größere Datenmengen stoßen - beispielsweise ein Jahr Stromverbrauch in Abständen von einer Minute? Das sind 60-mal mehr Daten, sodass Sie am Ende etwa eineinhalb Minuten warten müssen. Das klingt weniger erträglich.

Tatsächlich habe ich kürzlich 10 Jahre stündliche Stromdaten von 330 Standorten analysiert. Glaubst du, ich habe 88 Minuten gewartet, um die Daten zu konvertieren? Absolut nicht!

Wie können Sie das beschleunigen? In der Regel sind Pandas umso schneller, je weniger Daten Sie interpretieren müssen. In diesem Fall werden Sie enorme Geschwindigkeitsverbesserungen feststellen, wenn Sie Pandas mithilfe des Formatparameters mitteilen, wie Ihre Zeit- und Datumsdaten aussehen. Sie können dies tun, indem Sie die gefundenenstrftime-Codeshere verwenden und wie folgt eingeben:

>>>

>>> @timeit(repeat=3, number=100)
>>> def convert_with_format(df, column_name):
...     return pd.to_datetime(df[column_name],
...                           format='%d/%m/%y %H:%M')
Best of 3 trials with 100 function calls per trial:
Function `convert_with_format` ran in average of 0.032 seconds.

Das neue Ergebnis? 0.032 seconds, which is 50 times faster! Sie haben gerade 86 Minuten Verarbeitungszeit für meine 330 Websites gespart. Keine schlechte Verbesserung!

Ein feineres Detail ist, dass die Datumsangaben in der CSV nicht inISO 8601 format angegeben sind: Sie benötigenYYYY-MM-DD HH:MM. Wenn Sie kein Format angeben, verwendet Pandas das Paketdateutil, um jede Zeichenfolge in ein Datum zu konvertieren.

Wenn die Rohdaten für Datum und Uhrzeit bereits im ISO 8601-Format vorliegen, kann Pandas sofort einfast route zum Parsen der Daten benötigen. Dies ist ein Grund, warum es hier so vorteilhaft ist, das Format explizit anzugeben. Eine andere Möglichkeit besteht darin, den Parameterinfer_datetime_format=Truezu übergeben, aber es lohnt sich im Allgemeinen, explizit zu sein.

Note: Mitread_csv() von Pandas können Sie auch Daten als Teil des Datei-E / A-Schritts analysieren. Siehe die Parameterparse_dates,infer_datetime_format unddate_parser.

Einfache Schleife über Pandas-Daten

Jetzt, da Ihre Daten und Zeiten in einem praktischen Format vorliegen, können Sie sich mit der Berechnung Ihrer Stromkosten befassen. Denken Sie daran, dass die Kosten von Stunde zu Stunde variieren. Daher müssen Sie für jede Stunde des Tages einen Kostenfaktor festlegen. In diesem Beispiel werden die Nutzungsdauer wie folgt definiert:

Tarifart Cent pro kWh Zeitspanne

Peak

28

17:00 bis 24:00 Uhr

Schulter

20

7:00 bis 17:00 Uhr

Außerhalb der Spitzenzeiten

12

0:00 bis 7:00 Uhr

Wenn der Preis pauschal 28 Cent pro kWh für jede Stunde des Tages betragen würde, würden die meisten mit Pandas vertrauten Personen wissen, dass diese Berechnung in einer Zeile durchgeführt werden könnte:

>>>

>>> df['cost_cents'] = df['energy_kwh'] * 28

Dies führt zur Erstellung einer neuen Säule mit den Stromkosten für diese Stunde:

               date_time    energy_kwh       cost_cents
0    2013-01-01 00:00:00         0.586           16.408
1    2013-01-01 01:00:00         0.580           16.240
2    2013-01-01 02:00:00         0.572           16.016
3    2013-01-01 03:00:00         0.596           16.688
4    2013-01-01 04:00:00         0.592           16.576
# ...

Unsere Kostenberechnung hängt jedoch von der Tageszeit ab. Hier werden Sie viele Leute sehen, die Pandas so verwenden, wie es nicht beabsichtigt war: indem Sie eine Schleife schreiben, um die bedingte Berechnung durchzuführen.

Für den Rest dieses Tutorials gehen Sie von einer weniger als idealen Basislösung aus und arbeiten an einer Pythonic-Lösung, die Pandas voll ausnutzt.

Aber was ist Pythonic im Fall von Pandas? Die Ironie ist, dass diejenigen, die Erfahrung mit anderen (weniger benutzerfreundlichen) Codierungssprachen wie C ++ oder Java haben, dafür besonders anfällig sind, weil sie instinktiv „in Schleifen denken“.

Schauen wir uns einloop approachan, das nicht pythonisch ist und das viele Leute nehmen, wenn sie nicht wissen, wie Pandas verwendet werden soll. Wir werden wieder@timeit verwenden, um zu sehen, wie schnell dieser Ansatz ist.

Erstellen wir zunächst eine Funktion, um den entsprechenden Tarif auf eine bestimmte Stunde anzuwenden:

def apply_tariff(kwh, hour):
    """Calculates cost of electricity for given hour."""
    if 0 <= hour < 7:
        rate = 12
    elif 7 <= hour < 17:
        rate = 20
    elif 17 <= hour < 24:
        rate = 28
    else:
        raise ValueError(f'Invalid hour: {hour}')
    return rate * kwh

Hier ist die Schleife, die in ihrer ganzen Pracht nicht pythonisch ist:

>>>

>>> # NOTE: Don't do this!
>>> @timeit(repeat=3, number=100)
... def apply_tariff_loop(df):
...     """Calculate costs in loop.  Modifies `df` inplace."""
...     energy_cost_list = []
...     for i in range(len(df)):
...         # Get electricity used and hour of day
...         energy_used = df.iloc[i]['energy_kwh']
...         hour = df.iloc[i]['date_time'].hour
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
...
>>> apply_tariff_loop(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_loop` ran in average of 3.152 seconds.

Für Leute, die Pandas aufgegriffen haben, nachdem sie einige Zeit zuvor „reines Python“ geschrieben hatten, mag dieses Design natürlich erscheinen: Sie haben ein typisches „für jedesx, abhängig vony, tun Siez. ”

Diese Schleife ist jedoch klobig. Sie können das oben Genannte aus mehreren Gründen als „Antimuster“ in Pandas betrachten. Zunächst muss eine Liste initialisiert werden, in der die Ausgaben aufgezeichnet werden.

Zweitens wird das undurchsichtige Objektrange(0, len(df)) zum Durchlaufen verwendet, und nach dem Anwenden vonapply_tariff() muss das Ergebnis an eine Liste angehängt werden, die zum Erstellen der neuen DataFrame-Spalte verwendet wird. Es macht auch das, was alschained indexing bezeichnet wird, mitdf.iloc[i]['date_time'], was häufig zu unbeabsichtigten Ergebnissen führt.

Das größte Problem bei diesem Ansatz sind jedoch die Zeitkosten der Berechnungen. Auf meinem Computer dauerte diese Schleife mehr als 3 Sekunden für 8760 Datenzeilen. Als Nächstes sehen Sie sich einige verbesserte Lösungen für die Iteration über Pandas-Strukturen an.

Schleifen mit.itertuples() und.iterrows()

Welche anderen Ansätze können Sie verfolgen? Nun, Pandas hat die Syntax vonfor i in range(len(df)) tatsächlich überflüssig gemacht, indem sie dieDataFrame.itertuples() undDataFrame.iterrows() Methoden eingeführt hat. Dies sind beide Generatormethoden, bei denenyieldjeweils eine Zeile ist.

.itertuples() ergibt für jede Zeile einnamedtuple, wobei der Indexwert der Zeile das erste Element des Tupels ist. Anametuple ist eine Datenstruktur aus Pythonscollections-Modul, die sich wie ein Python-Tupel verhält, aber Felder enthält, auf die über die Attributsuche zugegriffen werden kann.

.iterrows() ergibt Paare (Tupel) von (Index,Series) für jede Zeile im DataFrame.

Während.itertuples() tendenziell etwas schneller ist, bleiben wir in Pandas und verwenden in diesem Beispiel.iterrows(), da einige Leser möglicherweise nicht aufnametuple gestoßen sind. Mal sehen, was damit erreicht wird:

>>>

>>> @timeit(repeat=3, number=100)
... def apply_tariff_iterrows(df):
...     energy_cost_list = []
...     for index, row in df.iterrows():
...         # Get electricity used and hour of day
...         energy_used = row['energy_kwh']
...         hour = row['date_time'].hour
...         # Append cost list
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
...
>>> apply_tariff_iterrows(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_iterrows` ran in average of 0.713 seconds.

Es wurden einige marginale Gewinne erzielt. Die Syntax ist expliziter und Ihre Zeilenwertreferenzen enthalten weniger Unordnung, sodass sie besser lesbar sind. In Bezug auf Zeitgewinne ist fast fünfmal fünfmal schneller!

Es gibt jedoch mehr Raum für Verbesserungen. Sie verwenden immer noch eine Form einer Python-for-Schleife. Dies bedeutet, dass jeder Funktionsaufruf in Python ausgeführt wird, wenn er idealerweise in einer schnelleren Sprache ausgeführt werden kann, die in die interne Architektur von Pandas integriert ist.

.apply()der Pandas

Sie können diesen Vorgang mit der Methode.apply() anstelle von.iterrows() weiter verbessern. Die.apply()-Methode von Pandas übernimmt Funktionen (aufrufbare Elemente) und wendet sie entlang einer Achse eines DataFrames (alle Zeilen oder alle Spalten) an. In diesem Beispiel hilft Ihnen einlambda function dabei, die beiden Datenspalten inapply_tariff() zu übergeben:

>>>

>>> @timeit(repeat=3, number=100)
... def apply_tariff_withapply(df):
...     df['cost_cents'] = df.apply(
...         lambda row: apply_tariff(
...             kwh=row['energy_kwh'],
...             hour=row['date_time'].hour),
...         axis=1)
...
>>> apply_tariff_withapply(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_withapply` ran in average of 0.272 seconds.

Die syntaktischen Vorteile von.apply() liegen auf der Hand, wobei die Anzahl der Zeilen und der gut lesbare, explizite Code erheblich reduziert werden. In diesem Fall war die benötigte Zeit ungefähr halb so lang wie die.iterrows()-Methode.

Dies ist jedoch noch nicht „blitzschnell“. Ein Grund ist, dass.apply() intern versucht,Cython Iteratoren zu durchlaufen. In diesem Fall können die von Ihnen übergebenenlambdain Cython nicht verarbeitet werden. Daher wird sie in Python aufgerufen, was folglich nicht allzu schnell ist.

Wenn Sie.apply() für meine 10-jährigen stündlichen Daten für 330 Websites verwenden würden, würden Sie etwa 15 Minuten Verarbeitungszeit in Betracht ziehen. Wenn diese Berechnung ein kleiner Teil eines größeren Modells sein soll, möchten Sie die Dinge wirklich beschleunigen. Hier bieten sich vektorisierte Operationen an.

Auswählen von Daten mit.isin()

Zuvor haben Sie gesehen, dass Sie bei einem einzigen Strompreis diesen Preis auf alle Stromverbrauchsdaten in einer Codezeile (df['energy_kwh'] * 28) anwenden können. Diese spezielle Operation war ein Beispiel für eine vektorisierte Operation und der schnellste Weg, um Dinge in Pandas zu tun.

Aber wie können Sie Bedingungsberechnungen als vektorisierte Operationen in Pandas anwenden? Ein Trick besteht darin, Teile des DataFrame basierend auf Ihren Bedingungen auszuwählen und zu gruppieren und dann eine vektorisierte Operation auf jede ausgewählte Gruppe anzuwenden.

In diesem nächsten Beispiel sehen Sie, wie Sie Zeilen mit der.isin()-Methode von Pandas auswählen und dann den entsprechenden Tarif in einer vektorisierten Operation anwenden. Bevor Sie dies tun, wird es etwas bequemer, wenn Sie die Spaltedate_timeals Index für den DataFrame festlegen:

df.set_index('date_time', inplace=True)

@timeit(repeat=3, number=100)
def apply_tariff_isin(df):
    # Define hour range Boolean arrays
    peak_hours = df.index.hour.isin(range(17, 24))
    shoulder_hours = df.index.hour.isin(range(7, 17))
    off_peak_hours = df.index.hour.isin(range(0, 7))

    # Apply tariffs to hour ranges
    df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28
    df.loc[shoulder_hours,'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20
    df.loc[off_peak_hours,'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12

Mal sehen, wie dies verglichen wird:

>>>

>>> apply_tariff_isin(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_isin` ran in average of 0.010 seconds.

Um zu verstehen, was in diesem Code passiert, müssen Sie wissen, dass die.isin()-Methode ein Array von Booleschen Werten zurückgibt, das folgendermaßen aussieht:

[False, False, False, ..., True, True, True]

Diese Werte geben an, welche DataFrame-Indizes (Datumszeiten) innerhalb des angegebenen Stundenbereichs liegen. Wenn Sie diese Booleschen Arrays an den.loc-Indexer des DataFrame übergeben, erhalten Sie einen Teil des DataFrame, der nur Zeilen enthält, die diesen Stunden entsprechen. Danach geht es einfach darum, den Slice mit dem entsprechenden Tarif zu multiplizieren, was eine schnelle vektorisierte Operation ist.

Wie ist dies im Vergleich zu unseren obigen Schleifenoperationen? Erstens stellen Sie möglicherweise fest, dass Sieapply_tariff() nicht mehr benötigen, da die gesamte bedingte Logik bei der Auswahl der Zeilen angewendet wird. Es gibt also eine enorme Reduzierung der Codezeilen, die Sie schreiben müssen, und des aufgerufenen Python-Codes.

Was ist mit der Bearbeitungszeit? 315-mal schneller als die Schleife, die nicht Pythonic war, ungefähr 71-mal schneller als.iterrows() und 27-mal schneller als.apply(). Jetzt bewegen Sie sich mit der Geschwindigkeit, die Sie benötigen, um große Datenmengen schön und schnell zu verarbeiten.

Können wir es besser machen?

Inapply_tariff_isin() erledigen wir zugegebenermaßen immer noch einige „manuelle Arbeiten“, indem wir jeweils dreimaldf.loc unddf.index.hour.isin() aufrufen. Sie könnten argumentieren, dass diese Lösung nicht skalierbar ist, wenn wir einen detaillierteren Bereich von Zeitfenstern hätten. (Eine andere Rate für jede Stunde würde 24.isin()Anrufe erfordern.) Glücklicherweise können Sie mit derpd.cut()-Funktion von Pandas in diesem Fall noch programmatischer arbeiten:

@timeit(repeat=3, number=100)
def apply_tariff_cut(df):
    cents_per_kwh = pd.cut(x=df.index.hour,
                           bins=[0, 7, 17, 24],
                           include_lowest=True,
                           labels=[12, 20, 28]).astype(int)
    df['cost_cents'] = cents_per_kwh * df['energy_kwh']

Nehmen wir uns eine Sekunde Zeit, um zu sehen, was hier los ist. pd.cut() wendet eine Reihe von Etiketten (unsere Kosten) an, nach denen jede Stunde in den Behälter gehört. Beachten Sie, dass der Parameterinclude_lowest angibt, ob das erste Intervall inklusive links sein soll oder nicht. (Sie möchtentime=0 in eine Gruppe aufnehmen.)

Dies ist eine vollständig vektorisierte Methode, um zu Ihrem beabsichtigten Ergebnis zu gelangen, und sie hat hinsichtlich des Timings die Nase vorn:

>>>

>>> apply_tariff_cut(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_cut` ran in average of 0.003 seconds.

Bisher haben Sie möglicherweise mehr als eine Stunde bis weniger als eine Sekunde gebraucht, um den gesamten Datensatz mit 300 Standorten zu verarbeiten. Nicht schlecht! Es gibt jedoch eine letzte Option: Verwenden Sie NumPy-Funktionen, um die zugrunde liegenden NumPy-Arrays für jeden DataFrame zu bearbeiten und die Ergebnisse dann wieder in Pandas-Datenstrukturen zu integrieren.

Vergiss NumPy nicht!

Ein Punkt, den Sie bei der Verwendung von Pandas nicht vergessen sollten, ist, dass Pandas Series und DataFrames über der NumPy-Bibliothek erstellt wurden. Dies gibt Ihnen noch mehr Rechenflexibilität, da Pandas nahtlos mit NumPy-Arrays und -Operationen zusammenarbeitet.

In diesem nächsten Fall verwenden Sie diedigitize()-Funktion von NumPy. Es ähnelt Pandas 'cut() darin, dass die Daten gruppiert werden, diesmal jedoch durch eine Reihe von Indizes dargestellt werden, die angeben, zu welchem ​​Bin jede Stunde gehört. Diese Indizes werden dann auf ein Preisarray angewendet:

@timeit(repeat=3, number=100)
def apply_tariff_digitize(df):
    prices = np.array([12, 20, 28])
    bins = np.digitize(df.index.hour.values, bins=[7, 17, 24])
    df['cost_cents'] = prices[bins] * df['energy_kwh'].values

Wie diecut()-Funktion ist diese Syntax wunderbar präzise und leicht zu lesen. Aber wie ist es in der Geschwindigkeit zu vergleichen? Mal sehen:

>>>

>>> apply_tariff_digitize(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_digitize` ran in average of 0.002 seconds.

Zu diesem Zeitpunkt gibt es noch eine Leistungsverbesserung, die jedoch eher marginal ist. Dies ist wahrscheinlich ein guter Zeitpunkt, um sich einen Tag lang mit der Verbesserung des Codes zu beschäftigen und über das Gesamtbild nachzudenken.

Mit Pandas kann es hilfreich sein, die "Hierarchie" der bevorzugten Optionen für Stapelberechnungen beizubehalten, wie Sie es hier getan haben. Diese reichen normalerweise vom schnellsten zum langsamsten (und am meisten zum am wenigsten flexiblen):

  1. Verwenden Sie vektorisierte Operationen: Pandas-Methoden und -Funktionen ohne for-Schleifen.

  2. Verwenden Sie die Methode.apply() mit einem aufrufbaren.

  3. Verwenden Sie.itertuples(): Iterieren Sie über DataFrame-Zeilen alsnamedtuples aus Pythonscollections-Modul.

  4. Verwenden Sie.iterrows(): Iterieren Sie über DataFrame-Zeilen als (Index,pd.Series) Paare. Während eine Pandas-Serie eine flexible Datenstruktur ist, kann es kostspielig sein, jede Zeile in eine Serie zu konstruieren und dann darauf zuzugreifen.

  5. Verwenden Sie "Element für Element" für Schleifen und aktualisieren Sie jede Zelle oder Zeile einzeln mitdf.loc oderdf.iloc. (Oder.at /.iat für einen schnellen skalaren Zugriff.)

Don’t Take My Word For It: Die obige Rangfolge ist ein Vorschlagstraight from a core Pandas developer.

Hier ist die oben beschriebene "Rangfolge" für jede Funktion, die Sie hier erstellt haben:

Funktion Laufzeit (Sekunden)

apply_tariff_loop()

3.152

apply_tariff_iterrows()

0.713

apply_tariff_withapply()

0.272

apply_tariff_isin()

0.010

apply_tariff_cut()

0.003

apply_tariff_digitize()

0.002

Verhindern Sie die Wiederaufbereitung mit HDFStore

Nachdem Sie sich nun mit schnellen Datenprozessen in Pandas befasst haben, wollen wir untersuchen, wie Sie die Wiederaufbereitungszeit mitHDFStore, die kürzlich in Pandas integriert wurde, insgesamt vermeiden können.

Wenn Sie ein komplexes Datenmodell erstellen, ist es häufig zweckmäßig, eine Vorverarbeitung Ihrer Daten durchzuführen. Wenn Sie beispielsweise 10 Jahre lang Daten zum Stromverbrauch mit Minutenfrequenz hatten, kann die einfache Konvertierung von Datum und Uhrzeit in Datum / Uhrzeit 20 Minuten dauern, selbst wenn Sie den Formatparameter angeben. Sie möchten dies wirklich nur einmal tun müssen, nicht jedes Mal, wenn Sie Ihr Modell ausführen, um es zu testen oder zu analysieren.

Eine sehr nützliche Sache, die Sie hier tun können, ist die Vorverarbeitung und Speicherung Ihrer Daten in der verarbeiteten Form, um sie bei Bedarf zu verwenden. Aber wie können Sie Daten im richtigen Format speichern, ohne sie erneut verarbeiten zu müssen? Wenn Sie als CSV speichern, verlieren Sie einfach Ihre datetime-Objekte und müssen sie beim erneuten Zugriff erneut verarbeiten.

Pandas verfügt über eine integrierte Lösung, dieHDF5 verwendet, ein Hochleistungsspeicherformat, das speziell zum Speichern tabellarischer Datenfelder entwickelt wurde. Mit derHDFStore-Klasse von Pandas können Sie Ihren DataFrame in einer HDF5-Datei speichern, damit effizient darauf zugegriffen werden kann, während die Spaltentypen und andere Metadaten beibehalten werden. Es ist eine wörterbuchähnliche Klasse, sodass Sie wie bei einem Pythondict-Objekt lesen und schreiben können.

So speichern Sie Ihren vorverarbeiteten Stromverbrauch DataFramedf in einer HDF5-Datei:

# Create storage object with filename `processed_data`
data_store = pd.HDFStore('processed_data.h5')

# Put DataFrame into the object setting the key as 'preprocessed_df'
data_store['preprocessed_df'] = df
data_store.close()

Jetzt können Sie Ihren Computer herunterfahren und eine Pause einlegen, da Sie wissen, dass Sie zurückkehren können und Ihre verarbeiteten Daten auf Sie warten, wenn Sie sie benötigen. Keine Wiederaufbereitung erforderlich. So würden Sie über die HDF5-Datei auf Ihre Daten zugreifen, wobei die Datentypen erhalten bleiben:

# Access data store
data_store = pd.HDFStore('processed_data.h5')

# Retrieve data using key
preprocessed_df = data_store['preprocessed_df']
data_store.close()

Ein Datenspeicher kann mehrere Tabellen enthalten, deren Name jeweils als Schlüssel dient.

Nur ein Hinweis zur Verwendung des HDFStore in Pandas: Sie müssen PyTables> = 3.0.0 installiert haben. Nachdem Sie Pandas installiert haben, müssen Sie PyTables wie folgt aktualisieren:

pip install --upgrade tables

Schlussfolgerungen

Wenn Sie nicht der Meinung sind, dass Ihr Pandas-Projektfast,flexible,easy undintuitive ist, sollten Sie überlegen, wie Sie die Bibliothek verwenden.

Die Beispiele, die Sie hier untersucht haben, sind recht einfach, veranschaulichen jedoch, wie die ordnungsgemäße Anwendung der Pandas-Funktionen die Laufzeit und die Lesbarkeit des Codes beim Booten erheblich verbessern kann. Hier sind einige Faustregeln, die Sie anwenden können, wenn Sie das nächste Mal mit großen Datenmengen in Pandas arbeiten:

  • Versuchen Sie, wenn möglichvectorized operations zu verwenden, anstatt sich Problemen mit der Mentalität vonfor x in df...zu nähern. Wenn Ihr Code viele for-Schleifen enthält, ist er möglicherweise besser für die Arbeit mit nativen Python-Datenstrukturen geeignet, da Pandas ansonsten viel Overhead verursachen.

  • Wenn Sie komplexere Operationen haben, bei denen eine Vektorisierung einfach unmöglich oder zu schwierig ist, um effizient zu arbeiten, verwenden Sie die Methode.apply().

  • Wenn Sie Ihr Array durchlaufen müssen (was passiert), verwenden Sie.iterrows() oder.itertuples(), um die Geschwindigkeit und Syntax zu verbessern.

  • Pandas hat viele Optionen, und es gibt fast immer mehrere Möglichkeiten, von A nach B zu gelangen. Beachten Sie dies, vergleichen Sie die Leistung verschiedener Routen und wählen Sie diejenige aus, die im Kontext Ihres Projekts am besten funktioniert.

  • Vermeiden Sie nach dem Erstellen eines Datenbereinigungsskripts eine erneute Verarbeitung, indem Sie Ihre Zwischenergebnisse in HDFStore speichern.

  • Die Integration von NumPy in Pandas-Vorgänge kann häufig die Geschwindigkeit verbessern und die Syntax vereinfachen.