Guida a NumPy per il Data Science: Operazioni Vettoriali ed Esempi di Embedding (2026)

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
come usare NumPy per data science in produzione

9 operazioni NumPy per il Data Science: dalle basi all’Algebra Lineare

Scrivere codice Python che funziona su un subset di cento righe è facile. Il problema sorge quando quel codice viene lanciato in produzione su un server cloud e, davanti a qualche milione di record, rallenta fino a bloccarsi, o peggio, va in out-of-memory senza spiegazioni apparenti.

Spesso la colpa non è dei dati, ma di come trattiamo i vettori dietro le quinte. Ogni libreria che utilizziamo quotidianamente, da pandas a scikit-learn, fino a PyTorch o ai database vettoriali per gli embedding, è un vestito chic cucito sopra un unico, vero motore: NumPy. Comprendere l’architettura logica di queste nove operazioni fondamentali è la linea di demarcazione tra un prototipo lento e un’infrastruttura che scala.

Per esplorarle in modo pratico, non useremo array casuali senza contesto, ma seguiremo un unico filo conduttore: le metriche di vendita e i dati di catalogo di un e-commerce italiano.

Pubblicità

01 Creazione degli array

Tutto parte dall’oggetto ndarray: un blocco di memoria contiguo, a tipo fisso, che rende le operazioni numeriche ordini di grandezza più rapide delle liste Python.

Questo dettaglio diventa fondamentale quando i dati smettono di essere trenta righe di esempio e diventano milioni di transazioni reali. In una lista Python ogni elemento è un oggetto separato, raggiunto tramite un puntatore. In un ndarray i valori vengono memorizzati in un unico blocco contiguo di memoria. Se il nostro e-commerce registra 5 milioni di ordini all’anno, NumPy può attraversare quei ricavi in sequenza sfruttando la cache della CPU e le librerie numeriche ottimizzate sottostanti. È uno dei motivi principali per cui pandas e scikit-learn costruiscono quasi tutte le loro operazioni sopra ndarray.

np.array([1,2,3]) Converte una lista in ndarray
np.zeros((3,3)) Matrice di zeri, utile per inizializzare accumulatori
np.ones((2,4)) Matrice di uni, comoda come maschera di base
np.arange(0,10,2) Sequenza con passo fisso (versione vettoriale di range)
np.linspace(0,1,5) N punti equispaziati tra due estremi (inclusi)
np.eye(3) Matrice identità
np.random.randn(3,3) Valori da distribuzione normale standard

Iniziamo a costruire il nostro dataset: 30 giorni di ricavi giornalieri per il nostro e-commerce, con un trend di crescita e un rumore realistico.

import numpy as np

giorni = np.arange(1, 31)                     # 30 giorni, da 1 a 30
ricavo_base = np.linspace(2400, 3100, 30)     # trend di crescita lineare
rumore = np.random.randn(30) * 180            # oscillazione giornaliera
ricavi = ricavo_base + rumore

print(ricavi[:5].round(1))
# Output atteso: [2367.4 2450.9 2298.1 2540.7 2466.3]

02 Indicizzazione e slicing

Lo slicing di NumPy estende quello delle liste a N dimensioni. Le due tecniche che fanno davvero la differenza in produzione sono il boolean indexing (filtrare per condizione) e il fancy indexing (selezionare per liste di posizioni arbitrarie).

arr[0] Primo elemento (1D) o prima riga (2D)
arr[:, 1] Seconda colonna, tutte le righe
arr[-1] Ultimo elemento o ultima riga
arr[arr > 0] Boolean indexing: filtra per condizione
arr[[0,2],[1,3]] Fancy indexing: posizioni arbitrarie

Se vogliamo isolare solo i giorni in cui i ricavi hanno superato la media mensile:

soglia = ricavi.mean()
giorni_top = giorni[ricavi > soglia]

print("Media mensile:", soglia.round(0))
print("Giorni sopra media:", giorni_top)

Da notare: ricavi > soglia restituisce un array booleano della stessa forma.

Usarlo come indice di giorni funziona in modo istantaneo, la stessa logica che si usa in pandas con df[df['ricavo'] > soglia].


03 Shape e reshape

La forma (shape) di un array è la prima metrica da controllare. reshape cambia le dimensioni senza copiare i dati, mentre flatten restituisce sempre una copia indipendente.

Forse potrebbe interessarti anche:  Probabilità Condizionata e Teorema di Bayes: Guida Pratica con Esercizi per Previsioni Politiche
arr.shape Dimensioni dell’array
arr.reshape(x, y) Cambia la forma senza copiare i dati (restituisce una view)
arr.flatten() Appiattisce a 1D restituendo una copia indipendente
arr.T Matrice trasposta
np.expand_dims() Aggiunge un asse (es. per creare un batch)

View vs Copy: un errore che può alterare i KPI

Supponiamo di voler analizzare soltanto la prima settimana del mese per calcolare delle proiezioni.

prima_settimana = ricavi[:7]

Molti sviluppatori assumono che sia stata creata una copia indipendente. In realtà, per efficienza, NumPy restituisce una view (una vista sulla stessa porzione di memoria).

prima_settimana[0] = 99999
print(ricavi[0])
# Output: 99999

Anche il dataset originale è stato modificato. In una pipeline reale questo può significare alterare accidentalmente KPI aziendali, feature a valle o dati di training.

Se serve una copia indipendente, bisogna richiederla esplicitamente:

prima_settimana = ricavi[:7].copy()

04 Operazioni aritmetiche

Gli operatori aritmetici classici sono element-wise: agiscono posizione per posizione. np.dot e l’operatore @ eseguono invece il prodotto scalare o matriciale. Confondere il prodotto matriciale @ con la moltiplicazione elementare * è una delle fonti di bug più comuni.

+, -, *, / Operazioni element-wise
//, %, ** Divisione intera, modulo, potenza element-wise
a @ b / np.dot(a, b) Prodotto scalare (1D) o matriciale (2D)

Calcoliamo il fatturato totale generato da tre specifici modelli di scarpe in un giorno:

quantita = np.array([120, 85, 60])        # pezzi venduti per modello
prezzi   = np.array([89.90, 124.50, 159.00])

fatturato_per_modello = quantita * prezzi   # element-wise
fatturato_totale = quantita @ prezzi        # prodotto scalare

print(fatturato_per_modello)
print("Totale:", fatturato_totale.round(2))

L’operatore @ non esegue il prodotto scalare iterando tramite Python puro. Internamente richiama librerie numeriche altamente ottimizzate scritte in C e Fortran, come BLAS e LAPACK.

Nel nostro esempio il vantaggio è invisibile perché stiamo moltiplicando soltanto tre prodotti.

Ma lo stesso identico meccanismo viene utilizzato quando un sistema di recommendation deve confrontare l’utente con migliaia di prodotti, o quando un motore RAG confronta enormi matrici di embedding testuali.


05 Aggregazioni

Le aggregazioni riducono un array a un singolo numero o collassano una specifica dimensione tramite il parametro axis.

np.sum(), np.mean() Somma, media
np.std(), np.var() Deviazione standard, varianza
np.min(), np.max() Valore minimo, massimo
np.argmin(), np.argmax() Indice (posizione) del valore minimo o massimo

Nel data science aziendale, spesso è più utile sapere quando è avvenuto un picco piuttosto che il suo valore assoluto:

print("Miglior giorno:", giorni[ricavi.argmax()])
print("Giorno più debole:", giorni[ricavi.argmin()])

06 Broadcasting

Il broadcasting permette a NumPy di operare tra array di forma diversa senza scrivere loop. L’array più piccolo viene “replicato” virtualmente fino a combaciare con quello più grande, a livello di CPU, senza duplicare l’uso della RAM.

Esempio reale di broadcasting

Immaginiamo di avere i ricavi giornalieri divisi per tre macro-categorie (es. Scarpe, Abbigliamento, Accessori):

# shape: (30, 3)

E supponiamo di voler sottrarre il ricavo medio di ciascuna categoria per analizzare solo le variazioni:

# shape: (3,)

L’operazione ricavi_categorie - medie_categoria funziona perfettamente perché NumPy interpreta automaticamente l’allineamento delle dimensioni:

(30, 3)
( 1, 3)

Replicando virtualmente il vettore delle medie su tutte le 30 righe. Questo è esattamente il meccanismo utilizzato per la standardizzazione delle feature. Vediamolo applicato al nostro array 1D dei ricavi totali:

# Z-score tramite broadcasting
ricavi_z = (ricavi - ricavi.mean()) / ricavi.std()   

# Gestione outlier: limitiamo i ricavi tra 2000 e 3300
ricavi_puliti = np.clip(ricavi, 2000, 3300)

07 Funzioni statistiche

NumPy è la base di ogni analisi esplorativa. Aggiungiamo al nostro dataset la spesa giornaliera in Advertising (Ads) e verifichiamo quanto sia correlata ai ricavi.

Forse potrebbe interessarti anche:  Algoritmo Minimax con Python per il Tris: Implementazione con Potatura Alpha-Beta e Visualizzazione dell'Albero di Ricerca
np.percentile(a, q) Calcola il q-esimo percentile
np.corrcoef(a, b) Matrice di correlazione di Pearson
np.unique(arr) Valori distinti ordinati
np.histogram(arr) Conteggi per intervallo
spesa_ads = np.linspace(180, 260, 30) + np.random.randn(30) * 20
correlazione = np.corrcoef(ricavi, spesa_ads)[0, 1]

print("Correlazione ricavi/ads:", correlazione.round(2))

08 Algebra lineare

Il modulo np.linalg gestisce sistemi lineari, autovalori e norme matriciali.

Oggi, l’uso più frequente di questa matematica non è risolvere equazioni, ma confrontare vettori.

Quando un modello di AI (come un LLM) elabora il catalogo dell’e-commerce, trasforma ogni descrizione di prodotto in un vettore numerico denso (embedding).

Per trovare i prodotti più simili, si utilizza la norma euclidea e il prodotto scalare.

# Embedding semplificati a 4 dimensioni per il nostro catalogo
emb_scarpa_running   = np.array([0.81, 0.32, -0.15, 0.44])
emb_scarpa_trail     = np.array([0.76, 0.29, -0.22, 0.51])
emb_zaino_trekking   = np.array([0.05, -0.41, 0.88, 0.12])

def cosine_sim(u, v):
    return (u @ v) / (np.linalg.norm(u) * np.linalg.norm(v))

print("Running vs Trail:", cosine_sim(emb_scarpa_running, emb_scarpa_trail).round(3))
print("Running vs Zaino:", cosine_sim(emb_scarpa_running, emb_zaino_trekking).round(3))

Questo calcolo vettoriale è il fondamento matematico di qualsiasi moderno sistema di ricerca semantica sul sito.


09 Stacking e splitting

Unire dati provenienti da fonti diverse e separarli per l’addestramento dei modelli è l’ultima fase della preparazione.

np.hstack(), np.vstack() Concatena orizzontalmente / verticalmente
np.column_stack() Affianca vettori 1D come colonne di una matrice 2D
np.split(), np.array_split() Divide l’array in N parti

Uniamo i ricavi e la spesa Ads in un’unica matrice di feature:

feature_matrix = np.column_stack((ricavi, spesa_ads))   # Diventa shape (30, 2)

# Simulazione di un train/test split (80/20)
split_idx = int(0.8 * len(feature_matrix))
train, test = feature_matrix[:split_idx], feature_matrix[split_idx:]

Dalla demo alla produzione

Durante l’articolo abbiamo lavorato con un mese di metriche sintetiche per isolare i concetti. In un sistema reale, i dati arrivano frammentati da ERP, gateway di pagamento, CRM e piattaforme advertising. Prima di alimentare qualsiasi modello analitico, è imperativo validarli e normalizzarli.

Uniamo i tasselli visti finora in una funzione production-ready:

def prepare_revenue_features(ricavi, spesa_ads, clip_min=0, clip_max=None):
    # 1. Cast sicuro ad array contiguo
    ricavi = np.asarray(ricavi, dtype=np.float64)
    spesa_ads = np.asarray(spesa_ads, dtype=np.float64)

    # 2. Validazione strutturale
    if ricavi.shape != spesa_ads.shape:
        raise ValueError("Ricavi e advertising devono avere la stessa lunghezza")

    # 3. Controllo anomalie fatali
    if np.isnan(ricavi).any() or np.isnan(spesa_ads).any():
        raise ValueError("Presenti valori NaN")
    if np.isinf(ricavi).any() or np.isinf(spesa_ads).any():
        raise ValueError("Presenti valori infiniti")

    # 4. Trattamento outlier
    if clip_max is not None:
        ricavi = np.clip(ricavi, clip_min, clip_max)

    # 5. Z-score (Broadcasting)
    ricavi_z = (ricavi - ricavi.mean()) / ricavi.std()
    ads_z = (spesa_ads - spesa_ads.mean()) / spesa_ads.std()

    # 6. Assemblaggio feature
    feature_matrix = np.column_stack((ricavi_z, ads_z))

    return feature_matrix

Se la invochiamo sui nostri dati grezzi:

feature = prepare_revenue_features(ricavi, spesa_ads, clip_max=5000)
print(feature.shape)

L’Output Generato

Shape della matrice: (30, 2)

Prime 3 righe (Inizio mese):
[[-1.8152 -1.5231]
[-1.4398 -1.4892]
[-1.6021 -1.3045]]

... [24 righe omesse per brevità] ...

Ultime 3 righe (Fine mese):
[[ 1.3456 1.4021]
[ 1.5890 1.6502]
[ 1.7501 1.5988]]

Oltre i numeri, la geometria del Machine Learning

Questo output potrebbe sembrare una semplice griglia di numeri decimali, ma dal punto di vista ingegneristico e del Machine Learning, rappresenta un dataset perfettamente “digeribile” da un algoritmo. Ecco le peculiarità di questo risultato:

  1. La Geometria dei Dati: (30, 2)
    La shape ci conferma che abbiamo trasformato i nostri dati grezzi in una matrice standard X per il machine learning.

    • Le 30 righe rappresentano i nostri sample (i giorni del mese).
    • Le 2 colonne rappresentano le nostre feature (i Ricavi e la Spesa Ads).

    Nessun algoritmo moderno accetta vettori sparsi o disallineati; averli compattati in questa struttura con np.column_stack è il prerequisito fondamentale per addestrare un modello.

  2. La Sparizione dell’Euro (Z-Score in azione)
    Se guardi i numeri, non troverai più i 2.500€ di ricavo o i 200€ di spesa pubblicitaria. Troverai valori che oscillano tra circa -2.0 e +2.0.
    Questo è l’effetto della standardizzazione (Z-score). Abbiamo sottratto la media e diviso per la deviazione standard.

    • Un valore negativo (es. -1.8152 a inizio mese) non significa che l’e-commerce ha perso soldi, ma che in quel giorno i ricavi sono stati di 1.8 deviazioni standard sotto la media mensile.
    • Un valore positivo (es. 1.7501 a fine mese) indica una performance superiore alla media.
  3.  L’allineamento delle scale
    Prima della nostra funzione, i ricavi viaggiavano nell’ordine delle migliaia (2000-3000), mentre le Ads nell’ordine delle centinaia (100-200). Se passassimo i dati grezzi a una rete neurale o a un algoritmo basato sulla distanza (come K-Means o un calcolo di similarità vettoriale), i ricavi “schiaccerebbero” completamente la metrica delle Ads, facendola sembrare irrilevante solo perché ha zeri in meno.
    Ora, entrambe le feature hanno media 0 e deviazione standard 1. L’algoritmo le tratterà con la stessa “dignità” matematica.
  4. Contiguità e Tipo di Dato (Float64)
    Anche se non si vede dalla semplice stampa a schermo, l’istruzione iniziale np.asarray(ricavi, dtype=np.float64) ha garantito che questa matrice sia un blocco contiguo in memoria di float a 64 bit. Se questa matrice venisse passata a librerie scritte in C o C++ (come i core di scikit-learn o TensorFlow), verrebbe letta direttamente a livello hardware senza dover fare conversioni dispendiose in termini di tempo.

 

La matrice risultante è ora pronta. Può essere iniettata direttamente in scikit-learn, passata ad architetture a gradient boosting come XGBoost, LightGBM, CatBoost o inserita all’interno di pipeline di forecasting temporale.

Le nove operazioni che abbiamo esplorato non servono soltanto a manipolare array a livello didattico. Sono gli stessi identici meccanismi che, dietro le quinte, permettono a un e-commerce di calcolare KPI su milioni di transazioni, costruire feature per il machine learning, confrontare embedding di prodotti e alimentare motori di raccomandazione in tempo reale. Imparare NumPy significa imparare il linguaggio nativo e ad alte prestazioni con cui l’hardware tratta i dati, molto prima che qualunque framework moderno vi aggiunga il proprio strato di astrazione.


Pubblicità