Python-Bibliothek pandas 2.0 optimiert die Speicherverwaltung

Die neue Version der meistgenutzten Python-Library für die Datenverarbeitung arbeitet mit Apache Arrow zusammen und führt Copy-on-Write ein.

In Pocket speichern vorlesen Druckansicht 8 Kommentare lesen
Science,Research,As,A,Concept,For,Presentation, EHDS, eHealth, Gesundheitsdaten

(Bild: foxaon1987/Shutterstock.com)

Lesezeit: 10 Min.
Von
  • Patrick Höfler
Inhaltsverzeichnis

Gut drei Jahre nach der ersten Hauptversion ist nun Version 2.0 von pandas erschienen. Das Release stabilisiert Funktionen, die teilweise bereits in pandas 1.5 enthalten waren, darunter die Extension Arrays, die Anbindung an Apache Arrow und Copy-on-Write.

Im Zuge des Versionssprungs hat das Team die Python-Bibliothek für die Verarbeitung und Analyse von Daten aufgeräumt und alle als überholt (deprecated) markierten Komponenten entfernt oder angepasst.

Bereits pandas 1.0 hat Extension Arrays (EAs) eingeführt, die es erlauben, eigene Datentypen zu definieren, die von NumPy-Datentypen abweichen. Das Team hat innerhalb von pandas EAs implementiert, die fehlende Werte in beliebigen Datentypen wie Integer oder Boolean erlauben.

Da frühere Versionen an vielen Stellen implizit NumPy-Datentypen voraussetzen, funktionierte die EA-Anbindung nicht vollständig. Das pandas-Team hat sie über die 1.x-Serie sukzessive verbessert. In pandas 2.0 berücksichtigen die meisten Aufrufe die EA-eigenen Methoden, statt auf NumPy-Funktionen zurückzufallen. Das verbessert auch die Performance:

# pandas 1.5.3:

In[3]: ser = pd.Series(list(range(1, 1_000_000)) 
                       + [pd.NA], dtype="Int64")
In[4]: %timeit ser.drop_duplicates()

22.7 ms ± 272 µs per loop \
  (mean ± std. dev. of 7 runs, 10 loops each)


# pandas 2.0:

In[3]: ser = pd.Series(list(range(1, 1_000_000)) 
                       + [pd.NA], dtype="Int64")

In[4]: %timeit ser.drop_duplicates()

7.54 ms ± 24 µs per loop \
  (mean ± std. dev. of 7 runs, 100 loops each)

Unter anderem findet innerhalb von GroupBy-Operationen keine Umwandlung zu float mehr statt, sondern es gelten die EA-Konventionen. Neben der verbesserten Performance verhindert dieses Vorgehen Genauigkeitsverluste bei größeren Zahlen.

pandas 2.0 kennt für fast alle I/O-Funktionen einen neuen Parameter, um automatisch zu EA-Datentypen umzuwandeln. Wenn der Parameter dtype_backend auf "numpy_nullable" gesetzt ist, gibt die Funktion ein DataFrame zurück, der nur aus Nullable-Datentypen besteht.

Außerdem lässt sich ein Index erzeugen, der Extension Arrays enthält. Das Vorgehen hat pandas 1.4.0 erstmals eingeführt, und mittlerweile sind alle Operationen effizient implementiert:

  • Index-Operationen benutzen EA-Funktionen.
  • Es gibt eine effiziente Engine, die das Selektieren von Daten über loc und iloc ermöglicht.
  • pandas kopiert die Werte in einem MultiIndex intern nicht mehr. Das verbessert die Performance und erlaubt es, die richtigen Datentypen konsistent zu verwenden.

Das pandas-Team arbeitet kontinuierlich an der Extension-Array-Schnittstelle, und jede neue Version bringt Verbesserungen mit.

Die Bibliothek Apache Arrow definiert ein programmiersprachenunabhängiges Format für In-Memory-Datenverarbeitungen und stellt insbesondere für String-Datentypen eine erhebliche Verbesserung im Vergleich zu NumPy und dem object-Datentyp dar.

pandas 1.5.0 führte erstmals ein auf Arrow-Arrays basiertes ExtensionArray ein. pandas 2.0 erhöht die Mindestversion der Python-Schnittstelle PyArrow und verbessert die Anbindung an Arrow deutlich. pandas 1.5.3 hat zahlreiche PerformanceWarnings ausgegeben, die anzeigten, dass eine NumPy- statt einer PyArrow-Implementierung zum Einsatz kam. Der Großteil dieser Warnung ist nun hinfällig. Außerdem hat das Team die PyArrow-Integration in den Teilen der Bibliothek verbessert, die aufgrund fehlender, spezialisierter Implementierung nicht die EA-Schnittstelle verwenden. pandas nutzt mit Version 2.0 in den meisten Fällen die korrespondierende PyArrow-Compute-Schnittstelle.

Die String-Anbindung für PyArrow-EAs entspricht im Wesentlichen der Umsetzung in älteren Extension Arrays, die durch den Datentyp string und der Option string_storage="pyarrow" aktiviert wurde.

Ein PyArrow-Datentyp lässt sich in pandas entweder durch f"{dtype}[pyarrow]", also int64[pyarrow] für Integer Datentypen, oder mit

import pandas as pd
import pyarrow as pa

dtype = pd.ArrowDtype(pa.int64)

festlegen. Diese Datentypen sind überall erlaubt.

Der für I/O-Funktionen neue Parameter dtype_backend dient auch zum Erzeugen DataFrames mit PyArrow-Arrays. Damit eine Funktion einen PyArrow-DataFrame zurückgibt, muss der Parameter den Wert "pyarrow" haben.

Einige I/O-Funktionen haben darüber hinaus eine PyArrow-spezifische Engine, die deutlich performanter ist, weil sie nativ PyArrow-Arrays erzeugt.

Ein weiterer Vorteil von PyArrow-DataFrames ist die verbesserte Interoperabilität mit anderen Arrow-Bibliotheken. Dabei kann es sich entweder um native PyArrow-Bibliotheken handeln oder andere DataFrame-Bibliotheken wie cuDF oder Polars. Beim Konvertieren zwischen den Bibliotheken kann auf eine Kopie der Daten verzichtet werden. Marc Garcia, der Teil des pandas-core-Teams ist, hat einen Blogbeitrag verfasst, der diesen Teil genauer erläutert.

Bislang hat pandas alle Zeitstempel mit Nanosekunden-Präzision dargestellt. Im Folgenden entspricht asm8 der datetime-Repräsentation:

# pandas 1.5.3
 
In[1]: pd.Timestamp("2019-12-31").asm8
Out[1]: 2019-12-31T00:00:00.000000000

# pandas 2.0:

In[1]: pd.Timestamp("2019-12-31").asm8
Out[1]: 2019-12-31T00:00:00

Aufgrund der hohen Genauigkeit war es unmöglich, Datumswerte vor dem 21. September 1677 oder nach dem 11. April 2264 darzustellen, da die zugehörigen Werte mit Nanosekunden-Präzision die 64-bit-Integer-Grenze überschritten hätten. Alle weiteren Datumswerte haben einen Fehler zurückgegeben. Das war insbesondere für Untersuchungen über Jahrtausende oder Jahrmillionen hinderlich.

Die Heise-Konferenz zu Machine Learning

Am 10. und 11. Mai findet die Minds Mastering Machines in Karlsruhe statt. Die seit 2018 von iX, heise Developer und dpunkt.verlag ausgerichtete Fachkonferenz richtet sich in erster Linie an Data Scientists, Data Engineers und Developer, die Machine-Learning-Projekte in die Realität umsetzen.

Das Programm bietet an zwei Tagen gut 30 Vorträge unter anderem zu Sprachmodellen, Cybersecurity, Resilienz, Federate Learning, Modelloptimierung, MLOps mit Argo Workflows und dem EU AI Act.

pandas 2.0 behebt das Problem mit drei neuen Präzisionsstufen:

  • Sekunden
  • Millisekunden
  • Mikrosekunden

Dadurch kann die Library alle Jahre zwischen -2.9e11 und 2.9e11 darstellen und beispielsweise einen Zeitstempel für das Jahr 1000 erzeugen:

In[5]: pd.Timestamp("1000-10-11", unit="s") Out[5]: Timestamp('1000-10-11 00:00:00')

Die unterschiedlichen Präzisionsstufen steuert der Parameter unit.

Ein großer Teil von pandas ist auf Zeitstempel mit Nanosekunden-Präzision ausgelegt. Daher musste das Team die Methoden aufwendig umbauen. Da die unterschiedlichen Präzisionsgrade noch neu sind, kann es sein, dass einzelne Bereiche der API noch nicht wie gewünscht funktionieren.

pandas 1.5.0 hat erstmals den Mechanismus Copy-on-Write (CoW) eingeführt. Mit pandas 2.0 ist die Implementierung weit genug fortgeschritten, um CoW ohne Schwierigkeiten zu verwenden. CoW ist aber noch nicht standardmäßig aktiviert.

Copy-on-Write bedeutet, dass sich jedes aus einem pandas-Objekt erzeugte neue pandas-Objekt so verhält, als wären die darunter liegenden Daten kopiert worden. Es ist insbesondere nicht mehr erlaubt, zwei verschiedene Objekte durch eine Operation zu verändern.

Wenn CoW nicht aktiviert ist, kann die folgende Operation zwei Objekte verändern:

df = pd.DataFrame({"a": [1, 2, 3], "b": 1})
view = df["a"]
view.iloc[0] = 100


# df:

     a  b
0  100  1
1    2  1
2    3  1

Der Code verändert sowohl df als auch view, da das Objekt view eine View auf die Daten von df ist. Das ist zum einen nicht leicht zu verstehen, da man wissen muss, ob ein Objekt eine View oder eine Kopie der ursprünglichen Daten ist. Außerdem verursachte das Vorgehen viele SettingWithCopyWarnings. Copy-on-Write vereinfacht die APIs der Methoden und verbessert gleichzeitig die durchschnittliche Performance.

pd.options.mode.copy_on_write = True

df = pd.DataFrame({"a": [1, 2, 3], "b": 1})
view = df["a"]
view.iloc[0] = 100


# df:

python
   a  b
0  1  1
1  2  1
2  3  1

CoW stellt sicher, dass pandas die Daten von view kopiert, bevor die iloc-Operation die Daten aktualisiert. Mit aktiviertem CoW ändert der Code demnach nur view, während df konstant bleibt. Dadurch sind Operationen berechenbarer und die API ist einfacher zu verstehen.

Die Umsetzung erfordert in diesem Fall eine zuvor nicht benötigte Kopie, was zunächst einen Performanceverlust bedeutet. Da es mit CoW nicht möglich ist, zwei Objekte mit einer Operation zu verändern, kann pandas jedoch in vielen anderen Bereichen auf defensive Kopien verzichten. Nahezu alle Methoden innerhalb von pandas haben die Daten eines DataFrame kopiert. Diese Kopien entfallen in pandas 2.0, wenn CoW aktiviert ist. Das Team hat bislang mehr als 60 Methoden optimiert. Die Library erstellt die Kopie erst beim Modifizieren eines DataFrame. Deswegen sollte CoW dazu beitragen, dass sich der durchschnittliche pandas-Workflow beschleunigt.

Entwicklerinnen und Entwickler können indirekt beeinflussen, wann pandas eine Kopie erstellt. Wenn die Inputvariable durch den Output einer Methode überschrieben wird, teilt sich das neue Objekt keine Daten mehr mit dem alten:

df = pd.DataFrame({"a": [1, 2, 3], "b": 1})
df = df.reset_index()
df.iloc[0, 0]

In diesem Fall muss die Library keine Kopie erstellen, da das alte Objekt nicht mehr existiert und Änderungen somit nur ein Objekt betreffen.

Ein Nebeneffekt von CoW ist, dass das ChainedAssignment, das für die meisten SettingWithCopyWarnings verantwortlich ist, konsistent nicht mehr funktioniert:

df = pd.DataFrame({"a": [1, 2, 3], "b": 1})
df["a"][df["b"] > 4] = 100

Solche Anweisungen haben bisher je nach Anordnung der Operationen funktioniert. Beim Austausch der Filter wird df auch jetzt nicht aktualisiert. Diese Anweisung erzeugt mit CoW eine ChainedAssignmentError-Warnung und lässt df unverändert.

Diese Anweisung lässt sich zu einer loc-Operation umformen, die auch mit CoW funktioniert:

df = pd.DataFrame({"a": [1, 2, 3], "b": 1})
df.loc[df["b"] > 4, "a"] = 100

Im Zuge der Weiterentwicklung von CoW kam zudem der Vorschlag auf, die Parameter copy und inplace weitgehend aus pandas zu entfernen. Die Diskussion findet im zugehörigen Pull Request auf GitHub statt.

Grundsätzlich rät der Autor dazu, neue Anwendungen unter Copy-on-Write zu entwickeln. Derzeit ist geplant, dass CoW mit dem nächsten Major Release für alle Anwendungen aktiviert ist, und es soll voraussichtlich keinen Nicht-CoW-Modus mehr geben. Wenn bisherige Anwendungen nicht darauf angewiesen sind, dass eine Operation mehrere Objekte modifiziert, verhindert der CoW-Modus einige Fehler. Grundsätzlich sollte er außerdem die Performance verbessern.

Die letzte Version der 1.x-Serie (1.5.3) zeigte gut 150 Warnungen für Anpassungen in pandas 2.0, die nicht kompatibel mit alten Versionen sind. Folgende Änderungen können potenziell größere Auswirkungen haben:

  • Der standardmäßig vergebene Datentyp für eine leere Series ist nun object statt float64.
  • Ein Index kann ab pandas 2.0 mit beliebigen NumPy-Datentypen umgehen und ist immer als Index repräsentiert. Frühere Versionen haben dafür unter anderem Klassen wie Int64Index verwendet, die nun entfernt wurden.
  • Der Parameter numeric_only ist nun konsistent über alle Aggregationsfunktionen hinweg implementiert. Außerdem ist der Default-Wert False. Aggregationen mit nicht numerische Spalten lösen neuerdings einen Fehler aus. Früheren Versionen haben für einige Funktionen die Spalte entfernt und für andere ebenfalls einen Fehler ausgegeben. Die Anpassung soll verhindern, dass unbeabsichtigt nicht numerische Spalten aus dem DataFrame verschwinden.

Anwendungen, die unter 1.5.3 keine Warnungen ausgeben, sollten problemlos mit pandas 2.0 zusammenarbeiten.

pandas 2.0 bringt einige neue Features, die künftige Entwicklungen von Applikationen mit der Python-Bibliothek nachhaltig verändern. Insbesondere eine Migration auf DataFrames, die von PyArrow-Arrays abgebildet werden, hat Potenzial. Copy-on-Write wird die Anzahl der Kopien innerhalb von pandas signifikant reduzieren und führt zu einer besser verständlichen API.

Patrick Höfler
ist Teil des pandas core development Teams und arbeitet als Senior Software Entwickler bei Coiled. Er beschäftigt sich hauptsächlich mit der Weiterentwicklung der Open-Source-Bibliothek Dask. Die Weiterentwicklung von Open-Source-Software ist ihm ein wichtiges Anliegen.

(rme)