Se lavori con i dati, conosci bene quella sensazione: il kernel Jupyter che arranca, la rotellina che gira mentre Pandas mastica a fatica un CSV da qualche milione di righe. È la normalità per molti, un collo di bottiglia che spezza il ritmo dell’analisi. E se ci fosse un modo per trasformare quell’attesa in un’esecuzione istantanea, senza cambiare completamente il modo in cui pensi ai dati?
Questo non è il solito tutorial. È il racconto di come Polars, una libreria Python forgiata in Rust, stia cambiando le regole del gioco per l’analisi dei dati. Partendo da un semplice set di dati di vendita, vedremo come Polars non solo replica, ma ottimizza le operazioni che fai ogni giorno: calcolare ricavi, segmentare per canale, e persino domare gli outlier che inquinano le tue medie.
Mettiti comodo, perché stai per rendere i tuoi script non solo più veloci, ma anche più eleganti e potenti.
💡 Analisi vendite con Polars: Ricavi, Canali, Outlier e Date
In questo tutorial vedremo come analizzare un dataset di vendite con Polars e
Scopriremo come:
- calcolare i ricavi totali per prodotto,
- ottenere i ricavi medi per canale e mese,
- identificare il prodotto più redditizio,
- estrarre il mese da una colonna di date,
- e gestire gli outlier con la tecnica del Winsorizing.
🧮 Setup iniziale
import polars as pl
# Esempio di dataset "vendite"
vendite = pl.DataFrame({
"prodotto": ["A","B","A","C","B","A"],
"quantità": [2, 1, 5, 3, 4, None],
"prezzo_unitario": [10.0, 20.0, 10.0, 7.5, 20.0, 10.0],
"canale": ["web","store","web","web","store","web"],
"data": ["2025-01-10", "2025-01-15", "2025-02-20", "2025-02-25", "2025-02-28", "2025-01-30"]
})
🧹 Pulizia e preparazione dei dati
1️⃣ Controlliamo i tipi:
vendite.dtypes
[String, Int64, Float64, String, String]
2️⃣ Convertiamo eventuali stringhe numeriche e gestiamo i valori mancanti:
vendite_clean = (
vendite
.with_columns([
pl.col("quantità").cast(pl.Float64).fill_null(0.0),
pl.col("prezzo_unitario").cast(pl.Float64),
pl.col("data").str.strptime(pl.Date, "%Y-%m-%d") # conversione in tipo data
])
)
3️⃣ Creiamo la colonna mese estraendola da data:
vendite_clean = vendite_clean.with_columns(
pl.col("data").dt.month().alias("mese")
)
.dt. — puoi anche estrarre anno, giorno_della_settimana, ecc.🔹 Esercizio 1 — Ricavi totali per prodotto
ricavi_prodotto = (
vendite_clean
.with_columns(
(pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo")
)
.group_by("prodotto")
.agg([
pl.col("ricavo").sum().alias("ricavo_totale"),
pl.count().alias("n_transazioni")
])
.sort("ricavo_totale", descending=True)
)
print(ricavi_prodotto)
shape: (3, 3)
┌──────────┬───────────────┬───────────────┐
│ prodotto ┆ ricavo_totale ┆ n_transazioni │
│ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ u32 │
╞══════════╪═══════════════╪═══════════════╡
│ B ┆ 100.0 ┆ 2 │
│ A ┆ 70.0 ┆ 3 │
│ C ┆ 22.5 ┆ 1 │
└──────────┴───────────────┴───────────────┘
Il ricavo viene calcolato come:
[math]\text{ricavo} = \text{quantità} \times \text{prezzo\_unitario}[/math]
L’aggregazione per prodotto calcola:
[math]\displaystyle \begin{aligned}
\text{ricavo\_totale} &= \sum (\text{ricavo}) \text{ per prodotto} \\
\text{n\_transazioni} &= \text{Count}() \text{ per prodotto}
\end{aligned}[/math]
💬 Commento applicativo:
Immagina di essere un Product Manager. Questa tabella non è solo un aggregato di numeri, è la tua prima linea di difesa strategica. Vedere che il prodotto B genera il 44% in più di ricavi del prodotto A nonostante un numero inferiore di transazioni (2 contro 3) è un’informazione critica. Significa che il suo valore medio per transazione è molto più alto. La prossima domanda? “Perché? Stiamo vendendo un pacchetto premium? O il prodotto A ha un problema di prezzo?”. Ecco come una semplice group_by diventa il punto di partenza per decisioni di business.
Possiamo scoprire quali prodotti generano il maggior fatturato e quanti ordini contribuiscono a tale risultato.
🔹 Esercizio 2 — Ricavi medi per canale e mese
Prima di calcolare le medie, eliminiamo l’effetto distorsivo degli outlier.
✂️ Gestione outlier con Winsorizing
Il Winsorizing consiste nel limitare i valori estremi al percentile inferiore e superiore desiderato (ad esempio[math]5^\text{o} \text{ e } 95^\text{o} \text{ percentile}[/math]).
# Calcolo ricavo per ogni transazione
vendite_ricavo = vendite_clean.with_columns(
(pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo")
)
# Calcolo soglie per Winsorizing
q05 = vendite_ricavo.select(pl.col("ricavo").quantile(0.05)).item()
q95 = vendite_ricavo.select(pl.col("ricavo").quantile(0.95)).item()
# Applichiamo Winsorizing
vendite_winsor = vendite_ricavo.with_columns(
pl.when(pl.col("ricavo") < q05).then(q05) .when(pl.col("ricavo") > q95).then(q95)
.otherwise(pl.col("ricavo"))
.alias("ricavo_winsor")
)
L’espressione condizionale per il Winsorizing è:
[math]\displaystyle \begin{aligned}
\text{ricavo winsor} &=
\begin{cases}
q_{0.05} & \text{se } \text{ricavo} < q_{0.05} \\ q_{0.95} & \text{se } \text{ricavo} > q_{0.95} \\
\text{ricavo} & \text{altrimenti}
\end{cases}
\end{aligned}[/math]
Ora calcoliamo la media per canale e mese, usando la colonna winsorizzata.
ricavo_medio = (
vendite_winsor
.group_by(["canale", "mese"])
.agg([
pl.col("ricavo_winsor").mean().alias("ricavo_medio"),
pl.col("ricavo_winsor").sum().alias("ricavo_totale"),
pl.count().alias("n_transazioni")
])
.sort(["canale", "mese"])
)
print(ricavo_medio)
shape: (4, 5)
┌────────┬──────┬──────────────┬───────────────┬───────────────┐
│ canale ┆ mese ┆ ricavo_medio ┆ ricavo_totale ┆ n_transazioni │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ i8 ┆ f64 ┆ f64 ┆ u32 │
╞════════╪══════╪══════════════╪═══════════════╪═══════════════╡
│ store ┆ 1 ┆ 20.0 ┆ 20.0 ┆ 1 │
│ store ┆ 2 ┆ 80.0 ┆ 80.0 ┆ 1 │
│ web ┆ 1 ┆ 10.0 ┆ 20.0 ┆ 2 │
│ web ┆ 2 ┆ 36.25 ┆ 72.5 ┆ 2 │
└────────┴──────┴──────────────┴───────────────┴───────────────┘
💬 Commento applicativo: Qui il nostro analista si trasforma in uno strategist di marketing. La tabella mostra che lo store fisico ha un ricavo medio per transazione molto più alto a febbraio ($80) rispetto a gennaio ($20). Il canale web, invece, ha una crescita più contenuta. Perché? Forse a febbraio è partita una promozione in-store che ha funzionato benissimo. L’uso del Winsorizing è fondamentale qui: garantisce che la nostra analisi non sia viziata da un singolo cliente “anomalo” che ha fatto un acquisto enorme, ma rifletta un andamento reale e ripetibile. È la differenza tra un’analisi superficiale e una su cui un manager può investire budget.
L’uso del Winsorizing evita che un singolo valore anomalo (es. un ordine da $10.000 \, \text{€}$) alteri la media, fornendo una visione più realistica.
🔹 Esercizio 3 — Top prodotto per ricavi
Variante 1 – Classica (ordinamento)
top_prodotto = (
vendite_clean
.with_columns((pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo"))
.group_by("prodotto")
.agg(pl.col("ricavo").sum().alias("ricavo_totale"))
.sort("ricavo_totale", descending=True)
.head(1)
)
Variante 2 – Efficiente (con arg_max)
agg = (
vendite_clean
.with_columns((pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo"))
.group_by("prodotto")
.agg(pl.col("ricavo").sum().alias("ricavo_totale"))
)
top_idx = agg["ricavo_totale"].arg_max()
top_prodotto = agg[top_idx: top_idx+1]
print(top_prodotto)
Shape: (1, 2)
┌──────────┬───────────────┐
│ prodotto ┆ ricavo_totale │
│ --- ┆ --- │
│ str ┆ f64 │
╞══════════╪═══════════════╡
│ B ┆ 100.0 │
└──────────┴───────────────┘
💬 Commento applicativo:
Questa analisi è per il responsabile del reporting o per chi prepara le slide per il CDA. A volte, non serve un’analisi complessa, ma una risposta secca e precisa: “Qual è il nostro cavallo di battaglia?”. Il metodo arg_max non è solo un vezzo da programmatori per risparmiare qualche millisecondo. Su un dataset di decine di milioni di vendite, la differenza tra ordinare tutto e trovare direttamente l’indice del massimo è la differenza tra un report che gira in 30 secondi e uno che ci mette 5 minuti. In un ambiente di business intelligence, questa efficienza è tutto.
Perfetto se ti serve solo il prodotto top da evidenziare in dashboard o report.
⚠️ Errori comuni in Polars (e come evitarli)
| Errore tipico | Causa | Correzione |
|---|---|---|
Usare .groupby() invece di .group_by() |
Abitudine da Pandas | In Polars si usa .group_by() |
Usare df["colonna"] per selezionare |
In Polars restituisce una Series | Usa df.select("colonna") o espressioni pl.col() |
Dimenticare .collect() in Lazy mode |
Lazy non esegue subito | Aggiungi .collect() alla fine |
Usare .apply() come in Pandas |
Non esiste per DataFrame | Usa espressioni o .map_elements() |
| Aspettarsi broadcasting implicito | Pandas lo fa, Polars no | Usa pl.lit() per valori costanti |
Tipi sbagliati (Utf8 invece di numerico) |
File CSV con numeri come stringhe | Usa .cast(pl.Float64) e sostituisci le virgole. |
.with_columns() dentro loop |
Inefficiente | Passa tutte le colonne insieme |
📊 Extra: Polars Lazy API per dataset grandi
Se i tuoi dati superano milioni di righe, puoi passare alla modalità lazy (.lazy()).
Polars costruisce un piano ottimizzato di esecuzione prima di processare i dati:
result_lazy = (
vendite_clean.lazy()
.with_columns((pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo"))
.group_by(["canale", "mese"])
.agg(pl.col("ricavo").mean().alias("ricavo_medio"))
.sort(["canale", "mese"])
.collect() # esegue tutto in un colpo solo
)
💡 Vantaggio:Polars unisce automaticamente i passaggi compatibili, riducendo le scansioni e migliorando le performance. Questo è il cuore dell’ottimizzazione Lazy.
Articoli di approfondimento
- 🔴 Cos’è un file JSON: guida completa per sviluppatori e non
- 🔴 Data Lakehouse: cos’è, come funziona e perché unisce il meglio di Data Lake e Warehouse
- 🔴 Lakehouse: cos’è?
- 🔴 Databricks: il laboratorio intelligente per dati, AI e automazione aziendale
- 🔴 Guida pratica a Databricks 2025: crea il tuo primo progetto da zero
- 🔴 🚀 Guida completa 2025: installazione di PySpark e Java per elaborazione Big Data in locale
- 🔴 Guida pratica al Data Lakehouse: progetto IoT completo con Python, Spark e Delta Lake
- 🔴 File Parquet: la guida pratica per usarli con Python — esempi e vantaggi rispetto a CSV
- 🔴 Guida completa a Polars per Python: l’alternativa veloce a Pandas — esempi e tutorial
- 🔴 Guida pratica a Polars: esercizi con soluzioni per analizzare dati velocemente
Dietro le Quinte: Perché Questi Esercizi Sono Importanti
Ogni esercizio in questa guida non è casuale, ma è pensato per illustrare un concetto chiave di Polars e risolvere un problema reale che ogni analista di dati incontra.
-
Esercizio 1 – Ricavi totali per prodotto
Questo esercizio è l’essenza del paradigma “split-apply-combine”. È la prima domanda che ci si pone davanti a un dataset di vendite. La sua bellezza sta nella leggibilità delle espressioni Polars:
(pl.col("quantità") * pl.col("prezzo_unitario")).alias("ricavo")è quasi come scrivere una frase in inglese. Insegna a creare nuove colonne “al volo” e ad aggregare i risultati, il pane quotidiano dell’analisi. È interessante perché mostra come Polars gestisca la logica di business in modo pulito e sequenziale. -
Esercizio 2 – Ricavi medi per canale e mese con Winsorizing
Questo esercizio fa un salto di qualità. Introduce tre concetti cruciali:
- Grouping su più colonne (
["canale", "mese"]): Mostra come creare segmenti più granulari. - Gestione degli outlier: Il Winsorizing non è una funzione pre-costruita, ma lo abbiamo implementato elegantemente con l’espressione
pl.when().then().otherwise(). Questo dimostra la flessibilità di Polars: se una funzione non esiste, puoi costruirla tu stesso con i suoi mattoncini logici. - Medie robuste: L’esercizio sottolinea l’importanza di non fidarsi ciecamente dei dati grezzi. Preparare i dati prima di calcolare le medie è un segno di maturità analitica, e Polars lo rende un passaggio fluido e integrato.
- Grouping su più colonne (
-
Esercizio 3 – Top prodotto per ricavi con
arg_maxQuesto esercizio non riguarda solo il cosa (trovare il prodotto migliore), ma il come. Mettendo a confronto
sort().head(1)conarg_max(), si introduce il concetto di efficienza computazionale. Per un analista che lavora con dati in continua crescita, capire che esistono modi diversi (e più intelligenti) per ottenere lo stesso risultato è una lezione fondamentale.arg_maxè un esempio perfetto della filosofia di Polars: fornire strumenti ottimizzati per operazioni comuni, spingendo l’utente a scrivere codice che scala meglio.
🔎 Conclusione
In pochi passaggi abbiamo visto come costruire una pipeline di analisi pulita, veloce e leggibile con Polars:
- Calcolo dei ricavi totali
- Analisi canale [math]\times[/math] mese
- Identificazione del top performer
- Pulizia date e estrazione del mese
- Winsorizing per medie realistiche
- Gestione degli errori tipici
Polars è ideale per chi cerca performance da big data senza abbandonare la semplicità espressiva di Pandas.
Con l’approccio “expression-first” (pl.col(...)) e la Lazy API, puoi scrivere codice elegante e scalabile per analisi di business o marketing avanzate.




