Raggiungere l’alta velocità al di fuori della libreria Polars nativa

Generato da DALL-E 3

polare sta conquistando il mondo grazie alla sua velocità, all’efficienza della memoria e alla bellissima API. Se vuoi sapere quanto è potente, non guardare oltre il Benchmark DuckDB. E questi non utilizzano nemmeno la versione più recente di Polars.

Nonostante tutte le cose straordinarie che i Polari possono fare, tradizionalmente non è stata una soluzione migliore dei Panda fare TUTTI i calcoli che potresti voler fare. Ci sono alcune eccezioni in cui Polars non ha sovraperformato. Con il recente rilascio del sistema di plug-in Polars per Rust, tuttavia, potrebbe non essere più così.

Cos’è esattamente un plugin Polars? È semplicemente un modo per creare le tue espressioni Polars utilizzando Rust nativo ed esporle alle espressioni utilizzando uno spazio dei nomi personalizzato. Ti consente di prendere la velocità di Rust e applicarla al tuo Polars DataFrame per eseguire calcoli in un modo che sfrutta la velocità e gli strumenti integrati forniti da Polars.

Diamo un’occhiata ad alcuni esempi concreti.

Calcoli sequenziali

Un’area in cui Polars sembra mancare di alcune funzionalità sono le operazioni che richiedono la conoscenza del valore precedente di un DataFrame. I calcoli di natura sequenziale non sono sempre semplicissimi o efficienti da scrivere nelle espressioni polari native. Diamo un’occhiata a un esempio specifico.

Abbiamo il seguente algoritmo per calcolare il valore cumulativo di un array di numeri per una data esecuzione, definita come un insieme di numeri che hanno lo stesso segno. Per esempio:

┌───────┬───────────┐
│ value ┆ run_value │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═══════╪═══════════╡
│ 1 ┆ 1 │ # First run starts here
│ 2 ┆ 3 │
│ 3 ┆ 6 │
│ -1 ┆ -1 │ # Run resets here
│ -2 ┆ -3 │
│ 1 ┆ 1 │ # Run resets here
└───────┴───────────┘

Quindi vogliamo avere una somma cumulativa di una colonna che si reimposta ogni volta che il segno del valore passa da positivo a negativo o da negativo a positivo.

Cominciamo con una versione di base scritta in panda.

def calculate_runs_pd(s: pd.Series) -> pd.Series:
out = ()
is_positive = True
current_value = 0.0
for value in s:
if value > 0:
if is_positive:
current_value += value
else:
current_value = value
is_positive = True
else:
if is_positive:
current_value = value
is_positive = False
else:
current_value += value
out.append(current_value)
return pd.Series(out)

Iteriamo su una serie, calcolando il valore corrente della corsa in ciascuna posizione e restituendo una nuova serie di Panda.

Analisi comparativa

Prima di procedere, imposteremo alcuni parametri di riferimento. Misureremo sia la velocità di esecuzione che il consumo di memoria utilizzando pytest-benchmark E pytest-memray. Imposteremo il problema in modo tale da avere una colonna di entità, una colonna di tempo e una colonna di funzionalità. L’obiettivo è calcolare i valori di esecuzione per ciascuna entità nei dati nel tempo. Imposteremo il numero di entità e timestamp ciascuno su 1.000, fornendoci un DataFrame con 1.000.000 di righe.

Quando eseguiamo la nostra implementazione di Panda rispetto al nostro benchmark utilizzando la funzionalità di applicazione del gruppo di Panda, otteniamo i seguenti risultati:

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *