Preparare dati, normalizzare valori, calcolare distanze, lavorare con le immagini
Negli ultimi tre moduli hai imparato NumPy in modo teorico. Ora vediamo a cosa serve davvero quando si costruisce un programma di Intelligenza Artificiale.
Le aziende che fanno AI (Netflix, Spotify, Google…) usano esattamente queste tecniche ogni giorno. Imparerai:
Come eliminare i valori mancanti e i dati "strani" che farebbero sbagliare l'AI
Come mettere tutto sulla stessa scala, così l'AI tratta tutti i dati in modo equo
Il concetto centrale di tantissimi algoritmi AI: "quanto sono simili due cose?"
Come una fotografia è in realtà solo una grande tabella di numeri
Prima di cucinare un piatto, uno chef deve preparare gli ingredienti: lavarli, tagliarli alla stessa dimensione, pesarli. Solo dopo li mette in pentola.
Per l'AI è uguale: prima prepari i dati con NumPy, poi li "cucini" con un modello. Senza la preparazione, il piatto viene male!
Nel mondo reale i dati sono sempre disordinati. Ci sono valori mancanti, errori di inserimento, dati fuori scala.
Se dai dati disordinati a un modello AI, le sue previsioni saranno sbagliate — un po' come se ti allenassi per una gara usando informazioni false. L'AI impara dai dati, quindi i dati devono essere corretti!
In NumPy un valore mancante si chiama NaN (Not a Number = "non è un numero"). Succede quando qualcuno non ha risposto a una domanda di un questionario, o quando un sensore non ha registrato nulla.
Perché è un problema? Prova a fare una media con un NaN dentro:
I valori in rosso sono NaN. Se calcoli la media di questo array, NumPy restituisce nan — un risultato inutilizzabile!
La soluzione: trovare dove si trovano i NaN e rimuoverli prima di usare i dati.
import numpy as np
# Dati con alcuni valori mancanti
dati = np.array([1.5, 2.3, np.nan, 4.1, np.nan, 5.7])
# np.isnan() restituisce True dove c'è un NaN, False altrove
print(np.isnan(dati))
# [False, False, True, False, True, False]
# Il simbolo ~ significa "al contrario" → True diventa False e viceversa
maschera_validi = ~np.isnan(dati)
print(maschera_validi)
# [True, True, False, True, False, True]
# Usiamo la maschera per tenere solo i valori validi
dati_puliti = dati[maschera_validi]
print("Dati puliti:", dati_puliti)
# Dati puliti: [1.5, 2.3, 4.1, 5.7]
# Ora la media funziona!
print("Media:", dati_puliti.mean()) # 3.4
Un outlier è un valore così fuori scala da essere probabilmente un errore — o comunque da rovinare le analisi. Esempio classico: stipendi in un'azienda dove tutti guadagnano tra 25.000 e 40.000 €, tranne il CEO che guadagna 500.000 €.
Se vuoi prevedere lo stipendio medio dei dipendenti, un valore di 500.000 € "trascina" la media verso l'alto in modo falso. Il modello AI crederà che gli stipendi siano enormi e farà previsioni sbagliate per tutti.
import numpy as np
# Stipendi di 10 dipendenti (in migliaia di euro)
stipendi = np.array([25, 28, 30, 32, 35, 28, 31, 29, 500, 27])
# ^^^
# questo è il CEO = outlier!
# Confrontiamo media e mediana per capire il problema
print(f"Media: {stipendi.mean():.1f}k") # 76.5k — distorta dall'outlier!
print(f"Mediana: {np.median(stipendi):.1f}k") # 29.5k — molto più realistica
# Come identificare gli outlier con la "regola dei 3 sigma"
# Un valore è sospetto se è più di 3 deviazioni standard dalla media
media = stipendi.mean()
deviazione = stipendi.std()
limite_alto = media + 3 * deviazione
limite_basso = media - 3 * deviazione
print(f"\nLimite superiore accettabile: {limite_alto:.1f}k")
print(f"Limiti inferiore accettabile: {limite_basso:.1f}k")
# Teniamo solo i valori DENTRO i limiti
dati_sani = stipendi[(stipendi >= limite_basso) & (stipendi <= limite_alto)]
print(f"\nDati sani: {dati_sani}")
print(f"Media corretta: {dati_sani.mean():.1f}k") # 28.9k — ha senso ora!
La mediana (che hai visto nel Modulo 3.2) è molto più robusta degli outlier rispetto alla media. Quando i dati hanno outlier, usa sempre la mediana per descriverli!
Immagina di voler descrivere una persona con tre numeri:
• Età: 17 anni
• Stipendio: 25.000 €
• Voto di maturità: 85/100
Se dai questi numeri direttamente a un modello AI, lui "pensa" che lo stipendio (25.000) sia molto più importante dell'età (17) solo perché è un numero più grande. Non è un errore di logica — è solo che i numeri hanno scale completamente diverse.
La normalizzazione risolve questo: trasforma tutto in numeri confrontabili, così l'AI tratta ogni informazione in modo equo.
Questa tecnica trasforma ogni valore in un numero tra 0 e 1, dove 0 è il valore più piccolo dell'array e 1 è il più grande.
La formula:
Sottraiamo il minimo per far iniziare da 0, poi dividiamo per l'intervallo totale
import numpy as np
altezze = np.array([150, 165, 180, 175, 160]) # in centimetri
# La formula Min-Max in NumPy:
altezze_norm = (altezze - altezze.min()) / (altezze.max() - altezze.min())
print("Altezze originali:", altezze)
print("Altezze normalizzate:", altezze_norm.round(2))
# [0. , 0.5 , 1. , 0.83, 0.33]
# ↑ ↑
# il più basso il più alto
Questa tecnica trasforma i dati in modo che abbiano media = 0 e deviazione standard = 1. I valori possono essere sia positivi che negativi. Un valore positivo significa "sopra la media", uno negativo "sotto la media".
La formula:
Sottraiamo la media per centrare in zero, poi dividiamo per "quanto variano i dati"
import numpy as np
voti = np.array([5, 7, 8, 4, 6, 9, 5.5, 7, 8.5, 6])
# La formula Z-score in NumPy:
voti_std = (voti - voti.mean()) / voti.std()
print(f"Media originale: {voti.mean():.2f}") # 6.60
print(f"Voti standardizzati: {voti_std.round(2)}")
print(f"\nVerifica — dopo la standardizzazione:")
print(f" Media: {voti_std.mean():.2f}") # ~0.00 ← quasi zero!
print(f" Dev. standard: {voti_std.std():.2f}") # ~1.00 ← quasi uno!
Il valore standardizzato ti dice di quante "deviazioni standard" sei lontano dalla media del gruppo.
• z = 0 → sei esattamente nella media
• z = +1.5 → sei 1.5 deviazioni sopra la media (abbastanza bravo)
• z = -2.0 → sei 2 deviazioni sotto la media (molto sotto)
Questo concetto lo usi anche nelle valutazioni scolastiche e nei test standardizzati come il QI!
Usala quando conosci i limiti naturali del dato: voti da 0 a 10, percentuali da 0 a 100, pixel da 0 a 255.
Usala quando non conosci i limiti o quando i dati hanno outlier. È la scelta più comune nell'AI. Se sei in dubbio, scegli questa.
Molti algoritmi AI funzionano su un'idea semplicissima: cose simili sono vicine nello spazio.
Netflix ti consiglia film che "gente simile a te" ha amato. Spotify ti suggerisce canzoni "vicine" al tuo gusto. Gmail manda le email nella cartella giusta perché le email spam si "assomigliano" tra loro.
In tutti questi casi, il programma deve calcolare "quanto sono distanti questi due punti?". E in NumPy, è facilissimo.
È la distanza in linea retta tra due punti — la stessa che misuri con un righello. In 2D è il teorema di Pitagora che forse conosci già:
d = √(3² + 4²) = √(9 + 16) = √25 = 5
import numpy as np
punto_A = np.array([2, 3])
punto_B = np.array([5, 7])
# Metodo 1: calcolo manuale (formula di Pitagora estesa)
differenza = punto_B - punto_A # [3, 4]
quadrati = differenza ** 2 # [9, 16]
somma = np.sum(quadrati) # 25
distanza = np.sqrt(somma) # 5.0
print(f"Distanza: {distanza}") # 5.0
# Metodo 2: formula compatta con np.linalg.norm()
# (fa la stessa cosa in una riga sola!)
distanza2 = np.linalg.norm(punto_B - punto_A)
print(f"Distanza: {distanza2}") # 5.0 — stesso risultato!
linalg sta per "linear algebra" (algebra lineare). norm è il termine tecnico per "lunghezza di un vettore" — che è esattamente la distanza dal punto di origine. Usalo sempre nelle tue soluzioni: è più breve e più leggibile.
Nella pratica si lavora sempre con molti punti. Ecco come calcolare le distanze tra tutti gli studenti di una classe, usando le loro ore di studio e le ore di sonno come coordinate:
import numpy as np
# Ogni riga = uno studente
# Colonne = [ore_di_studio, ore_di_sonno]
studenti = np.array([
[5, 8], # Studente 0
[7, 6], # Studente 1
[3, 9], # Studente 2
[6, 7], # Studente 3
])
n = len(studenti)
# Creiamo una tabella (matrice) 4x4 per tutte le distanze
distanze = np.zeros((n, n))
for i in range(n):
for j in range(n):
distanze[i, j] = np.linalg.norm(studenti[i] - studenti[j])
print("Tabella distanze tra studenti:")
print(distanze.round(2))
# Esempio: chi è più simile allo studente 0?
distanze_da_0 = distanze[0] # Riga 0 = distanze dallo studente 0
print(f"\nDistanze dallo studente 0: {distanze_da_0.round(2)}")
# Il valore minimo (escludendo 0.00 = sé stesso) è il più simile!
Una foto digitale non è "disegnata" — è una grandissima tabella di numeri. Ogni numero rappresenta la luminosità di un puntino (pixel) della foto.
• Una foto in bianco e nero: ogni cella ha un numero da 0 (nero totale) a 255 (bianco totale)
• Una foto a colori: ogni pixel ha tre numeri — uno per il rosso, uno per il verde, uno per il blu (RGB)
Quando un'AI "guarda" una foto, non vede immagini — vede solo una matrice NumPy piena di numeri!
Ecco un'immagine piccola (5×5 pixel) e la sua rappresentazione come matrice NumPy:
import numpy as np
# Ogni numero = luminosità di un pixel (0 = nero, 255 = bianco)
immagine = np.array([
[ 0, 64, 128, 192, 255], # Riga 0: da nero a bianco
[255, 192, 128, 64, 0], # Riga 1: da bianco a nero
[ 0, 0, 255, 0, 0], # Riga 2: punto bianco al centro
[128, 128, 128, 128, 128], # Riga 3: grigio uniforme
[255, 255, 255, 255, 255], # Riga 4: bianco puro
])
print(f"Dimensioni immagine: {immagine.shape}") # (5, 5)
print(f"Pixel in totale: {immagine.size}") # 25
print(f"Tipo dati: {immagine.dtype}") # int64
Passa il mouse sui pixel per vedere i loro valori numerici
import numpy as np
immagine = np.array([
[ 0, 64, 128, 192, 255],
[255, 192, 128, 64, 0],
[ 0, 0, 255, 0, 0],
[128, 128, 128, 128, 128],
[255, 255, 255, 255, 255],
])
# --- Operazione 1: scurire l'immagine ---
# Dividiamo ogni pixel per 2 → tutti i valori si dimezzano → più scuro
immagine_scura = immagine // 2 # // = divisione intera (senza decimali)
print("Pixel scuriti (prima riga):", immagine_scura[0])
# [ 0, 32, 64, 96, 127] ← valori dimezzati
# --- Operazione 2: ritagliare una parte dell'immagine ---
# Prendiamo solo le prime 3 righe e le prime 3 colonne
ritaglio = immagine[:3, :3]
print("\nRitaglio 3x3:")
print(ritaglio)
# --- Operazione 3: ruotare l'immagine di 90° ---
ruotata = np.rot90(immagine)
print("\nImmagine ruotata 90°:")
print(ruotata)
I filtri di Instagram sono solo operazioni matematiche su matrici NumPy:
• Filtro luminosità: aggiungi un numero fisso a tutti i pixel
• Filtro contrasto: moltiplica tutti i pixel per un coefficiente
• Bianco e nero: fai la media dei 3 canali RGB
• Ritaglio: usa lo slicing che hai già imparato nel Modulo 3.3!
Questo è esattamente come funzionano Netflix, Spotify e Amazon Prime.
Hai 5 utenti che hanno votato 4 film da 1 a 5 stelle:
import numpy as np
# Colonne: [Avengers, Titanic, Harry Potter, Star Wars]
utenti = np.array([
[5, 1, 4, 2], # Utente 0: ama azione, odia romantico
[1, 5, 2, 1], # Utente 1: ama i film romantici
[4, 2, 5, 3], # Utente 2: ama il fantasy
[2, 4, 3, 5], # Utente 3: ama la fantascienza
[3, 3, 3, 3], # Utente 4: vota tutto uguale
])
La tua missione in 4 passi:
import numpy as np
utenti = np.array([
[5, 1, 4, 2],
[1, 5, 2, 1],
[4, 2, 5, 3],
[2, 4, 3, 5],
[3, 3, 3, 3],
])
# Passo 1: normalizza con standardizzazione (asse 0 = per colonna)
utenti_norm = (utenti - utenti.mean(axis=0)) / utenti.std(axis=0)
# Passo 2: calcola le distanze dall'Utente 0
distanze = np.array([
np.linalg.norm(utenti_norm[0] - utenti_norm[i])
for i in range(len(utenti))
])
print("Distanze dall'Utente 0:", distanze.round(2))
# Passo 3: ordina per distanza crescente, salta l'indice 0 (sé stesso)
indici_simili = np.argsort(distanze)[1:3] # I 2 più vicini
print("Utenti più simili:", indici_simili)
# Passo 4: cosa amano questi utenti?
print("\nVoti degli utenti simili:")
print(utenti[indici_simili])
# Suggerisci il film che loro amano (>= 4) ma l'utente 0 vota poco (< 3)
film = ["Avengers", "Titanic", "Harry Potter", "Star Wars"]
for idx in indici_simili:
for f_idx, f_nome in enumerate(film):
if utenti[idx, f_idx] >= 4 and utenti[0, f_idx] < 3:
print(f"\n🎬 Suggerimento: '{f_nome}'")
Trasforma il codice sopra in una funzione suggerisci_film(utente_id) che funziona per qualsiasi utente, non solo lo 0. Provala con tutti e 5 gli utenti e vedi che suggerimenti produce!
Tre livelli di difficoltà — inizia dall'inizio e sali gradualmente. Ogni esercizio ha un suggerimento nascosto se sei bloccato, e la soluzione completa da confrontare con la tua.
Una app di running registra la velocità media (km/h) di un atleta in 8 allenamenti. In 2 sessioni il GPS ha avuto problemi e non ha registrato nulla → NaN.
import numpy as np
velocita = np.array([12.3, 11.8, np.nan, 13.1, 12.7, np.nan, 11.5, 14.0])
# DOMANDE:
# 1. Quante sessioni hanno dati mancanti?
# 2. Rimuovi i NaN e salva i dati validi in "velocita_pulita"
# 3. Calcola la velocità media dell'atleta (solo sui dati validi)
# 4. Quante sessioni aveva velocità sopra la media?
• Per contare i NaN: np.sum(np.isnan(velocita))
• Per filtrare: usa ~np.isnan() come maschera booleana
• Per contare sopra la media: usa un filtro con velocita_pulita > media
import numpy as np
velocita = np.array([12.3, 11.8, np.nan, 13.1, 12.7, np.nan, 11.5, 14.0])
# 1. Quante sessioni mancanti?
n_mancanti = np.sum(np.isnan(velocita))
print(f"Sessioni con dati mancanti: {n_mancanti}") # 2
# 2. Rimozione NaN
velocita_pulita = velocita[~np.isnan(velocita)]
print(f"Velocità valide: {velocita_pulita}")
# [12.3, 11.8, 13.1, 12.7, 11.5, 14.0]
# 3. Velocità media
media = velocita_pulita.mean()
print(f"Velocità media: {media:.2f} km/h") # 12.57 km/h
# 4. Sessioni sopra la media
sopra_media = velocita_pulita[velocita_pulita > media]
print(f"Sessioni sopra la media: {len(sopra_media)}") # 3
print(f"Velocità di quelle sessioni: {sopra_media}")
Spotify descrive ogni canzone con delle caratteristiche numeriche. Il problema è che sono su scale completamente diverse: prima di passarle a un algoritmo AI devi normalizzarle tutte tra 0 e 1.
import numpy as np
# Caratteristiche di 5 canzoni
# Colonne: [BPM (battiti/min), durata (secondi), loudness (dB), popolarita (0-100)]
canzoni = np.array([
[128, 210, -5, 87], # canzone 0
[ 95, 195, -8, 62], # canzone 1
[140, 240, -3, 95], # canzone 2
[ 75, 180, -12, 41], # canzone 3
[110, 225, -6, 78], # canzone 4
])
# DOMANDE:
# 1. Normalizza OGNI COLONNA con Min-Max (axis=0 lavora colonna per colonna)
# Formula: (valore - min_colonna) / (max_colonna - min_colonna)
# 2. Dopo la normalizzazione, qual è il valore massimo e minimo in ogni colonna?
# (dovrebbero essere tutti 1.0 e 0.0)
# 3. Quale canzone ha il BPM più alto in assoluto? (indice nella colonna 0)
• Quando scrivi canzoni.min(axis=0), NumPy calcola il minimo di ogni colonna separatamente → restituisce un array di 4 valori (uno per colonna)
• Poi canzoni - canzoni.min(axis=0) sottrae automaticamente il minimo giusto a ogni colonna (broadcasting!)
• Per trovare l'indice del massimo in una colonna: np.argmax(canzoni_norm[:, 0])
import numpy as np
canzoni = np.array([
[128, 210, -5, 87],
[ 95, 195, -8, 62],
[140, 240, -3, 95],
[ 75, 180, -12, 41],
[110, 225, -6, 78],
])
# 1. Normalizzazione Min-Max colonna per colonna
minimi = canzoni.min(axis=0) # [75, 180, -12, 41]
massimi = canzoni.max(axis=0) # [140, 240, -3, 95]
canzoni_norm = (canzoni - minimi) / (massimi - minimi)
print("Canzoni normalizzate:")
print(canzoni_norm.round(3))
# 2. Verifica: ogni colonna va da 0.0 a 1.0
print("\nMinimi per colonna:", canzoni_norm.min(axis=0)) # [0. 0. 0. 0.]
print("Massimi per colonna:", canzoni_norm.max(axis=0)) # [1. 1. 1. 1.]
# 3. Canzone con BPM più alto
idx_bpm_max = np.argmax(canzoni_norm[:, 0])
print(f"\nCanzone con BPM più alto: canzone {idx_bpm_max}") # canzone 2 (140 BPM)
Un ospedale registra la pressione sistolica (la "prima" delle due misure, es. 120 in "120/80") di 12 pazienti. Alcuni valori potrebbero essere errori di inserimento dati. Prima di usarli per un'analisi AI, devi pulire e standardizzare i dati.
import numpy as np
# Pressione sistolica in mmHg di 12 pazienti
pressione = np.array([118, 125, 132, 119, 450, 128, 121,
135, 127, 122, 12, 130])
# ^^^ ^^
# 450 = errore inserimento 12 = impossibile (troppo basso)
# DOMANDE:
# 1. Calcola media e mediana — perché sono molto diverse?
# 2. Identifica i valori "impossibili": una pressione normale è tra 80 e 200 mmHg.
# Filtra i dati tenendo solo i valori nel range normale.
# 3. Standardizza (Z-score) i dati puliti
# 4. Quale paziente ha la pressione più alta rispetto al gruppo?
# (cerca il Z-score massimo nei dati standardizzati)
• Per filtrare nel range usa due condizioni unite con &: pressione[(pressione >= 80) & (pressione <= 200)]
• La standardizzazione è: (dati - dati.mean()) / dati.std()
• np.argmax(array) restituisce l'indice del valore più grande
import numpy as np
pressione = np.array([118, 125, 132, 119, 450, 128, 121,
135, 127, 122, 12, 130])
# 1. Media vs mediana
print(f"Media: {pressione.mean():.1f} mmHg") # 139.1 — distorta dagli outlier
print(f"Mediana: {np.median(pressione):.1f} mmHg") # 126.5 — molto più realistica
print("→ Sono diverse perché 450 e 12 distorcono la media!\n")
# 2. Filtrare nel range fisiologico
pressione_pulita = pressione[(pressione >= 80) & (pressione <= 200)]
print(f"Dati rimossi: {len(pressione) - len(pressione_pulita)}") # 2
print(f"Pressioni valide: {pressione_pulita}")
# 3. Standardizzazione
media = pressione_pulita.mean()
std = pressione_pulita.std()
pressione_std = (pressione_pulita - media) / std
print(f"\nPressioni standardizzate: {pressione_std.round(2)}")
print(f"Verifica media: {pressione_std.mean():.4f}") # ~0.0000
print(f"Verifica std: {pressione_std.std():.4f}") # ~1.0000
# 4. Paziente con pressione più alta nel gruppo
idx_max = np.argmax(pressione_std)
print(f"\nPaziente {idx_max} ha la pressione più alta nel gruppo")
print(f"Z-score: {pressione_std[idx_max]:.2f} → {pressione_std[idx_max]:.1f} dev.std sopra la media")
Un sito e-commerce descrive ogni smartphone con 3 caratteristiche: prezzo (€), RAM (GB) e punteggio fotocamera (da 1 a 10). Vuoi costruire una funzione "prodotti simili" — la sezione che appare in fondo alle pagine prodotto.
import numpy as np
# Ogni riga = uno smartphone
# Colonne: [prezzo €, RAM GB, voto_fotocamera]
smartphone = np.array([
[799, 8, 8.5], # iPhone SE
[1299, 12, 9.2], # iPhone 15 Pro
[749, 8, 7.8], # Samsung A55
[1199, 12, 8.9], # Samsung S24
[699, 6, 7.2], # Pixel 7a
[1099, 12, 9.0], # Pixel 8 Pro
])
nomi = ["iPhone SE", "iPhone 15 Pro", "Samsung A55",
"Samsung S24", "Pixel 7a", "Pixel 8 Pro"]
# DOMANDE:
# 1. Prima di calcolare le distanze, normalizza con Min-Max (le scale sono molto diverse:
# il prezzo va fino a 1299, la RAM fino a 12, il voto fino a 10!)
# 2. Calcola la distanza tra TUTTI i prodotti (matrice 6x6)
# 3. Per ogni smartphone, stampa quale altro smartphone gli è più simile
# (escludi sé stesso cercando il secondo minimo, non il primo!)
• È fondamentale normalizzare prima di calcolare le distanze: senza normalizzare, il prezzo (fino a 1299) dominerebbe completamente RAM (max 12) e voto (max 10)
• np.argsort(riga)[1] → ordina gli indici per distanza crescente e prende il secondo (indice 1), cioè il più vicino escludendo sé stesso (indice 0)
import numpy as np
smartphone = np.array([
[799, 8, 8.5],
[1299, 12, 9.2],
[749, 8, 7.8],
[1199, 12, 8.9],
[699, 6, 7.2],
[1099, 12, 9.0],
])
nomi = ["iPhone SE", "iPhone 15 Pro", "Samsung A55",
"Samsung S24", "Pixel 7a", "Pixel 8 Pro"]
# 1. Normalizzazione Min-Max (essenziale prima delle distanze!)
minimi = smartphone.min(axis=0)
massimi = smartphone.max(axis=0)
smart_norm = (smartphone - minimi) / (massimi - minimi)
# 2. Matrice distanze 6x6
n = len(smartphone)
distanze = np.zeros((n, n))
for i in range(n):
for j in range(n):
distanze[i, j] = np.linalg.norm(smart_norm[i] - smart_norm[j])
print("Matrice distanze (arrotondata):")
print(distanze.round(2))
# 3. Per ogni prodotto, trova il più simile
print("\n--- Prodotti simili ---")
for i in range(n):
indici_ordinati = np.argsort(distanze[i])
# indici_ordinati[0] = sé stesso (dist 0), indici_ordinati[1] = più vicino
piu_simile = indici_ordinati[1]
dist = distanze[i, piu_simile]
print(f"{nomi[i]:15s} → più simile a: {nomi[piu_simile]:15s} (dist={dist:.2f})")
Stai costruendo un tool per analizzare profili Instagram e trovare creator simili tra loro. Hai dati grezzi con valori mancanti, outlier e scale diverse. Devi costruire una pipeline completa: pulizia → normalizzazione → calcolo somiglianza.
import numpy as np
# Dati di 8 creator (colonne: follower, post_per_settimana, engagement_rate %)
# Alcuni valori sono NaN (profili privati o non tracciabili)
dati_grezzi = np.array([
[15000, 4, 3.2],
[82000, 7, 5.1],
[ np.nan, 3, 2.8], # follower non tracciabili
[210000, 2, 4.7],
[9500000, 1, 0.3], # influencer con 9.5M follower = possibile outlier
[44000, 5, np.nan], # engagement non tracciabile
[67000, 6, 4.2],
[31000, 3, 3.8],
])
nomi = ["Alice", "Bob", "Carla", "Diego",
"Elena", "Fabio", "Giulia", "Hana"]
# MISSIONE — costruisci una pipeline in 4 fasi:
#
# FASE 1 — Identifica le righe con almeno un NaN e rimuovile.
# Usa np.any(np.isnan(dati_grezzi), axis=1) per trovare le righe con NaN
# (axis=1 = controlla lungo le colonne, per ogni riga)
#
# FASE 2 — Identifica gli outlier nella colonna dei follower.
# Un valore è outlier se supera media + 2 * deviazione_standard
# Rimuovi anche quelle righe.
#
# FASE 3 — Normalizza i dati puliti con Min-Max.
#
# FASE 4 — Calcola la matrice delle distanze e stampa,
# per ogni creator rimasto, chi gli è più simile.
Fase 1: np.any(np.isnan(dati), axis=1) restituisce True per le righe che contengono almeno un NaN. Con ~ puoi invertire e tenere solo le righe senza NaN.
Fase 2: dopo aver rimosso i NaN, lavora sulla colonna 0 (follower): calcola media e std, poi filtra le righe dove la colonna 0 è entro i limiti.
Ricorda: quando filtri le righe dei dati, filtra anche la lista nomi con lo stesso array booleano, così rimangono allineati!
import numpy as np
dati_grezzi = np.array([
[15000, 4, 3.2],
[82000, 7, 5.1],
[ np.nan, 3, 2.8],
[210000, 2, 4.7],
[9500000, 1, 0.3],
[44000, 5, np.nan],
[67000, 6, 4.2],
[31000, 3, 3.8],
])
nomi = np.array(["Alice", "Bob", "Carla", "Diego",
"Elena", "Fabio", "Giulia", "Hana"])
# FASE 1 — Rimuovi righe con NaN
# np.any(..., axis=1) → True se almeno un valore nella riga è NaN
mask_nan = ~np.any(np.isnan(dati_grezzi), axis=1)
dati = dati_grezzi[mask_nan]
nomi = nomi[mask_nan]
print(f"Fase 1 — Rimossi {np.sum(~mask_nan)} profili con dati mancanti")
print(f"Rimasti: {list(nomi)}\n")
# FASE 2 — Rimuovi outlier nei follower (colonna 0)
follower = dati[:, 0]
media_f = follower.mean()
std_f = follower.std()
limite = media_f + 2 * std_f
print(f"Fase 2 — Limite follower: {limite:,.0f}")
mask_out = follower <= limite
dati = dati[mask_out]
nomi = nomi[mask_out]
print(f"Rimossi outlier: {np.sum(~mask_out)}")
print(f"Rimasti: {list(nomi)}\n")
# FASE 3 — Normalizzazione Min-Max
minimi = dati.min(axis=0)
massimi = dati.max(axis=0)
dati_norm = (dati - minimi) / (massimi - minimi)
print("Fase 3 — Dati normalizzati:")
print(dati_norm.round(3))
# FASE 4 — Matrice distanze + creator simili
n = len(dati_norm)
distanze = np.zeros((n, n))
for i in range(n):
for j in range(n):
distanze[i, j] = np.linalg.norm(dati_norm[i] - dati_norm[j])
print("\nFase 4 — Creator più simili:")
for i in range(n):
simile_idx = np.argsort(distanze[i])[1]
print(f" {nomi[i]:8s} → simile a {nomi[simile_idx]} (dist={distanze[i,simile_idx]:.2f})")
Lavori come programmatore in un piccolo studio di videogiochi. Hai uno "sprite" (immagine del personaggio) rappresentato come una matrice 6×6 di pixel in scala di grigi. Devi applicare tre effetti grafici diversi per creare varianti del personaggio.
import numpy as np
# Sprite del personaggio (6x6 pixel, valori 0-255)
sprite = np.array([
[ 0, 0, 200, 200, 0, 0],
[ 0, 200, 255, 255, 200, 0],
[200, 255, 180, 180, 255, 200],
[200, 255, 180, 180, 255, 200],
[ 0, 200, 255, 255, 200, 0],
[ 0, 0, 200, 200, 0, 0],
])
# EFFETTI DA CREARE:
#
# EFFETTO 1 — "Versione notturna" (sprite scuro)
# Riduci la luminosità al 35% moltiplicando per 0.35
# Usa .astype(int) per riportare i valori a interi
#
# EFFETTO 2 — "Versione fantasma" (sprite semitrasparente)
# Clip i valori tra 80 e 200 con np.clip(sprite, 80, 200)
# (np.clip taglia i valori fuori dal range: sotto 80 → 80, sopra 200 → 200)
#
# EFFETTO 3 — "Flip orizzontale" (sprite specchiato)
# Usa np.fliplr() che capovolge la matrice da sinistra a destra
# (fliplr = flip left-right)
#
# BONUS — "Negativo fotografico"
# In fotografia il negativo inverte i colori: bianco diventa nero e viceversa
# Formula: negativo = 255 - sprite
• La moltiplicazione sprite * 0.35 produce float (numeri con virgola). Aggiungi .astype(int) per riconvertire in interi
• np.clip(array, min, max): qualsiasi valore sotto min viene portato a min, qualsiasi sopra max viene portato a max
• Il negativo è semplicissimo: 255 - sprite. I pixel bianchi (255) diventano neri (0) e viceversa!
import numpy as np
sprite = np.array([
[ 0, 0, 200, 200, 0, 0],
[ 0, 200, 255, 255, 200, 0],
[200, 255, 180, 180, 255, 200],
[200, 255, 180, 180, 255, 200],
[ 0, 200, 255, 255, 200, 0],
[ 0, 0, 200, 200, 0, 0],
])
print("Sprite originale:")
print(sprite)
# EFFETTO 1 — Versione notturna
notturno = (sprite * 0.35).astype(int)
print("\nEffetto 1 — Notturno (35% luminosità):")
print(notturno)
# EFFETTO 2 — Versione fantasma
fantasma = np.clip(sprite, 80, 200)
print("\nEffetto 2 — Fantasma (clip 80-200):")
print(fantasma)
# EFFETTO 3 — Flip orizzontale
specchio = np.fliplr(sprite)
print("\nEffetto 3 — Specchio orizzontale:")
print(specchio)
# BONUS — Negativo fotografico
negativo = 255 - sprite
print("\nBonus — Negativo fotografico:")
print(negativo)
# Verifica del negativo:
# Il pixel in alto a sinistra era 0 (nero) → diventa 255 (bianco) ✓
print(f"\nVerifica: 0 → {255-0}, 255 → {255-255}, 200 → {255-200}")