Dall’anomalia al cambiamento strutturale: un laboratorio di Data Science applicata al business
La dashboard è verde. Il KPI è nella norma. Nessun alert è scattato. Eppure qualcosa è già cambiato.
È uno dei problemi più difficili da affrontare quando i dati diventano parte delle decisioni quotidiane: riconoscere un cambiamento quando è ancora troppo piccolo per attirare l’attenzione.
Una nuova esigenza dei clienti può comparire inizialmente in poche ricerche interne. Una metrica operativa può spostarsi lentamente senza superare nessuna soglia. Un comportamento che oggi sembra irrilevante può diventare tra qualche mese una delle variabili più importanti per il business.
Il problema è che i sistemi di analytics sono progettati, giustamente, per rendere leggibile ciò che sta già accadendo. Aggregano dati, confrontano periodi, calcolano KPI e generano alert quando una misura supera una soglia.
Ma proprio queste semplificazioni possono rendere poco visibili i fenomeni che stanno appena iniziando.
Nel precedente articolo, “Weak Signals Detection: come identificare segnali deboli ed emergenti nei dati”, abbiamo costruito il quadro concettuale distinguendo rumore, anomalia, weak signal e trend.
Qui facciamo un passo ulteriore: portiamo quel ragionamento dentro Python.
Non cercheremo un algoritmo capace di proclamare automaticamente:
“Questo è un weak signal.”
Una conclusione del genere sarebbe troppo forte. Costruiremo invece una sequenza di analisi che permetta di capire quali fenomeni meritano un’indagine più approfondita.
Partiremo da un outlier in un KPI commerciale e confronteremo Z-score, IQR e MAD. Passeremo poi a una query rara che comincia a comparire con maggiore frequenza, introducendo persistenza e crescita. Infine analizzeremo un caso ancora più sottile: un sistema che cambia regime senza produrre alcun valore abbastanza estremo da generare un alert.
La domanda, quindi, non sarà soltanto:
“C’è qualcosa di anomalo?”
Sarà soprattutto:
“C’è qualcosa che sta cambiando e che sarebbe utile capire prima che diventi evidente?”
È questa la differenza tra costruire un sistema che produce alert e costruire un sistema che aiuta un’organizzazione a fare domande migliori sui propri dati.
La progressione: anomalia, persistenza, cambiamento
Nei tre esercizi analizzeremo tre situazioni differenti:
- un valore anomalo in un KPI commerciale;
- un fenomeno persistente e crescente che inizialmente ha un volume molto basso;
- un cambiamento di regime che non produce necessariamente outlier.
La progressione è quindi:
[math]\displaystyle \begin{aligned}
& \boxed{\text{Anomalia} \rightarrow \text{Persistenza} \rightarrow \text{Cambiamento strutturale}}
\end{aligned}[/math]
Questa sequenza è importante perché evita uno degli errori più comuni nella Weak Signals Detection:
confondere ciò che è statisticamente insolito con ciò che è realmente interessante per il business.
Un valore estremo può essere rumore.
Una sequenza persistente può essere stagionalità.
Un cambiamento statisticamente significativo può avere un’importanza operativa trascurabile.
La detection è soltanto l’inizio.
Esercizio 1 — Dal rumore all’anomalia: IQR, Z-score e MAD su un KPI commerciale
Scenario
Sei il data scientist di un marketplace.
La dashboard giornaliera mostra l’Average Order Value (AOV), cioè il valore medio degli ordini.
Un lunedì compare un valore di:
[math]187\text{ €}[/math]
mentre il livello abituale è intorno agli 80 €.
Il team commerciale interpreta immediatamente il dato come un possibile risultato eccezionale.
Tu, invece, hai un dubbio.
Il problema non è stabilire se 187 € sia un valore grande.
Lo è.
Il problema è capire se sia statisticamente insolito rispetto alla distribuzione del KPI e, soprattutto, se abbia una spiegazione business.
Un singolo valore estremo può essere prodotto da:
- una promozione;
- pochi ordini di importo elevato;
- un cambiamento nel mix dei clienti;
- un errore di tracking;
- una modifica reale nel comportamento d’acquisto.
Prima di parlare di weak signal dobbiamo quindi capire se abbiamo davanti un’anomalia.
La media non basta
L’AOV è una metrica particolarmente delicata perché può avere una distribuzione asimmetrica.
Pochi ordini di importo molto elevato possono spostare sensibilmente la media.
La mediana è meno sensibile a questo tipo di osservazioni.
Il confronto tra media e mediana può quindi essere utile come indicatore descrittivo dell’asimmetria, ma non deve essere trasformato in una soglia universale.
Nel nostro esempio utilizzeremo tre detector:
- Z-score, basato su media e deviazione standard;
- IQR, basato sui quartili;
- MAD, basato sulla deviazione assoluta mediana.
La domanda non è:
“Quale algoritmo è il migliore?”
La domanda è:
“Quanto cambia la nostra interpretazione del dato quando cambiamo il modo in cui definiamo ‘anomalo’?”
Z-score
Lo Z-score misura la distanza di un’osservazione dalla media in unità di deviazione standard:
[math]\displaystyle z=\frac{x-\mu}{\sigma}[/math]
Una regola convenzionale considera potenzialmente anomali i valori per cui:
[math]\displaystyle |z|>3[/math]
Il limite è noto: media e deviazione standard sono entrambe sensibili ai valori estremi.
Inoltre, su una distribuzione fortemente asimmetrica, la distanza dalla media espressa in deviazioni standard può avere un’interpretazione meno intuitiva.
Lo Z-score rimane quindi utile, ma va interpretato in funzione della distribuzione e del processo che ha generato i dati.
IQR
L’Interquartile Range è definito come:
[math]IQR=Q_3-Q_1[/math]
Le osservazioni oltre:
[math]Q_1-1.5IQR[/math]
oppure:
[math]Q_3+1.5IQR[/math]
sono considerate potenziali outlier.
Il vantaggio è che la misura utilizza quartili ed è quindi meno influenzata dai valori estremi rispetto a media e deviazione standard.
MAD
Il Median Absolute Deviation è:
[math]MAD=\operatorname{med}(|x-\operatorname{med}(x)|)[/math]
Una versione normalizzata può essere utilizzata come:
[math]\displaystyle \frac{|x-\operatorname{med}(x)|}{1.4826\,MAD}>3.5[/math]
Il fattore 1.4826 rende la scala confrontabile con la deviazione standard nel caso gaussiano.
Il MAD è particolarmente utile quando vogliamo costruire un detector robusto rispetto agli outlier.
Ma attenzione:
robusto non significa automaticamente migliore.
Significa che il metodo ha proprietà diverse e può comportarsi diversamente rispetto allo Z-score in presenza di distribuzioni non gaussiane o di valori estremi.
Python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
rng = np.random.default_rng(42)
# -----------------------------------------
# Serie AOV: distribuzione asimmetrica
# -----------------------------------------
N = 365
aov = rng.lognormal(
mean=np.log(80),
sigma=0.35,
size=N
)
# Inseriamo un valore sospetto
aov[200] = 187.0
df = pd.DataFrame({
"day": np.arange(N),
"aov": aov
})
# -----------------------------------------
# Detector
# -----------------------------------------
def zscore_outliers(x, threshold=3.0):
z = (x - x.mean()) / x.std(ddof=1)
return np.abs(z) > threshold
def iqr_outliers(x, k=1.5):
q1, q3 = np.percentile(x, [25, 75])
iqr = q3 - q1
lower = q1 - k * iqr
upper = q3 + k * iqr
return (x < lower) | (x > upper)
def mad_outliers(x, threshold=3.5):
median = np.median(x)
mad = np.median(np.abs(x - median))
score = np.abs(x - median) / (1.4826 * mad)
return score > threshold
df["z"] = zscore_outliers(df["aov"].values)
df["iqr"] = iqr_outliers(df["aov"].values)
df["mad"] = mad_outliers(df["aov"].values)
# -----------------------------------------
# Risultati
# -----------------------------------------
print(
f"Media = {df.aov.mean():.1f} | "
f"Mediana = {df.aov.median():.1f} | "
f"Rapporto = {df.aov.mean()/df.aov.median():.2f}"
)
print(
f"Outlier → "
f"Z-score: {df.z.sum()}, "
f"IQR: {df.iqr.sum()}, "
f"MAD: {df.mad.sum()}"
)
print(
df[df.z | df.iqr | df.mad][
["day", "aov"]
]
)
# -----------------------------------------
# Visualizzazione
# -----------------------------------------
fig, ax = plt.subplots(
2, 1,
figsize=(12, 7),
sharex=True
)
ax[0].plot(
df.day,
df.aov,
lw=0.8,
label="AOV"
)
ax[0].axhline(
df.aov.mean(),
ls="--",
label="Media"
)
ax[0].axhline(
df.aov.median(),
ls="--",
label="Mediana"
)
ax[0].scatter(
df.day[df.z],
df.aov[df.z],
s=40,
label="Z-score"
)
ax[0].scatter(
df.day[df.iqr],
df.aov[df.iqr],
s=60,
marker="x",
label="IQR"
)
ax[0].scatter(
df.day[df.mad],
df.aov[df.mad],
s=100,
facecolors="none",
label="MAD"
)
ax[0].legend()
ax[0].grid(alpha=0.3)
ax[0].set_ylabel("AOV (€)")
ax[0].set_title("Anomalie su AOV — 365 giorni")
ax[1].hist(
df.aov,
bins=40,
alpha=0.7
)
ax[1].axvline(
df.aov.mean(),
ls="--",
label="Media"
)
ax[1].axvline(
df.aov.median(),
ls="--",
label="Mediana"
)
ax[1].set_xlabel("AOV (€)")
ax[1].set_ylabel("Frequenza")
ax[1].set_title(
"Distribuzione dell'AOV"
)
ax[1].legend()
ax[1].grid(alpha=0.3)
plt.tight_layout()
plt.show()
Output principale
Con il seed utilizzato nell’esempio otteniamo:
Media = 84.5
Mediana = 79.5
Rapporto = 1.06
Outlier →
Z-score: 4
IQR: 12
MAD: 7

Il valore di 187 € viene quindi identificato, ma i tre metodi producono una fotografia diversa della distribuzione.
Questo è il punto didatticamente più importante.
L’IQR segnala un insieme più ampio di osservazioni, il MAD un insieme più ristretto, mentre lo Z-score è influenzato dalle proprietà della distribuzione.
Il detector scelto modifica quindi direttamente il numero di eventi che entrano nella coda di analisi.
E qui la questione smette di essere soltanto statistica.
Se un sistema produce 500 alert al giorno, qualcuno dovrà analizzarli.
Il problema diventa quindi anche operativo.
Dal punto statistico alla decisione
Un errore frequente è concludere:
“Il MAD ha trovato sette outlier, quindi quelli sono i sette segnali.”
No.
Sono sette osservazioni candidate a un’indagine secondo una determinata regola.
Per capire se una di queste osservazioni è interessante dobbiamo chiederci:
- era associata a una promozione?
- proveniva da un particolare segmento?
- riguardava un singolo prodotto?
- è comparsa anche nei giorni successivi?
- si osserva anche negli altri canali?
- è dovuta a pochi ordini estremamente grandi?
- esiste un problema di tracking?
La detection individua il fenomeno.
L’analisi business prova a spiegarlo.
Un problema spesso sottovalutato: la granularità
Nel nostro caso stiamo analizzando l’AOV giornaliero.
Ma l’AOV è già una metrica aggregata.
Un valore di 187 € potrebbe essere prodotto da:
- molti ordini leggermente più elevati;
- pochi ordini estremamente elevati;
- un cambiamento nella composizione dei clienti;
- un cambiamento nel mix dei prodotti.
Per questo un’analisi reale dovrebbe poter scendere di livello:
[math]\displaystyle \begin{aligned}
& \text{AOV giornaliero} \rightarrow \text{ordini} \\
&{} \rightarrow \text{clienti} \rightarrow \text{prodotti}
\end{aligned}[/math]
L’anomalia osservata a livello aggregato potrebbe infatti scomparire o assumere un significato completamente diverso a livello disaggregato.
Trend e stagionalità
I detector di outlier non dovrebbero essere applicati meccanicamente a una serie temporale non trattata.
Se l’AOV presenta:
- trend crescente;
- stagionalità settimanale;
- stagionalità mensile;
- effetti promozionali ricorrenti;
un valore che appare anomalo rispetto alla media globale potrebbe essere perfettamente normale nel suo contesto temporale.
In produzione può quindi essere necessario costruire prima una baseline che tenga conto della componente sistematica della serie, per esempio attraverso:
- decomposizione STL;
- differenziazione;
- modelli specifici per serie temporali;
- confronti con periodi equivalenti;
- baseline condizionate al giorno della settimana.
Il principio è semplice:
un valore può essere anomalo rispetto alla media globale ma normale rispetto al suo contesto.
Perché questo esercizio è interessante nel lavoro reale
L’esercizio sull’AOV mostra che la definizione di anomalia non è indipendente dalla struttura dei dati.
In un ambiente commerciale, un valore estremo può essere reale: una promozione particolarmente efficace, pochi ordini enterprise o un prodotto ad alto prezzo.
Oppure può essere un errore di tracking.
La scelta del detector determina quindi quanti eventi entrano nel processo di investigazione.
Ma c’è un secondo aspetto importante: prima di correggere o spiegare un’anomalia bisogna capire a quale livello del processo si è prodotta.
Un AOV giornaliero anomalo potrebbe essere soltanto il riflesso di un cambiamento nella composizione degli ordini.
Per questo l’esercizio non insegna soltanto a calcolare tre statistiche robuste. Insegna a evitare una scorciatoia molto comune:
detector → etichetta → conclusione.
Tra detector e conclusione deve esserci un’indagine.
Attenzione: un outlier non è ancora un weak signal
Un outlier è un’osservazione che merita attenzione.
Non è automaticamente un weak signal.
Potrebbe essere:
- rumore;
- errore di misura;
- errore di tracking;
- evento straordinario;
- effetto stagionale;
- fenomeno già noto.
Il passaggio successivo consiste quindi nel verificare se il comportamento si ripresenta o evolve nel tempo.
Ed è esattamente ciò che faremo nel secondo esercizio.
Mini-quiz
- Perché lo Z-score può produrre risultati problematici su distribuzioni asimmetriche?
- Perché IQR e MAD sono meno sensibili agli outlier rispetto a media e deviazione standard?
- Che cosa può succedere se applichiamo direttamente un detector a una serie con forte stagionalità?
- Perché un outlier non può essere considerato automaticamente un weak signal?
- Perché può essere utile analizzare gli ordini alla base di un AOV anomalo?
Esercizio 2 — Dalla singola anomalia al segnale persistente
Scenario
Il team CRM di una piattaforma SaaS nota qualcosa di insolito.
Nel motore di ricerca interno iniziano a comparire alcune query che non erano mai state osservate con frequenza significativa.
Una di queste è:
“fatturazione a consumo AI”
La query non entra nella top-100 perché il volume è ancora molto basso.
Dal punto di vista della dashboard tradizionale, quindi, il fenomeno è praticamente invisibile.
Tu però osservi i dati a livello giornaliero e scopri che la query compare sempre più frequentemente.
La domanda diventa interessante:
È ancora rumore oppure sta emergendo un nuovo bisogno del mercato?
Qui l’outlier detection non basta più.
Non stiamo cercando necessariamente un valore estremo.
Stiamo cercando una dinamica.
Persistenza
Definiamo il volume giornaliero della query [math]q[/math] come:
[math]v_t(q)[/math]
e definiamo la persistenza:
[math]P(q,W)=
\#\{t\in W:v_t(q)>0\}[/math]
Il tasso di persistenza è:
[math]\displaystyle \rho(q,W)=
\frac{P(q,W)}{|W|}[/math]
Un valore elevato indica che la query non è comparsa una sola volta, ma continua a riapparire.
Nell’esercizio utilizzeremo soglie euristiche:
[math]\rho\geq0.8[/math]
può essere interpretato come comportamento altamente persistente;
[math]0.4\leq\rho<0.8[/math]
come pattern intermittente;
[math]\rho<0.4[/math]
come comportamento sporadico.
Queste soglie non sono valori statistici universali.
In un sistema reale dovrebbero essere calibrate sulla distribuzione storica delle query e sul costo dei falsi positivi.
Persistenza non significa crescita
Una query può essere presente ogni giorno senza crescere.
Per questo aggiungiamo una seconda dimensione:
la dinamica di crescita.
Una trasformazione logaritmica permette di modellare in modo descrittivo la crescita relativa:
[math]\log(1+v_t)
\approx
\alpha+\beta t[/math]
Da questa relazione possiamo ottenere un tasso di crescita settimanale approssimato:
[math]g_7=e^{7\beta}-1[/math]
Il termine [math]+1[/math] permette di gestire i giorni con volume zero.
Importante:
questa regressione non è un modello causale e non dimostra che la query continuerà a crescere.
È uno strumento euristico di prioritizzazione.
Una simulazione più informativa
Una simulazione binaria:
0 oppure 1
può essere utile per rappresentare una semplice presenza o assenza, ma non permette di studiare bene la crescita del volume.
Qui simuliamo invece il numero di ricerche giornaliere utilizzando una distribuzione di Poisson con intensità crescente.
In questo modo una dinamica come:
1 → 2 → 3 → 5 → 8 → 12
può essere distinta da una query che rimane stabilmente intorno a uno o due eventi giornalieri.
Python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import linregress
rng = np.random.default_rng(7)
T = 42 # 6 settimane
# -----------------------------------------
# Query rare: rumore di fondo
# -----------------------------------------
noise_queries = {}
for i in range(60):
lam = rng.uniform(0.03, 0.25)
noise_queries[f"noise_{i}"] = (
rng.poisson(lam, T)
)
# -----------------------------------------
# Query emergente
# -----------------------------------------
signal = np.zeros(T, dtype=int)
for t in range(T):
# Crescita esponenziale lenta
lam = 0.8 * np.exp(0.055 * t)
signal[t] = rng.poisson(lam)
signal_name = "fatturazione a consumo AI"
# -----------------------------------------
# DataFrame
# -----------------------------------------
df = pd.DataFrame({
"day": np.arange(T),
signal_name: signal,
**noise_queries
})
# -----------------------------------------
# Metriche
# -----------------------------------------
def persistence(v):
return (v > 0).mean()
def growth_rate(v, t):
# log(1 + volume)
y = np.log1p(v)
slope, intercept, r_value, p_value, std_err = (
linregress(t, y)
)
weekly_growth = np.exp(7 * slope) - 1
return weekly_growth, r_value**2, p_value
# -----------------------------------------
# Report
# -----------------------------------------
rows = []
for q in df.columns[1:]:
v = df[q].values
rho = persistence(v)
growth, r2, pval = growth_rate(
v,
df.day.values
)
rows.append({
"query": q,
"persistence": rho,
"weekly_growth": growth,
"r2": r2,
"pval": pval,
"total_events": v.sum()
})
report = pd.DataFrame(rows)
# -----------------------------------------
# Score euristico
# -----------------------------------------
median_total = report["total_events"].median()
report["score"] = (
report["persistence"]
* report["weekly_growth"].clip(lower=0)
* (
report["total_events"]
/ median_total
)
)
# -----------------------------------------
# Top query
# -----------------------------------------
top = (
report
.sort_values(
"score",
ascending=False
)
.head(5)
)
print(top.to_string(index=False))
# -----------------------------------------
# Visualizzazione
# -----------------------------------------
fig, ax = plt.subplots(
1, 2,
figsize=(14, 5)
)
# Query emergente
ax[0].plot(
df.day,
df[signal_name],
marker="o",
label=signal_name
)
ax[0].set_title(
f"Query emergente: {signal_name}"
)
ax[0].set_xlabel("Giorno")
ax[0].set_ylabel("Numero di query")
ax[0].grid(alpha=0.3)
ax[0].legend()
# Persistenza vs crescita
sc = ax[1].scatter(
report.persistence,
report.weekly_growth,
s=np.maximum(
report.total_events * 3,
10
),
alpha=0.6
)
ax[1].axhline(
0,
ls="--",
lw=0.8
)
ax[1].axvline(
0.8,
ls="--",
alpha=0.5,
label="ρ = 0.8"
)
ax[1].set_xlabel(
"Persistenza (ρ)"
)
ax[1].set_ylabel(
"Crescita settimanale"
)
ax[1].set_title(
"Persistenza × crescita"
)
ax[1].legend()
ax[1].grid(alpha=0.3)
plt.tight_layout()
plt.show()
Un possibile risultato
Con il seed utilizzato, la query emergente presenta circa:
Persistenza ≈ 0.93
Crescita settimanale ≈ 25%
Eventi complessivi = 142

mentre le query casuali rimangono generalmente molto più basse per persistenza e volume.
Il risultato è quello che volevamo ottenere:
non abbiamo cercato semplicemente la query con più ricerche, ma quella che presenta una combinazione di:
- presenza ripetuta;
- dinamica crescente;
- massa sufficiente per essere interessante;
- comportamento differente rispetto alle query rare di riferimento.
Lo score non è una probabilità
Possiamo definire uno score operativo:
[math]\displaystyle \begin{aligned}
Score(q) &= \rho(q) \cdot \max(0,g_7(q)) \\
&{} \quad \cdot \frac{v_T(q)}{\operatorname{med}(v_{\text{rare}})}
\end{aligned}[/math]
La funzione ha un obiettivo pratico:
ordinare i fenomeni da investigare.
Non significa che:
[math]Score=5[/math]
abbia una probabilità del 5%, del 50% o del 500% che la query diventi un trend.
Lo score non è una misura probabilistica.
È un ranking euristico che combina:
[math]\displaystyle \text{persistenza}
\times
\text{dinamica}
\times
\text{rilevanza relativa}[/math]
Questa distinzione è fondamentale quando si portano modelli quantitativi in azienda.
Uno score decisionale serve a stabilire:
dove guardare prima.
Non serve a trasformare un’ipotesi in una certezza.
La baseline conta più della soglia
Il vero problema di una query rara non è soltanto stabilire se il suo volume stia aumentando.
Dobbiamo capire se l’aumento sia diverso da quello che normalmente osserviamo in fenomeni simili.
Una baseline può essere costruita utilizzando:
- query storicamente rare;
- periodi equivalenti;
- distribuzioni stagionali;
- segmenti di utenti;
- categorie di prodotto;
- traffico complessivo;
- eventi commerciali.
Questo permette di passare da:
“la query è cresciuta”
a una domanda più utile:
“la query è cresciuta più di quanto ci aspetteremmo dato il comportamento normale del sistema?”
La seconda domanda è molto più vicina alla detection di un segnale emergente.
Il problema della stagionalità
Immaginiamo che la query sia:
“regalo Natale”
Durante dicembre la persistenza e il tasso di crescita potrebbero diventare molto elevati.
Ma non abbiamo scoperto necessariamente un weak signal.
Potremmo aver semplicemente osservato una dinamica stagionale perfettamente prevedibile.
Per questo una pipeline reale dovrebbe confrontare il fenomeno anche con:
- stesso periodo dell’anno precedente;
- calendario promozionale;
- festività;
- campagne pubblicitarie;
- variazioni del traffico;
- modifiche al prodotto.
Un segnale diventa più interessante quando eccede ciò che la nostra baseline già conosce.
Dal dato alla decisione
Supponiamo che la query “fatturazione a consumo AI” continui a crescere.
Che cosa dovrebbe fare il business?
Non necessariamente lanciare subito un nuovo prodotto.
Potrebbe invece:
- analizzare chi effettua la ricerca;
- verificare se appartiene a nuovi segmenti;
- analizzare le query correlate;
- osservare eventuali richieste al customer care;
- controllare la conversione degli utenti interessati;
- intervistare un campione di clienti;
- verificare la presenza del fenomeno sui competitor.
Il weak signal diventa quindi una ipotesi di mercato da sottoporre a triangolazione.
La Data Science ha portato il fenomeno all’attenzione.
Non ha ancora spiegato perché esiste.
Perché questo esercizio è interessante nel lavoro reale
La query emergente introduce una dimensione che un semplice detector di outlier non riesce a catturare: il tempo.
Una query con due ricerche oggi non è necessariamente interessante.
Ma se passa da 1 a 2, poi a 3, 5, 8 e 12 ricerche, continua a comparire e coinvolge utenti diversi, la sequenza diventa informativa.
Il volume assoluto può rimanere piccolo mentre il cambiamento relativo è già rilevante.
Questo tipo di analisi può essere utilizzato per monitorare:
- ricerca interna;
- ticket di assistenza;
- richieste commerciali;
- query al motore di ricerca;
- nuove categorie di prodotto;
- combinazioni insolite di funzionalità;
- comportamenti emergenti degli utenti.
L’esercizio mostra quindi perché persistenza e crescita devono essere analizzate insieme al volume.
Una crescita enorme da una base di due eventi può essere molto rumorosa.
Una crescita più modesta da una base di centinaia di eventi può avere un significato operativo molto maggiore.
Mini-quiz
- Perché una query con alta persistenza può non essere un weak signal?
- Perché utilizziamo [math]\log(1+v)[/math] invece del volume grezzo?
- Perché lo score composito non può essere interpretato come una probabilità?
- Come cambieresti l’analisi per una query fortemente stagionale?
- Una query che passa da 1 a 8 ricerche settimanali è automaticamente più interessante di una che passa da 100 a 150? Perché?
- Perché una baseline storica è più informativa di una soglia arbitraria?
Esercizio 3 — Change Point Detection: quando il sistema cambia regime senza produrre outlier
Scenario
Una piattaforma SaaS monitora il tempo medio di risposta delle proprie API.
Per diversi mesi il sistema oscilla intorno a:
[math]180\text{ ms}[/math]
Poi qualcosa cambia.
Il tempo medio inizia a oscillare intorno a:
[math]210\text{ ms}[/math]
Non ci sono però valori estremi.
La soglia di alert è:
[math]350\text{ ms}[/math]
Nessuna osservazione supera quindi il limite.
La dashboard continua a essere verde.
Eppure il sistema è cambiato.
Questo è un caso particolarmente importante per la Weak Signals Detection perché dimostra che:
un weak signal non deve necessariamente essere un outlier.
Può essere un cambiamento nella distribuzione.
Anomaly Detection e Change Point Detection non sono la stessa cosa
L’Anomaly Detection cerca generalmente osservazioni che si discostano dal comportamento atteso:
[math]\displaystyle \exists t:
x_t\not\approx\mu[/math]
La Change Point Detection cerca invece un punto:
[math]\tau[/math]
tale che il processo prima e dopo [math]\tau[/math] abbia caratteristiche differenti:
[math]\displaystyle P(X_{t<\tau})
\neq
P(X_{t\geq\tau})[/math]
Nel nostro esempio nessun singolo valore deve essere estremamente anomalo.
È la struttura complessiva della serie a essere cambiata.
PELT
Utilizziamo l’algoritmo PELT — Pruned Exact Linear Time, disponibile nella libreria ruptures.
L’idea generale è suddividere la serie in segmenti e minimizzare una funzione del tipo:
[math]\displaystyle \begin{aligned}
& \sum_{k=0}^{K} C(y_{(\tau_k,\tau_{k+1}]}) \\
&{} \quad + \beta K
\end{aligned}[/math]
dove:
- [math]C[/math] è il costo associato al segmento;
- [math]K[/math] è il numero di change point;
- [math]\beta[/math] è la penalizzazione applicata a ogni nuovo segmento.
La penalità è fondamentale.
Se è troppo bassa, il modello può trovare troppi cambiamenti.
Se è troppo alta, può non individuare cambiamenti realmente presenti.
Non esiste quindi una penalità universalmente corretta.
Va calibrata rispetto ai dati, alla funzione di costo e al contesto operativo.
Perché in questo esempio utilizziamo il modello L2?
Nel nostro esercizio abbiamo costruito deliberatamente un cambiamento di media:
[math]\displaystyle 180\text{ ms}
\rightarrow
210\text{ ms}[/math]
con una dispersione simile nei due regimi.
Per questo utilizziamo:
rpt.Pelt(model="l2")
Il modello L2 è adatto a questo esempio perché il fenomeno che vogliamo rilevare è principalmente un cambiamento di livello della media.
Questo non significa che L2 sia sempre la scelta migliore.
Se il cambiamento riguardasse anche dispersione o forma della distribuzione, potremmo utilizzare funzioni di costo differenti, tra cui approcci kernel come model="rbf".
La scelta del modello deve quindi partire dal tipo di cambiamento che stiamo cercando, non dal desiderio di utilizzare un algoritmo più sofisticato.
Python
import numpy as np
import matplotlib.pyplot as plt
import ruptures as rpt
from scipy.stats import mannwhitneyu
rng = np.random.default_rng(11)
N = 600
tau_true = 380
# -----------------------------------------
# Serie con due regimi
# -----------------------------------------
x = np.concatenate([
rng.normal(180, 12, tau_true),
rng.normal(210, 12, N - tau_true)
])
# -----------------------------------------
# PELT
# -----------------------------------------
algo = rpt.Pelt(
model="l2"
).fit(x)
# -----------------------------------------
# Penalità esplicita
# -----------------------------------------
penalty = 2 * x.std()**2 * np.log(N)
breakpoints = algo.predict(
pen=penalty
)
print(
"Breakpoints trovati:",
breakpoints
)
# -----------------------------------------
# Analisi complementare
# -----------------------------------------
def validate_bp(
x,
breakpoints,
alpha=0.01,
window=100
):
valid = []
for b in breakpoints:
if b >= len(x):
continue
left_start = max(0, b - window)
right_end = min(
b + window,
len(x)
)
seg1 = x[left_start:b]
seg2 = x[b:right_end]
if len(seg1) < 10 or len(seg2) < 10:
continue
statistic, p_value = mannwhitneyu(
seg1,
seg2,
alternative="two-sided"
)
if p_value < alpha:
valid.append({
"breakpoint": b,
"p_value": p_value
})
return valid
validated = validate_bp(
x,
breakpoints
)
print(
"Breakpoints con evidenza statistica locale:"
)
for item in validated:
print(item)
# -----------------------------------------
# Visualizzazione
# -----------------------------------------
fig, ax = plt.subplots(
2, 1,
figsize=(12, 7),
sharex=True
)
# Serie completa
ax[0].plot(
x,
lw=0.8,
label="API latency"
)
ax[0].axvline(
tau_true,
ls=":",
lw=2,
label="Cambio simulato"
)
for b in breakpoints[:-1]:
ax[0].axvline(
b,
ls="--",
lw=1,
label="PELT"
)
ax[0].set_ylabel(
"Response time (ms)"
)
ax[0].set_title(
"Change Point Detection su API latency"
)
ax[0].legend()
ax[0].grid(alpha=0.3)
# Zoom concettuale
ax[1].plot(
x,
lw=0.8
)
ax[1].axvline(
tau_true,
ls=":",
lw=2
)
ax[1].axvspan(
0,
tau_true,
alpha=0.1,
label="Regime precedente"
)
ax[1].axvspan(
tau_true,
N,
alpha=0.1,
label="Nuovo regime"
)
ax[1].set_xlabel(
"Campione"
)
ax[1].set_ylabel(
"Response time (ms)"
)
ax[1].legend()
ax[1].grid(alpha=0.3)
plt.tight_layout()
plt.show()
Un possibile risultato
Con i parametri utilizzati nell’esempio, PELT individua un breakpoint interno vicino al punto di cambiamento simulato.
L’output può essere, per esempio:
Breakpoints trovati: [380, 600]
Breakpoints con evidenza statistica locale:
{'breakpoint': 380, 'p_value': ...}
Il valore finale 600 non rappresenta un vero change point: è il limite della serie restituito da ruptures per indicare la fine dell’ultimo segmento.
Questo è un dettaglio importante quando si interpreta l’output di PELT:
l’ultimo elemento della lista dei breakpoint coincide normalmente con la fine della serie e non deve essere interpretato come un cambiamento.
Perché il test statistico non “conferma” PELT
Possiamo utilizzare un test come Mann-Whitney per verificare se le osservazioni immediatamente prima e dopo un breakpoint mostrano una differenza statisticamente significativa.
Ma attenzione al linguaggio.
Il test non dimostra che PELT abbia trovato il vero breakpoint.
Fornisce un’evidenza complementare del fatto che le due finestre di osservazioni siano differenti.
La distinzione è importante:
PELT propone una segmentazione.
Il test statistico verifica una differenza tra due insiemi di osservazioni.
L’analista deve stabilire se il cambiamento ha anche senso operativo.
Inoltre, se vengono valutati molti breakpoint o molte serie contemporaneamente, occorre considerare anche il problema dei confronti multipli e del controllo dei falsi positivi.
La domanda più importante arriva dopo il breakpoint
Supponiamo che PELT individui un cambiamento intorno al campione 380.
La domanda successiva non è:
“Abbiamo trovato il change point?”
La domanda è:
“Che cosa è successo intorno al campione 380?”
Potremmo trovare:
- un nuovo deployment;
- una modifica al database;
- un aumento del traffico;
- un nuovo algoritmo;
- un cambiamento infrastrutturale;
- una campagna marketing;
- un nuovo cliente enterprise;
- un problema di rete;
- un cambiamento nel comportamento degli utenti.
Questa è la differenza tra Change Point Detection e causal analysis.
La prima identifica una discontinuità.
Non identifica automaticamente la causa.
PELT è offline: e in produzione?
Il nostro esempio utilizza una serie completa.
Abbiamo quindi accesso anche alle osservazioni future rispetto al breakpoint.
È una situazione offline.
In un sistema operativo reale potremmo invece voler rispondere alla domanda:
“Il sistema sta cambiando proprio adesso?”
In quel caso entrano in gioco metodi online o quasi-online, come:
- BOCPD;
- CUSUM;
- ADWIN;
- finestre mobili;
- detector specifici per streaming data.
La scelta dipende da:
- latenza richiesta;
- costo dei falsi positivi;
- frequenza dei dati;
- natura del processo;
- quantità di dati disponibili;
- costo del ritardo nella detection.
Un algoritmo offline può utilizzare informazioni future rispetto al cambiamento.
Un sistema online deve invece prendere decisioni mentre i dati arrivano.
Questa differenza può essere determinante in produzione.
Un falso segnale molto comune: la stagionalità
Immaginiamo che le API siano più lente ogni lunedì mattina perché il traffico aumenta sistematicamente.
Un algoritmo non consapevole della stagionalità potrebbe interpretare il comportamento ricorrente come una successione di cambiamenti.
In questo caso il problema non è necessariamente l’algoritmo.
È la baseline.
Prima di interpretare un change point dobbiamo quindi chiederci se il fenomeno sia compatibile con:
- stagionalità giornaliera;
- stagionalità settimanale;
- stagionalità mensile;
- campagne;
- finestre di manutenzione;
- eventi ricorrenti.
Un sistema di Weak Signal Detection efficace deve conoscere il proprio regime normale prima di dichiarare che qualcosa è cambiato.
Perché questo esercizio è interessante nel lavoro reale
Il terzo esercizio affronta uno dei casi più insidiosi per un sistema di monitoraggio.
Se la latenza passa stabilmente da 180 a 210 millisecondi ma la soglia di alert è 350 ms, il sistema può continuare a essere formalmente “nella norma”.
Dal punto di vista operativo, però, potrebbe essere già iniziato un cambiamento.
La Change Point Detection permette di cercare proprio questo tipo di situazione: non il valore eccezionale, ma il momento in cui cambia la struttura del processo.
È un problema che può comparire nel monitoraggio:
- delle API;
- dei tempi di risposta;
- dei volumi di traffico;
- dei consumi infrastrutturali;
- dei tempi di consegna;
- dei comportamenti degli utenti;
- dei costi operativi.
La peculiarità dell’esercizio è soprattutto una:
rilevare il cambiamento e spiegarne la causa sono due problemi diversi.
PELT può indicare che intorno a un certo punto la serie ha cambiato comportamento.
Per capire perché dobbiamo collegare quel punto temporale agli eventi del sistema.
Mini-quiz
- Qual è la differenza concettuale tra Anomaly Detection e Change Point Detection?
- Perché una penalità PELT troppo bassa può produrre troppi breakpoint?
- Perché Mann-Whitney non dimostra la causa del cambiamento?
- Come potrebbe la stagionalità produrre falsi change point?
- Quale differenza esiste tra un algoritmo offline come PELT e un detector online?
- Perché nell’esempio utilizziamo
model="l2"invece dimodel="rbf"?
Risposte ai quiz
Esercizio 1
1. Perché lo Z-score è problematico su distribuzioni asimmetriche?
Perché utilizza media e deviazione standard, entrambe sensibili ai valori estremi. Inoltre, quando la distribuzione è fortemente asimmetrica, la distanza dalla media in deviazioni standard può avere un’interpretazione meno adatta rispetto al caso gaussiano.
2. Perché IQR e MAD sono più robusti?
Perché utilizzano statistiche basate su mediana e quartili, che sono meno influenzate dai valori estremi rispetto a media e deviazione standard.
3. Che cosa succede con una forte stagionalità?
Il detector può classificare come anomalie osservazioni che sono perfettamente normali per quella particolare fase del ciclo.
4. Perché un outlier non è automaticamente un weak signal?
Perché potrebbe essere prodotto da rumore, errore di misura, evento straordinario, stagionalità o comportamento già noto.
5. Perché analizzare gli ordini alla base dell’AOV?
Perché un valore aggregato può nascondere meccanismi molto diversi: pochi ordini estremamente elevati, un cambiamento del mix di prodotti o una variazione nella composizione dei clienti.
Esercizio 2
1. Una query persistente è automaticamente un weak signal?
No.
Potrebbe essere una query stabile, stagionale o già consolidata.
La persistenza è un criterio di selezione, non una prova definitiva.
2. Perché utilizzare il logaritmo?
Per rendere più leggibile la crescita relativa e ridurre l’effetto dei valori molto grandi. In un modello log-lineare, la pendenza può inoltre essere trasformata in un tasso di crescita composto.
3. Perché lo score non è una probabilità?
Perché non deriva da un modello probabilistico calibrato.
È uno score euristico costruito per ordinare i fenomeni da investigare.
4. Come trattare una query stagionale?
Confrontandola con baseline stagionali, periodi equivalenti degli anni precedenti e calendario commerciale.
5. Una crescita da 1 a 8 è più interessante di una da 100 a 150?
Non necessariamente.
La prima ha una crescita percentuale molto più elevata, ma parte da una base minima e può essere molto rumorosa.
La seconda presenta una crescita relativa inferiore ma coinvolge una massa molto maggiore.
È proprio per questo che persistenza, crescita e rilevanza devono essere considerate insieme.
6. Perché utilizzare una baseline?
Per capire se la crescita osservata è realmente insolita rispetto al comportamento normale del sistema e non soltanto diversa da una soglia arbitraria.
Esercizio 3
1. Anomaly Detection vs Change Point Detection
L’Anomaly Detection cerca osservazioni insolite.
La Change Point Detection cerca un momento in cui cambia la struttura del processo.
2. Penalità troppo bassa
Una penalità bassa rende conveniente introdurre molti segmenti e può produrre over-segmentation.
3. Perché Mann-Whitney non dimostra la causa?
Perché evidenzia una differenza tra campioni, non identifica il meccanismo che l’ha prodotta.
4. Stagionalità
Se la distribuzione cambia regolarmente in corrispondenza di un ciclo noto, il detector può interpretare la dinamica ricorrente come una successione di cambiamenti strutturali.
5. Offline vs online
Un metodo offline può utilizzare l’intera serie disponibile per segmentarla.
Un metodo online deve prendere decisioni mentre arrivano nuovi dati e deve quindi gestire il compromesso tra tempestività, falsi allarmi e ritardo nella detection.
6. Perché model="l2"?
Perché nell’esempio il cambiamento simulato riguarda principalmente la media della serie: circa 180 ms prima del breakpoint e 210 ms dopo.
Dal detector al business: la vera pipeline dei weak signals
I tre esercizi mostrano tre livelli diversi di analisi.
| Livello | Domanda | Tecniche | Output | Azione |
|---|---|---|---|---|
| 1 | C’è qualcosa di insolito? | Z-score, IQR, MAD | Candidato anomalo | Investigare |
| 2 | Sta diventando persistente? | Persistenza, crescita, baseline | Segnale da monitorare | Monitorare |
| 3 | Il sistema ha cambiato regime? | Change Point Detection, PELT | Possibile cambiamento strutturale | Cercare la causa |
Questa distinzione evita di trasformare la Weak Signals Detection in una semplice gara tra algoritmi.
Il vero processo è più vicino a:
[math]\displaystyle \begin{aligned}
& \boxed{
Detection
\rightarrow
Validation
\rightarrow
Context
\rightarrow
Investigation
\rightarrow
Decision
\rightarrow
Monitoring
}
\end{aligned}[/math]
La prima fase è prevalentemente quantitativa.
Le successive richiedono sempre più contesto.
Il costo degli alert: quando la detection diventa un problema
È facile costruire un sistema che produca migliaia di anomalie.
È molto più difficile costruire un sistema che aiuti un’organizzazione a capire quali anomalie meritano attenzione.
Se ogni giorno arrivano 2.000 alert, il problema non è più soltanto la detection.
È il costo cognitivo dell’alerting.
Ogni alert può richiedere:
- tempo dell’analista;
- verifica del dato;
- confronto con altre fonti;
- coinvolgimento del product manager;
- escalation verso un team tecnico;
- eventuale intervento operativo.
Per questo la qualità di un sistema di detection non può essere valutata soltanto attraverso metriche come precision e recall.
Anche un detector statisticamente valido può produrre un sistema operativo poco utile se genera troppi falsi positivi.
La domanda diventa allora:
[math]\displaystyle \begin{aligned}
& \text{Valore dell’indagine} \\
&{} = \text{Valore potenziale del segnale} \\
&{} \quad – \text{Costo dell’investigazione}
\end{aligned}[/math]
Il costo dell’investigazione non è soltanto economico.
È anche il tempo sottratto ad altre attività e l’attenzione consumata da fenomeni che potrebbero non portare ad alcuna decisione.
Falso positivo e falso negativo non costano allo stesso modo
Nel weak signal detection esistono almeno due errori importanti.
Falso positivo
Il sistema segnala un fenomeno che non ha reale rilevanza.
Il costo può essere il tempo speso per investigarlo.
Falso negativo
Il sistema non segnala un cambiamento che avrebbe meritato attenzione.
Il costo potrebbe essere molto maggiore: perdita di clienti, ritardo nell’adattamento del prodotto, aumento dei costi o mancata risposta a una nuova esigenza del mercato.
Il rapporto tra questi due costi dipende dal problema.
Non esiste quindi una soglia universalmente ottimale.
La soglia deve riflettere il costo degli errori e il valore delle decisioni che il sistema deve supportare.
Il principio manageriale: non cercare più alert, cerca migliori domande
Un buon sistema di Weak Signals Detection non dovrebbe avere come obiettivo finale produrre il maggior numero possibile di anomalie.
Dovrebbe aiutare l’organizzazione a capire:
quali cambiamenti meritano tempo, contesto e investigazione.
Una variazione minima del comportamento di un piccolo segmento potrebbe avere scarso valore economico.
La stessa variazione osservata in un segmento strategico potrebbe invece giustificare un’indagine immediata.
Per questo la rilevanza business non dovrebbe essere aggiunta alla fine come un’etichetta descrittiva.
Dovrebbe entrare nel processo di prioritizzazione.
Una possibile architettura operativa
In un ambiente reale possiamo immaginare una pipeline composta da sei livelli.
1. Detection
Individuiamo:
- osservazioni insolite;
- cambiamenti di distribuzione;
- nuove sequenze comportamentali;
- variazioni persistenti.
2. Persistence
Verifichiamo se il fenomeno si ripete.
3. Growth
Misuriamo se sta aumentando, diminuendo o rimanendo stabile.
4. Context
Confrontiamo il segnale con:
- stagionalità;
- segmenti;
- prodotti;
- geografie;
- campagne;
- competitor;
- eventi esterni.
5. Business validation
La domanda finale diventa:
Se questo fenomeno continuasse, quale decisione cambierebbe?
Se la risposta è “nessuna”, forse non vale la pena investire molto nell’indagine.
Se invece il segnale potrebbe modificare:
- pricing;
- prodotto;
- marketing;
- supply chain;
- customer experience;
- infrastruttura;
- allocazione degli investimenti;
allora il costo dell’analisi può essere pienamente giustificato.
6. Monitoring
La decisione non chiude necessariamente il processo.
Se il business interviene, dobbiamo osservare che cosa succede dopo.
Il ciclo diventa:
[math]\displaystyle \begin{aligned}
& \boxed{
Segnale
\rightarrow
Indagine
\rightarrow
Decisione
\rightarrow
Effetto
\rightarrow
Nuovo\ monitoraggio
}
\end{aligned}[/math]
In questo modo la detection diventa parte di un processo decisionale e non un semplice sistema di notifiche.
Perché i tre esercizi formano un unico percorso
I tre esercizi non sono soltanto tre modi diversi di applicare Python a una serie di dati.
Rappresentano tre domande differenti.
Esercizio 1
C’è un’osservazione insolita?
La risposta richiede detector di anomalie.
Esercizio 2
Il fenomeno sta continuando e sta crescendo?
La risposta richiede analisi temporale, persistenza e baseline.
Esercizio 3
Il processo nel suo complesso è cambiato?
La risposta richiede Change Point Detection.
Il percorso è quindi:
[math]\displaystyle \begin{aligned}
& \boxed{
\text{Outlier}
\rightarrow
\text{Persistenza}
\rightarrow
\text{Cambiamento di regime}
}
\end{aligned}[/math]
Ma nessuno dei tre passaggi, da solo, dimostra che sia comparso un nuovo trend.
La lezione finale
La Weak Signals Detection non consiste nel cercare valori strani nei dati.
Consiste nel riconoscere cambiamenti ancora troppo piccoli, troppo recenti o troppo distribuiti per essere evidenti nelle metriche aggregate.
Nel primo esercizio abbiamo visto che un outlier è soltanto un punto di partenza.
Nel secondo abbiamo aggiunto la persistenza e la dinamica temporale.
Nel terzo abbiamo fatto un passo ulteriore: il segnale può emergere anche senza alcun outlier, semplicemente perché il sistema ha cambiato regime.
La progressione può quindi essere sintetizzata così:
[math]\displaystyle \begin{aligned}
& \boxed{
\begin{aligned}
&\text{Anomalia}
\rightarrow
\text{Persistenza}
\rightarrow
\text{Crescita}\\
&\rightarrow
\text{Cambio di regime}
\end{aligned}
}
\end{aligned}[/math]
Ma c’è un’ultima distinzione da non perdere.
Nessuno dei tre esercizi dimostra che il fenomeno osservato diventerà necessariamente un trend.
La Data Science può dirci:
“Questo comportamento merita attenzione.”
Non può sostituire automaticamente la domanda successiva:
“Perché sta succedendo e che cosa significa per il business?”
E ancora:
“Se questo cambiamento continuasse, quale decisione dovremmo modificare?”
È proprio in questo passaggio che la Weak Signals Detection smette di essere soltanto una tecnica di anomaly detection e diventa uno strumento di competitive intelligence, product analytics e decision support.
Il valore non sta nel prevedere ogni futuro cambiamento.
Sta nel ridurre il tempo che intercorre tra il momento in cui qualcosa comincia a cambiare e il momento in cui l’organizzazione se ne accorge.
👉 prosegui con gli altri esercizi
📚 Per approfondire: segnali deboli, outlier e Anomaly Detection
Un segnale anomalo non è necessariamente un errore e un outlier non dovrebbe essere eliminato automaticamente.
Questi approfondimenti mostrano come passare dall’identificazione dei segnali deboli alla rilevazione degli outlier,
utilizzando metodi robusti come IQR e MAD, fino alle pipeline di Anomaly Detection e all’analisi multivariata con la distanza di Mahalanobis.
👉Weak Signals Detection: come identificare segnali deboli ed emergenti nei dati
👉Outlier: cosa sono, come trovarli e rimuoverli con Pandas, IQR e Z-score
👉Anomaly Detection in produzione: pipeline con IQR, MAD e Isolation Forest
👉Distanza di Mahalanobis: dalla geometria all’Anomaly Detection in Python





