Se lavori con i dati, probabilmente conosci questa situazione: lanci un groupby('citta') e ottieni “New York”, “new york”, e “NY” come tre risultati separati. È un incubo che rende le tue analisi inutilizzabili.
O forse i tuoi dati sono già puliti, ma quel groupby su milioni di righe impiega minuti interi, bloccando il tuo lavoro perché confrontare stringhe è lento.
Entrambi i problemi (dati sporchi e analisi lente) si risolvono smettendo di usare le stringhe. Il primo passo è normalizzare i dati (trasformando “NY” in “new york”). Il secondo è codificarli.
pandas.factorize è lo strumento perfetto per questo secondo passo. È un comando velocissimo che converte le tue colonne di testo pulite in numeri interi leggeri.
In questa guida, vediamo come usarlo per ottimizzare memoria, velocizzare le analisi e creare “chiavi primarie” (PK) uniche, anche quando la chiave è sparsa su più colonne.
1. Cos’è pandas.factorize?
Pensala come un “assegnatore di etichette” incredibilmente veloce.
Il suo compito è prendere una colonna di dati (stringhe, date, oggetti) e “tradurla” in numeri interi. È fondamentale perché la maggior parte delle operazioni sui dati (e quasi tutti gli algoritmi di machine learning) funzionano molto meglio, o esclusivamente, con i numeri.
2. L’analogia: Il Registro di Classe
Il modo migliore per capire factorize è con un’analogia. Immagina di essere il primo giorno di scuola. Hai un elenco di studenti che si iscrivono a un corso, nell’ordine in cui arrivano:
['Anna', 'Bruno', 'Chiara', 'Anna', 'Bruno', 'Davide']
Vuoi creare un registro ufficiale (gli uniques) e assegnare a ogni studente un numero di matricola (i codes) per velocizzare l’appello.
- Anna è la prima. Non è nel registro.
- La aggiungi al registro:
['Anna'] - Le assegni il primo numero: 0
- La aggiungi al registro:
- Bruno è il secondo. Non è nel registro.
- Lo aggiungi al registro:
['Anna', 'Bruno'] - Gli assegni il numero successivo: 1
- Lo aggiungi al registro:
- Chiara è la terza. Non è nel registro.
- La aggiungi al registro:
['Anna', 'Bruno', 'Chiara'] - Le assegni il numero successivo: 2
- La aggiungi al registro:
- Arriva di nuovo Anna. Guardi il registro. C’è già!
- Non la aggiungi di nuovo.
- Le assegni il suo numero di matricola esistente: 0
- Arriva di nuovo Bruno. C’è già!
- Gli assegni il suo numero: 1
- Davide è l’ultimo. Non è nel registro.
- Lo aggiungi al registro:
['Anna', 'Bruno', 'Chiara', 'Davide'] - Gli assegni il numero successivo: 3
- Lo aggiungi al registro:
Alla fine, pandas.factorize ti restituisce due cose:
codes(I numeri di matricola):[0, 1, 2, 0, 1, 3]uniques(Il registro / Legenda):['Anna', 'Bruno', 'Chiara', 'Davide']
Il “registro” (uniques) è la tua chiave di lettura. Se vedi un 2 nei codes, sai che significa “Chiara”.
3. Esempio Pratico (Singola Colonna)
Vediamo l’analogia in azione con il codice:
import pandas as pd
# I nostri dati originali
studenti = pd.Series(['Anna', 'Bruno', 'Chiara', 'Anna', 'Bruno', 'Davide'])
# Applichiamo factorize
codes, uniques = pd.factorize(studenti)
print(f"Studenti Originali:\n{studenti.values}")
print("---")
print(f"Codes (Numeri di Matricola):\n{codes}")
print("---")
print(f"Uniques (Il Registro / Legenda):\n{uniques}")
Output:
Studenti Originali:
['Anna' 'Bruno' 'Chiara' 'Anna' 'Bruno' 'Davide']
---
Codes (Numeri di Matricola):
[0 1 2 0 1 3]
---
Uniques (Il Registro / Legenda):
['Anna' 'Bruno' 'Chiara' 'Davide']
4. Sintassi e Parametri
La sintassi completa è pandas.factorize(values, sort=False, na_sentinel=-1, size_hint=None).
I parametri che contano davvero sono due:
sort=False(Default): Gli ID (0, 1, 2...) vengono assegnati nell’ordine in cui appaiono nei dati (come nella nostra analogia).sort=True: Prima di assegnare gli ID,factorizeordina alfabeticamente/numericamente i valori unici. (Consort=True, ‘Anna’ sarebbe sempre0, ‘Bruno’1, ‘Chiara’2, ecc.).na_sentinel=-1(Default): Cosa fare con i valori mancanti (NaN,None)? Di default, gli assegna -1. Questo è utilissimo, come vedremo.
5. Il Caso Avanzato: Creare una PK Univoca da Colonne Multiple
Questo è il trucco più potente. Spesso una chiave univoca non è una sola colonna, ma la combinazione di più colonne (es. Nome + Cognome + Data di Nascita).
pandas.factorize non accetta un DataFrame, solo un array. La soluzione? Creare un array di tuple.
Perché le tuple? Perché sono hashable (immutabili, come le stringhe), mentre le liste non lo sono. factorize ha bisogno di oggetti hashable per creare il suo “registro”.
Esempio Pratico Multi-colonna
Immaginiamo di voler creare un ID univoco per ogni coppia (cliente, regione).
import pandas as pd
import numpy as np
df = pd.DataFrame({
'cliente': ['Mario Rossi', 'Luigi Verdi', 'Mario Rossi', 'Anna Bianchi', 'Mario Rossi'],
'regione': ['Lombardia', 'Lazio', 'Lombardia', 'Lazio', 'Veneto'],
'importo': [100, 200, 100, 300, 400]
})
# 1. Definiamo le colonne che compongono la nostra chiave
colonne_pk = ['cliente', 'regione']
# 2. Creiamo l'array di tuple
# Usiamo list(zip(...)), che è molto più veloce di .apply(tuple, axis=1)
valori_combinati = list(zip(*[df[col] for col in colonne_pk]))
# print(valori_combinati)
# [('Mario Rossi', 'Lombardia'),
# ('Luigi Verdi', 'Lazio'),
# ('Mario Rossi', 'Lombardia'),
# ('Anna Bianchi', 'Lazio'),
# ('Mario Rossi', 'Veneto')]
# 3. Applichiamo factorize ai valori combinati
codes, uniques = pd.factorize(valori_combinati)
# 4. Assegniamo i codici come nuova colonna PK
df['ID_Univoco'] = codes
print("DataFrame con PK Multi-colonna:")
print(df)
print("\nLegenda (Uniques):")
# Nota come gli uniques sono ora tuple!
print(uniques)
Output:
DataFrame con PK Multi-colonna:
cliente regione importo ID_Univoco
0 Mario Rossi Lombardia 100 0
1 Luigi Verdi Lazio 200 1
2 Mario Rossi Lombardia 100 0
3 Anna Bianchi Lazio 300 2
4 Mario Rossi Veneto 400 3
Legenda (Uniques):
[('Mario Rossi', 'Lombardia'), ('Luigi Verdi', 'Lazio'), ('Anna Bianchi', 'Lazio'), ('Mario Rossi', 'Veneto')]
Questo ID_Univoco è ora una chiave surrogata perfetta. Occupa pochissima memoria (un intero) ma rappresenta in modo univoco la combinazione di due colonne stringa.
6. Perché e Quando Usarlo (Il Contesto Professionale)
factorize brilla in tre scenari principali:
1. Risparmio di Memoria (Il Data Engineer)
Invece di memorizzare la stringa “Stati Uniti d’America” un milione di volte (occupando tantissimo spazio), puoi memorizzare l’intero 0 un milione di volte.
Per un Data Engineer, questo è il segreto per ridurre un DataFrame da 10GB a 2GB prima di salvarlo in formato Parquet. pandas.factorize è il motore che alimenta il tipo di dati category in pandas.
2. Prestazioni (Il Data Analyst)
Confrontare, ordinare e raggruppare numeri interi è migliaia di volte più rapido per un computer che farlo con stringhe.
Se devi fare un groupby('Nazione') su un miliardo di righe, trasformare prima ‘Nazione’ in ID_Nazione numerico renderà l’operazione quasi istantanea.
3. Preprocessing (Il Data Scientist)
Un modello di machine learning non capisce “Anna” o “Bruno”. factorize è il modo più rapido per fare un Label Encoding di base.
Il trucco da professionisti: Non usare factorize sul tuo test set.
- Esegui
factorizesolo sul training set e salva l’arrayuniques(la “legenda”). - Quando hai nuovi dati (test set o dati in produzione), usa quella legenda per tradurli, così:
# uniques_training = ['Mario', 'Luigi'] (salvati dal training) nuovi_dati = pd.Series(['Luigi', 'Anna', 'Mario', 'Luigi']) # get_indexer mappa i nuovi dati sulla legenda # 'Anna' non era nel training, quindi ottiene -1 (non trovato) nuovi_codes = pd.Index(uniques_training).get_indexer(nuovi_dati) # Risultato: [1, -1, 0, 1]
Questo garantisce che ‘Luigi’ sia sempre 1 e ‘Mario’ sempre 0.
7. Analisi Applicativa dei Parametri
Gli esempi di codice sono semplici, ma le loro implicazioni sono profonde.
- Peculiarità 1:
na_sentinel=-1(Default)- Perché è interessante? L’uso di
-1è una scelta di design geniale. Perché non0? Perché0è un codice valido (il primo valore “Anna” nel nostro esempio). Usando-1, i valoriNaNsono immediatamente identificabili e filtrabili (df[df['ID_Cliente'] == -1]). È la “chiave nulla” perfetta.
- Perché è interessante? L’uso di
- Peculiarità 2:
na_sentinel=None- Perché è interessante (e rischioso)? Questo comando produce
NaN(un float) nell’arraycodes. Questo “inquina” l’array di interi, forzandolo a diventare di tipofloat64per contenere ilNaN. Si perde il vantaggio di avere un array di interi pulito e performante. È quasi sempre meglio la soluzione di default (-1).
- Perché è interessante (e rischioso)? Questo comando produce
- Peculiarità 3:
sort=True- Perché è interessante? Questo garantisce la riproducibilità. Con
sort=True, ‘Anna Bianchi’ (che inizia per ‘A’) riceverà sempre un ID più basso di ‘Mario Rossi’ (che inizia per ‘M’), indipendentemente dall’ordine in cui appaiono nei dati. È fondamentale se, per qualche motivo, devi eseguirefactorizeseparatamente su due file (es. training e test) e vuoi che gli stessi valori abbiano lo stesso ID in entrambi.
- Perché è interessante? Questo garantisce la riproducibilità. Con
8. Contesto: Altre Tecniche di Codifica (Alternative)
factorize (o Label Encoding) è solo una delle tante tecniche di codifica. È importante sapere quando le altre sono migliori.
- Codifica One-Hot (One-Hot Encoding)
- È la tecnica standard e più sicura per le variabili Nominali (categorie senza ordine, come “Rosso”, “Verde”, “Blu”).
- Crea una nuova colonna binaria (0 o 1) per ogni categoria.
- Vantaggi: Impedisce al modello di pensare che “Blu” (codice 2) sia “più grande” di “Rosso” (codice 0).
- Svantaggi: Crea moltissime colonne se hai migliaia di categorie.
- Codifica Frequenza (Frequency Encoding)
- Sostituisce la categoria con la frequenza con cui appare (es. “Italia” -> 0.45, perché il 45% delle righe è “Italia”).
- Vantaggi: Non crea nuove colonne.
- Svantaggi: Categorie diverse con la stessa frequenza otterranno lo stesso numero, perdendo informazioni.
La Scelta:
- Variabile Nominale (es. Nomi di città):
- Per modelli lineari (Regressione, SVM): Usa One-Hot Encoding.
- Per modelli ad albero (Random Forest, XGBoost):
factorizeva benissimo ed è più veloce.
- Variabile Ordinale (es. “Basso”, “Medio”, “Alto”):
- Usa
factorize, ma assicurati che l’ordine sia corretto (es. consort=Trueo pre-ordinando i dati).
- Usa
- Creazione di PK o Ottimizzazione Memoria:
- Usa sempre
factorize.
- Usa sempre
L’esempio nell’introduzione
Come abbiamo visto, pandas.factorize è uno strumento di codifica, non di pulizia.
Se i tuoi dati sono “sporchi” (come “New York”, “new york”, “NY”), factorize li tratterà come tre cose completamente diverse, assegnando loro tre ID differenti (es. 0, 1, 2).
Questo è l’opposto di ciò che vogliamo.
La soluzione è una pipeline in due fasi:
- Normalizzazione: Pulire i dati per renderli tutti uguali (es. “new york”).
- Codifica: Usare
factorizesui dati puliti per ottenere l’ID univoco.
Vediamo come farlo.
Risolviamo il Caso: Normalizzare Dati Sporchi prima di factorize
Prendiamo un DataFrame d’esempio che rappresenta esattamente dall’ “incubo” descritto nell’introduzione:
import pandas as pd
df = pd.DataFrame({
'citta_ordine': [
'New York',
'Milano',
'new york', # Duplicato 1 (case)
'Roma',
'NY', # Duplicato 2 (abbreviazione)
'milano', # Duplicato 3 (case)
'New York' # Duplicato 0 (originale)
],
'valore': [100, 50, 90, 200, 80, 40, 110]
})
print("Dati Originali ('Sporchi'):")
print(df)
Output:
Dati Originali ('Sporchi'):
citta_ordine valore
0 New York 100
1 Milano 50
2 new york 90
3 Roma 200
4 NY 80
5 milano 40
6 New York 110
1. Il Problema: factorize su Dati Sporchi
Se applichiamo factorize direttamente, otteniamo un disastro:
codes_errati, uniques_errati = pd.factorize(df['citta_ordine'])
print(f"\nCodes (ERRATI): {codes_errati}")
print(f"Uniques (ERRATI): {uniques_errati.values}")
Output:
Codes (ERRATI): [0 1 2 3 4 5 0]
Uniques (ERRATI): ['New York' 'Milano' 'new york' 'Roma' 'NY' 'milano']
Abbiamo 6 ID univoci… ma in realtà le città sono solo 3 (New York, Milano, Roma). L’analisi fallirebbe.
2. La Soluzione: Normalizzazione + factorize
Dobbiamo “insegnare” a Pandas che “NY” e “new york” sono la stessa cosa.
Fase A: Pulizia e Normalizzazione
Per prima cosa, creiamo una colonna normalizzata.
- Uniformiamo il “case”: Trasformiamo tutto in minuscolo (
.str.lower()). - Mappiamo le eccezioni: Usiamo un dizionario e
.replace()per correggere le abbreviazioni.
# 1. Convertiamo tutto in minuscolo
colonna_normalizzata = df['citta_ordine'].str.lower()
# 2. Definiamo un dizionario per le abbreviazioni e i sinonimi
mappa_correzioni = {
'ny': 'new york'
# 'mi': 'milano', # Se avessimo anche 'mi'
# 'rm': 'roma' # Se avessimo anche 'rm'
}
# 3. Applichiamo la mappatura (replace è sicuro, sostituisce solo le chiavi)
colonna_normalizzata = colonna_normalizzata.replace(mappa_correzioni)
# Aggiungiamo la colonna pulita al DataFrame per vederla
df['citta_norm'] = colonna_normalizzata
print("\nDataFrame dopo la Normalizzazione:")
print(df)
Output:
DataFrame dopo la Normalizzazione:
citta_ordine valore citta_norm
0 New York 100 new york
1 Milano 50 milano
2 new york 90 new york
3 Roma 200 roma
4 NY 80 new york
5 milano 40 milano
6 New York 110 new york
Ora sì! La colonna citta_norm è pulita e coerente.
Fase B: factorize sui Dati Puliti
Adesso, e solo adesso, applichiamo factorize sulla colonna citta_norm.
codes_corretti, uniques_corretti = pd.factorize(df['citta_norm'])
# Aggiungiamo l'ID finale
df['ID_Citta'] = codes_corretti
print("\nDataFrame Finale con ID Corretti:")
print(df)
print(f"\nCodes (CORRETTI): {codes_corretti}")
print(f"Uniques (CORRETTI): {uniques_corretti.values}")
Output Finale:
DataFrame Finale con ID Corretti:
citta_ordine valore citta_norm ID_Citta
0 New York 100 new york 0
1 Milano 50 milano 1
2 new york 90 new york 0
3 Roma 200 roma 2
4 NY 80 new york 0
5 milano 40 milano 1
6 New York 110 new york 0
Codes (CORRETTI): [0 1 0 2 0 1 0]
Uniques (CORRETTI): ['new york' 'milano' 'roma']
⇒ Abbiamo risolto l’incubo.
- Tutte le varianti di “New York” hanno ID = 0.
- Tutte le varianti di “Milano” hanno ID = 1.
- “Roma” ha ID = 2.
Questo approccio (Clean -> Normalize -> Factorize) è la vera soluzione al problema che hai citato e risolve anche la seconda parte: un groupby('ID_Citta') ora sarà fulmineo perché confronterà solo interi (0, 1, 2) invece di stringhe sporche.
Conclusione
pandas.factorize è molto più di un semplice strumento di preprocessing. È una leva fondamentale per la performance, l’ottimizzazione della memoria e la creazione di chiavi surrogate robuste. Ora hai non solo l’analogia per ricordarlo, ma anche la tecnica avanzata per applicarlo a chiavi multi-colonna, trasformandolo da un comando utile a uno strumento indispensabile nel tuo arsenale.
Articoli di approfondimento
- 👉 🆕 Novità Pandas come un Database: gestione di chiavi primarie (PK) e integrità referenziale (FK)
- 👉 🆕 Novità Progettare database relazionali: guida pratica dall’ERD all’integrità dei dati
- 👉 🆕 Novità Pandas — Tutorial completo per l’analisi e la manipolazione dei dati
- 👉 Guida a map() in Python: scrivere codice elegante e veloce
- 👉 Dataset di qualità: guida completa — dalla raccolta dati alla pulizia per la tua AI
- 👉 Pulizia dati con Python: guida pratica — da dataset sporco ad analisi affidabile con Pandas
- 👉 5 esercizi di Pandas: guida passo-passo da base ad esperto




