Pandas vs. Polars: Ein vollständiger Vergleich von Syntax, Geschwindigkeit und Speicher
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:

  1. Wir nutzen die psutil-Bibliothek um die Speichernutzung vor und nach Vorgängen zu verfolgen
  2. Beide Bibliotheken lesen dieselbe Datei und führen Filterung und Gruppierung durch
  3. 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().rss kann 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 mit pl.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.



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert