
Bild vom Autor
# Einführung
Wenn Sie mit Daten in Python gearbeitet haben, haben Sie es mit ziemlicher Sicherheit verwendet Pandas. Seit über einem Jahrzehnt ist sie die Anlaufstelle für Datenmanipulation. Aber vor kurzem, Polaren hat ernsthaft an Bedeutung gewonnen. Polars verspricht schneller, speichereffizienter und intuitiver zu sein als Pandas. Aber lohnt es sich, es zu lernen? Und wie unterschiedlich ist es wirklich?
In diesem Artikel vergleichen wir Pandas und Eisbären nebeneinander. Sie sehen Leistungsbenchmarks und lernen die Syntaxunterschiede kennen. Am Ende können Sie eine fundierte Entscheidung für Ihr nächstes Datenprojekt treffen.
Den Code finden Sie auf GitHub.
# Erste Schritte
Lassen Sie uns zuerst beide Bibliotheken installieren:
pip set up pandas polars
Notiz: Dieser Artikel verwendet Pandas 2.2.2 und Polars 1.31.0.
Für diesen Vergleich verwenden wir auch einen Datensatz, der groß genug ist, um echte Leistungsunterschiede zu erkennen. Wir werden verwenden Schwindler So generieren Sie Testdaten:
Jetzt können wir mit dem Codieren beginnen.
# Messen der Geschwindigkeit durch Lesen großer CSV-Dateien
Beginnen wir mit einem der häufigsten Vorgänge: dem Lesen einer CSV-Datei. Wir erstellen einen Datensatz mit 1 Million Zeilen, um echte Leistungsunterschiede zu sehen.
Lassen Sie uns zunächst unsere Beispieldaten generieren:
import pandas as pd
from faker import Faker
import random
# Generate a big CSV file for testing
pretend = Faker()
Faker.seed(42)
random.seed(42)
information = {
'user_id': vary(1000000),
'title': (pretend.title() for _ in vary(1000000)),
'electronic mail': (pretend.electronic mail() for _ in vary(1000000)),
'age': (random.randint(18, 80) for _ in vary(1000000)),
'wage': (random.randint(30000, 150000) for _ in vary(1000000)),
'division': (random.selection(('Engineering', 'Gross sales', 'Advertising and marketing', 'HR', 'Finance'))
for _ in vary(1000000))
}
df_temp = pd.DataFrame(information)
df_temp.to_csv('large_dataset.csv', index=False)
print("✓ Generated large_dataset.csv with 1M rows")
Dieser Code erstellt eine CSV-Datei mit realistischen Daten. Vergleichen wir nun die Lesegeschwindigkeiten:
import pandas as pd
import polars as pl
import time
# pandas: Learn CSV
begin = time.time()
df_pandas = pd.read_csv('large_dataset.csv')
pandas_time = time.time() - begin
# Polars: Learn CSV
begin = time.time()
df_polars = pl.read_csv('large_dataset.csv')
polars_time = time.time() - begin
print(f"Pandas learn time: {pandas_time:.2f} seconds")
print(f"Polars learn time: {polars_time:.2f} seconds")
print(f"Polars is {pandas_time/polars_time:.1f}x sooner")
Ausgabe beim Lesen der Beispiel-CSV:
Pandas learn time: 1.92 seconds
Polars learn time: 0.23 seconds
Polars is 8.2x sooner
Folgendes passiert: Wir messen, wie lange es dauert, bis jede Bibliothek dieselbe CSV-Datei liest. Während Pandas seinen traditionellen Single-Threaded-CSV-Reader verwendet, parallelisiert Polars das Lesen automatisch über mehrere CPU-Kerne hinweg. Wir berechnen den Beschleunigungsfaktor.
Auf den meisten Geräten werden Sie feststellen, dass Polars CSVs zwei- bis fünfmal schneller liest. Dieser Unterschied wird bei größeren Dateien noch deutlicher.
# Messen der Speichernutzung während des Betriebs
Geschwindigkeit ist nicht die einzige Überlegung. Sehen wir uns an, wie viel Speicher jede Bibliothek verbraucht. Wir führen eine Reihe von Vorgängen durch und messen den Speicherverbrauch. Bitte pip set up psutil falls Sie es noch nicht in Ihrer Arbeitsumgebung haben:
import pandas as pd
import polars as pl
import psutil
import os
import gc # Import rubbish collector for higher reminiscence launch makes an attempt
def get_memory_usage():
"""Get present course of reminiscence utilization in MB"""
course of = psutil.Course of(os.getpid())
return course of.memory_info().rss / 1024 / 1024
# — - Check with Pandas — -
gc.gather()
initial_memory_pandas = get_memory_usage()
df_pandas = pd.read_csv('large_dataset.csv')
filtered_pandas = df_pandas(df_pandas('age') > 30)
grouped_pandas = filtered_pandas.groupby('division')('wage').imply()
pandas_memory = get_memory_usage() - initial_memory_pandas
print(f"Pandas reminiscence delta: {pandas_memory:.1f} MB")
del df_pandas, filtered_pandas, grouped_pandas
gc.gather()
# — - Check with Polars (keen mode) — -
gc.gather()
initial_memory_polars = get_memory_usage()
df_polars = pl.read_csv('large_dataset.csv')
filtered_polars = df_polars.filter(pl.col('age') > 30)
grouped_polars = filtered_polars.group_by('division').agg(pl.col('wage').imply())
polars_memory = get_memory_usage() - initial_memory_polars
print(f"Polars reminiscence delta: {polars_memory:.1f} MB")
del df_polars, filtered_polars, grouped_polars
gc.gather()
# — - Abstract — -
if pandas_memory > 0 and polars_memory > 0:
print(f"Reminiscence financial savings (Polars vs Pandas): {(1 - polars_memory/pandas_memory) * 100:.1f}%")
elif pandas_memory == 0 and polars_memory > 0:
print(f"Polars used {polars_memory:.1f} MB whereas Pandas used 0 MB.")
elif polars_memory == 0 and pandas_memory > 0:
print(f"Polars used 0 MB whereas Pandas used {pandas_memory:.1f} MB.")
else:
print("Can not compute reminiscence financial savings as a consequence of zero or unfavorable reminiscence utilization delta in each frameworks.")
Dieser Code misst den Speicherbedarf:
- Wir nutzen die psutil-Bibliothek um die Speichernutzung vor und nach Vorgängen zu verfolgen
- Beide Bibliotheken lesen dieselbe Datei und führen Filterung und Gruppierung durch
- Wir berechnen den Unterschied im Speicherverbrauch
Beispielausgabe:
Pandas reminiscence delta: 44.4 MB
Polars reminiscence delta: 1.3 MB
Reminiscence financial savings (Polars vs Pandas): 97.1%
Die obigen Ergebnisse zeigen das Speichernutzungsdelta für Pandas und Polars, wenn Filter- und Aggregationsvorgänge für durchgeführt werden large_dataset.csv.
- Pandas Speicherdelta: Gibt den von Pandas für die Vorgänge verbrauchten Speicher an.
- Polarspeicher-Delta: Gibt den von Polars für dieselben Vorgänge verbrauchten Speicher an.
- Speichereinsparungen (Polaren vs. Pandas): Diese Metrik gibt einen Prozentsatz an, wie viel weniger Speicher Polars im Vergleich zu Pandas verbrauchten.
Aufgrund der spaltenorientierten Datenspeicherung und der optimierten Ausführungs-Engine weist Polars häufig Speichereffizienz auf. Normalerweise werden Sie durch die Verwendung von Polars eine Verbesserung von 30 bis 70 % feststellen.
Notiz: Allerdings werden sequentielle Speichermessungen innerhalb desselben Python-Prozesses verwendet
psutil.Course of(...).memory_info().rsskann manchmal irreführend sein. Der Speicherzuweiser von Python gibt Speicher nicht immer sofort an das Betriebssystem zurück, sodass eine „bereinigte“ Basislinie für einen nachfolgenden Check möglicherweise immer noch von früheren Vorgängen beeinflusst wird. Für möglichst genaue Vergleiche sollten Assessments idealerweise in separaten, isolierten Python-Prozessen ausgeführt werden.
# Vergleich der Syntax für grundlegende Operationen
Schauen wir uns nun an, wie sich die Syntax zwischen den beiden Bibliotheken unterscheidet. Wir behandeln die häufigsten Vorgänge, die Sie verwenden werden.
// Spalten auswählen
Wählen wir eine Teilmenge von Spalten aus. Wir erstellen hierfür (und für die folgenden Beispiele) einen viel kleineren DataFrame.
import pandas as pd
import polars as pl
# Create pattern information
information = {
'title': ('Anna', 'Betty', 'Cathy'),
'age': (25, 30, 35),
'wage': (50000, 60000, 70000)
}
# Pandas strategy
df_pandas = pd.DataFrame(information)
result_pandas = df_pandas(('title', 'wage'))
# Polars strategy
df_polars = pl.DataFrame(information)
result_polars = df_polars.choose(('title', 'wage'))
# Different: Extra expressive
result_polars_alt = df_polars.choose((pl.col('title'), pl.col('wage')))
print("Pandas consequence:")
print(result_pandas)
print("nPolars consequence:")
print(result_polars)
Die wichtigsten Unterschiede hier:
- Pandas verwendet die Klammernotation:
df(('col1', 'col2')) - Polars verwendet die
.choose()Verfahren - Polars unterstützt auch das Ausdrucksstärkere
pl.col()Syntax, die für komplexe Operationen leistungsstark ist
Ausgabe:
Pandas consequence:
title wage
0 Anna 50000
1 Betty 60000
2 Cathy 70000
Polars consequence:
form: (3, 2)
┌───────┬────────┐
│ title ┆ wage │
│ — - ┆ — - │
│ str ┆ i64 │
╞═══════╪════════╡
│ Anna ┆ 50000 │
│ Betty ┆ 60000 │
│ Cathy ┆ 70000 │
└───────┴────────┘
Beide erzeugen die gleiche Ausgabe, aber die Syntax von Polars beschreibt deutlicher, was Sie tun.
// Zeilen filtern
Jetzt filtern wir die Zeilen:
# pandas: Filter rows the place age > 28
filtered_pandas = df_pandas(df_pandas('age') > 28)
# Different Pandas syntax with question
filtered_pandas_alt = df_pandas.question('age > 28')
# Polars: Filter rows the place age > 28
filtered_polars = df_polars.filter(pl.col('age') > 28)
print("Pandas filtered:")
print(filtered_pandas)
print("nPolars filtered:")
print(filtered_polars)
Beachten Sie die Unterschiede:
- In Pandas verwenden wir die boolesche Indizierung mit Klammernotation. Sie können auch die verwenden
.question()Verfahren. - Polars verwendet die
.filter()Methode mitpl.col()Ausdrücke. - Die Syntax von Polars liest sich eher wie SQL: „Filtern, wenn das Spaltenalter größer als 28 ist“.
Ausgabe:
Pandas filtered:
title age wage
1 Betty 30 60000
2 Cathy 35 70000
Polars filtered:
form: (2, 3)
┌───────┬─────┬────────┐
│ title ┆ age ┆ wage │
│ — - ┆ — - ┆ — - │
│ str ┆ i64 ┆ i64 │
╞═══════╪═════╪════════╡
│ Betty ┆ 30 ┆ 60000 │
│ Cathy ┆ 35 ┆ 70000 │
└───────┴─────┴────────┘
// Neue Spalten hinzufügen
Jetzt fügen wir dem DataFrame neue Spalten hinzu:
# pandas: Add a brand new column
df_pandas('bonus') = df_pandas('wage') * 0.1
df_pandas('total_comp') = df_pandas('wage') + df_pandas('bonus')
# Polars: Add new columns
df_polars = df_polars.with_columns((
(pl.col('wage') * 0.1).alias('bonus'),
(pl.col('wage') * 1.1).alias('total_comp')
))
print("Pandas with new columns:")
print(df_pandas)
print("nPolars with new columns:")
print(df_polars)
Ausgabe:
Pandas with new columns:
title age wage bonus total_comp
0 Anna 25 50000 5000.0 55000.0
1 Betty 30 60000 6000.0 66000.0
2 Cathy 35 70000 7000.0 77000.0
Polars with new columns:
form: (3, 5)
┌───────┬─────┬────────┬────────┬────────────┐
│ title ┆ age ┆ wage ┆ bonus ┆ total_comp │
│ — - ┆ — - ┆ — - ┆ — - ┆ — - │
│ str ┆ i64 ┆ i64 ┆ f64 ┆ f64 │
╞═══════╪═════╪════════╪════════╪════════════╡
│ Anna ┆ 25 ┆ 50000 ┆ 5000.0 ┆ 55000.0 │
│ Betty ┆ 30 ┆ 60000 ┆ 6000.0 ┆ 66000.0 │
│ Cathy ┆ 35 ┆ 70000 ┆ 7000.0 ┆ 77000.0 │
└───────┴─────┴────────┴────────┴────────────┘
Folgendes passiert:
- Pandas verwendet die direkte Spaltenzuweisung, die den vorhandenen DataFrame ändert
- Polars verwendet
.with_columns()und gibt einen neuen DataFrame zurück (standardmäßig unveränderlich) - In Polars verwenden Sie
.alias()um die neue Spalte zu benennen
Der Polars-Ansatz fördert die Unveränderlichkeit und macht Datentransformationen lesbarer.
# Messen der Leistung beim Gruppieren und Aggregieren
Schauen wir uns ein nützlicheres Beispiel an: das Gruppieren von Daten und das Berechnen mehrerer Aggregationen. Dieser Code zeigt, wie wir Daten nach Abteilung gruppieren, mehrere Statistiken für verschiedene Spalten berechnen und beide Vorgänge zeitlich festlegen, um den Leistungsunterschied zu erkennen:
# Load our giant dataset
df_pandas = pd.read_csv('large_dataset.csv')
df_polars = pl.read_csv('large_dataset.csv')
# pandas: Group by division and calculate stats
import time
begin = time.time()
result_pandas = df_pandas.groupby('division').agg({
'wage': ('imply', 'median', 'std'),
'age': 'imply'
}).reset_index()
result_pandas.columns = ('division', 'avg_salary', 'median_salary', 'std_salary', 'avg_age')
pandas_time = time.time() - begin
# Polars: Identical operation
begin = time.time()
result_polars = df_polars.group_by('division').agg((
pl.col('wage').imply().alias('avg_salary'),
pl.col('wage').median().alias('median_salary'),
pl.col('wage').std().alias('std_salary'),
pl.col('age').imply().alias('avg_age')
))
polars_time = time.time() - begin
print(f"Pandas time: {pandas_time:.3f}s")
print(f"Polars time: {polars_time:.3f}s")
print(f"Speedup: {pandas_time/polars_time:.1f}x")
print("nPandas consequence:")
print(result_pandas)
print("nPolars consequence:")
print(result_polars)
Ausgabe:
Pandas time: 0.126s
Polars time: 0.077s
Speedup: 1.6x
Pandas consequence:
division avg_salary median_salary std_salary avg_age
0 Engineering 89954.929266 89919.0 34595.585863 48.953405
1 Finance 89898.829762 89817.0 34648.373383 49.006690
2 HR 90080.629637 90177.0 34692.117761 48.979005
3 Advertising and marketing 90071.721095 90154.0 34625.095386 49.085454
4 Gross sales 89980.433386 90065.5 34634.974505 49.003168
Polars consequence:
form: (5, 5)
┌─────────────┬──────────────┬───────────────┬──────────────┬───────────┐
│ division ┆ avg_salary ┆ median_salary ┆ std_salary ┆ avg_age │
│ — - ┆ — - ┆ — - ┆ — - ┆ — - │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞═════════════╪══════════════╪═══════════════╪══════════════╪═══════════╡
│ HR ┆ 90080.629637 ┆ 90177.0 ┆ 34692.117761 ┆ 48.979005 │
│ Gross sales ┆ 89980.433386 ┆ 90065.5 ┆ 34634.974505 ┆ 49.003168 │
│ Engineering ┆ 89954.929266 ┆ 89919.0 ┆ 34595.585863 ┆ 48.953405 │
│ Advertising and marketing ┆ 90071.721095 ┆ 90154.0 ┆ 34625.095386 ┆ 49.085454 │
│ Finance ┆ 89898.829762 ┆ 89817.0 ┆ 34648.373383 ┆ 49.00669 │
└─────────────┴──────────────┴───────────────┴──────────────┴───────────┘
Aufschlüsselung der Syntax:
- Pandas verwendet ein Wörterbuch, um Aggregationen anzugeben, was bei komplexen Vorgängen verwirrend sein kann
- Polars verwendet Methodenverkettung: Jede Operation ist klar und benannt
Die Polars-Syntax ist ausführlicher, aber auch besser lesbar. Sie können sofort sehen, welche Statistiken berechnet werden.
# Verstehen der verzögerten Auswertung bei Polaren
Die verzögerte Auswertung ist eine der hilfreichsten Funktionen von Polars. Das heisst Ihre Abfrage wird nicht sofort ausgeführt. Stattdessen wird der gesamte Vorgang geplant und vor der Ausführung optimiert.
Sehen wir uns das in Aktion an:
import polars as pl
# Learn in lazy mode
df_lazy = pl.scan_csv('large_dataset.csv')
# Construct a posh question
consequence = (
df_lazy
.filter(pl.col('age') > 30)
.filter(pl.col('wage') > 50000)
.group_by('division')
.agg((
pl.col('wage').imply().alias('avg_salary'),
pl.len().alias('employee_count')
))
.filter(pl.col('employee_count') > 1000)
.kind('avg_salary', descending=True)
)
# Nothing has been executed but!
print("Question plan created, however not executed")
# Now execute the optimized question
import time
begin = time.time()
result_df = consequence.gather() # This runs the question
execution_time = time.time() - begin
print(f"nExecution time: {execution_time:.3f}s")
print(result_df)
Ausgabe:
Question plan created, however not executed
Execution time: 0.177s
form: (5, 3)
┌─────────────┬───────────────┬────────────────┐
│ division ┆ avg_salary ┆ employee_count │
│ — - ┆ — - ┆ — - │
│ str ┆ f64 ┆ u32 │
╞═════════════╪═══════════════╪════════════════╡
│ HR ┆ 100101.595816 ┆ 132212 │
│ Advertising and marketing ┆ 100054.012365 ┆ 132470 │
│ Gross sales ┆ 100041.01049 ┆ 132035 │
│ Finance ┆ 99956.527217 ┆ 132143 │
│ Engineering ┆ 99946.725458 ┆ 132384 │
└─────────────┴───────────────┴────────────────┘
Hier, scan_csv() lädt die Datei nicht sofort; es ist nur geplant, es zu lesen. Wir verketten mehrere Filter, Gruppierungen und Sortierungen. Polars analysiert die gesamte Abfrage und optimiert sie. Es könnte beispielsweise vor dem Lesen aller Daten gefiltert werden.
Nur wenn wir anrufen .gather() findet die eigentliche Berechnung statt? Die optimierte Abfrage wird viel schneller ausgeführt, als wenn jeder Schritt einzeln ausgeführt würde.
# Zusammenfassung
Wie man sieht, ist Polars für die Datenverarbeitung mit Python äußerst nützlich. Es ist schneller, speichereffizienter und verfügt über eine sauberere API als Pandas. Das heißt, Pandas gehen nirgendwo hin. Es hat über ein Jahrzehnt Entwicklungszeit, ein riesiges Ökosystem und Millionen von Benutzern. Für viele Projekte ist Pandas immer noch die richtige Wahl.
Lernen Sie Polars, wenn Sie groß angelegte Analysen für Knowledge-Engineering-Projekte und dergleichen in Betracht ziehen. Die Syntaxunterschiede sind nicht groß und die Leistungssteigerungen sind actual. Behalten Sie jedoch Pandas in Ihrem Toolkit, um Kompatibilität und schnelle Erkundungsarbeiten zu gewährleisten.
Probieren Sie Polars zunächst in einem Nebenprojekt oder einer Datenpipeline aus, die langsam läuft. Sie werden schnell ein Gefühl dafür bekommen, ob es für Ihren Anwendungsfall das Richtige ist. Viel Spaß beim Daten-Wrangeln!
Bala Priya C ist ein Entwickler und technischer Redakteur aus Indien. Sie arbeitet gerne an der Schnittstelle von Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Zu ihren Interessen- und Fachgebieten gehören DevOps, Datenwissenschaft und Verarbeitung natürlicher Sprache. Sie liebt es zu lesen, zu schreiben, zu programmieren und Kaffee zu trinken! Derzeit arbeitet sie daran, zu lernen und ihr Wissen mit der Entwickler-Neighborhood zu teilen, indem sie Tutorials, Anleitungen, Meinungsbeiträge und mehr verfasst. Bala erstellt außerdem ansprechende Ressourcenübersichten und Programmier-Tutorials.
