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.
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.
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.
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:
- 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. - 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.
- 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. - Contiguità e Tipo di Dato (Float64)
Anche se non si vede dalla semplice stampa a schermo, l’istruzione inizialenp.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.
NumPy, Forecasting e Matematica dei Transformer
👉NumPy: il concetto di ndarray e dtype
👉NumPy: creare, indicizzare e manipolare array in Python
👉NumPy nella pratica: simulazioni, analisi dati e integrazione numerica
👉Modelli Transformer per il forecasting delle vendite con NumPy





