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.
- Patrick Höfler
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.
Eigene Datentypen für die Library
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
undiloc
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.
Zusammenspiel mit Apache Arrow
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 PerformanceWarning
s 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 DataFrame
s 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.
Weniger genaue Datumswerte
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.
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.
Separierte Daten mit Copy-on-Write
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 SettingWithCopyWarning
s. 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 SettingWithCopyWarning
s 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.
Änderungen an der API
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 nunobject
stattfloat64
. - 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 wieInt64Index
verwendet, die nun entfernt wurden. - Der Parameter
numeric_only
ist nun konsistent über alle Aggregationsfunktionen hinweg implementiert. Außerdem ist der Default-WertFalse
. 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 demDataFrame
verschwinden.
Anwendungen, die unter 1.5.3 keine Warnungen ausgeben, sollten problemlos mit pandas 2.0 zusammenarbeiten.
Solide Basis für die Weiterentwicklung der Library
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)