Dai test d’ipotesi al codice Python: verificare, automatizzare e interpretare i risultati statistici nel marketing

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Test d’ipotesi nel marketing con Python

Dal calcolo manuale alla Data Science

Un test statistico non termina quando abbiamo calcolato un p-value.

Nel lavoro quotidiano di un Data Scientist esiste un passaggio ulteriore e decisivo: trasformare il procedimento matematico in un algoritmo riproducibile, verificabile e riutilizzabile su nuovi dati.

È questo il collegamento naturale tra statistica teorica e Data Science applicata.

Nel precedente articolo pubblicato su Matematica & Oltre, “Test d’Ipotesi nel Marketing: Quando la Significatività Statistica Non Basta”, abbiamo analizzato tre situazioni tipiche:

  • Un test Z sulla differenza tra due medie;
  • Il calcolo preventivo della dimensione campionaria;
  • Un A/B test in cui i risultati dichiarati da un software devono essere verificati e sottoposti ad audit.

Il filo conduttore era una distinzione fondamentale:

[math]\displaystyle \boxed{\text{Significatività statistica} \neq \text{Rilevanza pratica} \neq \text{Convenienza economica}}[/math]

Il codice Python che sviluppiamo in questo articolo serve proprio a rendere operativa questa distinzione.

Non vogliamo semplicemente far fare i conti alla macchina, ma costruire un piccolo laboratorio computazionale di statistica applicata alle decisioni aziendali.

Pubblicità

Tre scenari di Data Science applicata al marketing

Per capire perché sia utile tradurre i test statistici in codice, immaginiamo di trovarci davanti a tre problemi apparentemente diversi, accomunati da un’unica domanda: come trasformare i dati di un esperimento in una decisione affidabile?

Valutazione di una campagna pubblicitaria:

Un’azienda ha introdotto una nuova campagna e vuole verificare se il gruppo esposto presenta una notorietà del brand superiore rispetto al gruppo di controllo. Dai dati emerge una differenza tra le due medie, ma l’azienda non considera sufficiente un incremento qualsiasi: per essere realmente interessante, la campagna deve produrre almeno 3 punti di miglioramento. Il problema statistico va quindi oltre la classica ipotesi [math]H_0: \mu_T – \mu_C \le 0[/math]. Vogliamo verificare se la differenza supera una soglia di rilevanza pratica: [math]H_0: \mu_T – \mu_C \le 3[/math]. Python riprodurrà passo dopo passo il calcolo del test Z, dall’errore standard al p-value.

Progettazione dell’esperimento:

Supponiamo di voler confrontare due versioni di un’offerta commerciale. Prima ancora di raccogliere i dati, dobbiamo stabilire quanti consumatori inserire nei due gruppi. Il problema non è più “il risultato osservato è significativo?”, ma “quanti dati dobbiamo raccogliere per avere una probabilità sufficientemente elevata di rilevare l’effetto che ci interessa?”. Entrano in gioco [math]\alpha[/math] (errore di I tipo), [math]\beta[/math] (errore di II tipo) e la potenza statistica ([math]1 – \beta[/math]). Con Python trasformiamo la formula della dimensione campionaria in una funzione riutilizzabile per esplorare diversi scenari di business.

Audit degli A/B test ed analisi economica:

Un e-commerce introduce un nuovo algoritmo di raccomandazione. Il team osserva un incremento nel valore medio degli ordini e il software restituisce un p-value apparentemente significativo. Prima di presentare i dati al management, il Data Scientist deve verificare che il calcolo sia coerente con le medie, le numerosità campionarie, le deviazioni standard e le ipotesi del test. Inoltre, se l’effetto esiste, occorre stabilire se sia abbastanza grande da giustificare il costo infrastrutturale dell’algoritmo, collegando la statistica a margine netto, costo mensile, profitto incrementale e ROI.

La progressione metodologica è quindi intenzionale:

[math]\displaystyle \boxed{\text{Verificare} \rightarrow \text{Progettare} \rightarrow \text{Decidere}}[/math]


La riunione che rischia di partire male

Il responsabile marketing appoggia il foglio sulla scrivania: “Abbiamo +5 punti di notorietà rispetto al gruppo di controllo. La campagna funziona, possiamo allargarla a tutto il database.”

Guardi i numeri: 70 contro 65, una differenza netta a prima vista. Qualcuno inizia già a parlare di allocare il budget per il trimestre successivo.

Tuttavia, quando il progetto è stato approvato, l’azienda aveva stabilito un vincolo chiaro: l’incremento deve essere di almeno 3 punti per coprire i costi della campagna. La domanda corretta non è “la campagna ha avuto un effetto?”, ma “abbiamo evidenza sufficiente per affermare che l’incremento supera la soglia minima di 3 punti?”. Un +5 osservato su un campione ridotto può essere solo rumore statistico.

Forse potrebbe interessarti anche:  Random Walk e Machine Learning nel Trading: perché i dati storici non prevedono il mercato

E se l’analisi è errata, quel budget diventa una perdita netta.

Esercizio 1 — Verificare un test Z con Python

Consideriamo i dati dell’esperimento sulla notorietà del brand:

Gruppo Trattamento: [math]n_T = 70[/math], [math]\bar{x}_T = 70[/math], [math]\sigma_T = 12[/math]

Gruppo di Controllo: [math]n_C = 80[/math], [math]\bar{x}_C = 65[/math], [math]\sigma_C = 12[/math]

La differenza osservata è [math]\bar{x}_T – \bar{x}_C = 5[/math], ma la soglia di rilevanza pratica è [math]\Delta_0 = 3[/math]. Le ipotesi del test ad una coda sono:

[math]H_0: \mu_T – \mu_C \le 3 \quad \text{contro} \quad H_1: \mu_T – \mu_C > 3[/math]

Il calcolo analitico prevede:

[math]\displaystyle SE = \sqrt{\frac{12^2}{70} + \frac{12^2}{80}} \approx 1.964[/math]

[math]\displaystyle Z = \frac{(70 – 65) – 3}{1.964} \approx 1.018[/math]

Con [math]\alpha = 0.05[/math], il valore critico sulla distribuzione normale standardizzata è [math]z_{0.95} = 1.645[/math]. Poiché [math]1.018 < 1.645[/math], non possiamo rifiutare [math]H_0[/math]. Il p-value corrispondente è [math]p \approx 0.154[/math]: non vi è evidenza statistica sufficiente per affermare che l’incremento medio superi i 3 punti.

Python

import numpy as np
from scipy import stats

# Dati dell'esperimento
n_T, n_C = 70, 80
mean_T, mean_C = 70, 65
sigma_T, sigma_C = 12, 12

# Differenza minima rilevante e significatività
delta_0 = 3
alpha = 0.05

# 1. Errore standard
se = np.sqrt(sigma_T**2 / n_T + sigma_C**2 / n_C)

# 2. Statistica Z
z = ((mean_T - mean_C) - delta_0) / se

# 3. p-value
p_value = 1 - stats.norm.cdf(z)

# 4. Valore critico
z_crit = stats.norm.ppf(1 - alpha)

# Output
print(f"Errore standard = {se:.3f}")
print(f"Z osservato     = {z:.3f}")
print(f"p-value         = {p_value:.3f}")
print(f"Valore critico  = {z_crit:.3f}")

if z > z_crit:
    print("Decisione: rifiutiamo H0.")
else:
    print("Decisione: non rifiutiamo H0.")

Output

Errore standard = 1.964
Z osservato     = 1.018
p-value         = 0.154
Valore critico  = 1.645
Decisione: non rifiutiamo H0.

Perché la sottrazione di [math]\Delta_0[/math] cambia la prospettiva di business

Nel codice, la statistica [math]Z[/math] è calcolata come z = ((mean_T - mean_C) - delta_0) / se. Se avessimo omesso delta_0, avremmo testato l’ipotesi standard [math]H_0: \mu_T – \mu_C \le 0[/math], rispondendo alla domanda “la campagna ha un effetto positivo qualsiasi?”.

Inserendo [math]\Delta_0 = 3[/math], il test risponde alla vera domanda aziendale: “l’effetto è abbastanza grande da giustificare l’investimento?”. Sottraendo la soglia a numeratore, imponiamo un criterio più severo che evita di approvare interventi con impatti trascurabili.


Il capo vuole la risposta per venerdì

“Facciamo un test rapido”, propone il product manager. “Mandiamo la nuova offerta a un po’ di clienti, confrontiamo con il gruppo di controllo e vediamo chi compra di più.”

Ti chiede quanti utenti servano e ipotizza un centinaio per parte per chiudere la prova in tre giorni.

Torni alla scrivania, apri Python e imposti i parametri: deviazione standard [math]\sigma = 2[/math], differenza minima rilevabile [math]\Delta = 0.5[/math], potenza desiderata [math]1 – \beta = 0.90[/math] e [math]\alpha = 0.05[/math].

Il risultato indica 337 utenti per gruppo.

Raccogliere un campione inferiore significa condurre un esperimento sottodimensionato, incapace di rilevare l’effetto desiderato anche qualora fosse reale. Ridurre arbitrariamente il campione non fa risparmiare tempo: aumenta solo il rischio di prendere decisioni al buio.

Pubblicità

Esercizio 2 — Automatizzare il calcolo della dimensione campionaria

Determiniamo la dimensione campionaria per un test bilaterale su due medie indipendenti con deviazione standard nota ([math]\sigma = 2[/math]), differenza minima da rilevare [math]\Delta = 0.5[/math], [math]\alpha = 0.05[/math] e potenza [math]1 – \beta = 0.90[/math].

La formula teorica è:

[math]\displaystyle n = \frac{(z_{\alpha/2} + z_{1-\beta})^2 (\sigma_A^2 + \sigma_B^2)}{\Delta^2}[/math]

Sostituendo i valori otteniamo [math]n \approx 336.25[/math], che arrotondiamo per eccesso a 337 consumatori per gruppo (674 complessivi).

Python

import math
from scipy import stats

def sample_size_two_means(alpha, beta, sigma, delta):
    """
    Calcola la dimensione campionaria per ciascun gruppo
    in un test bilaterale su due medie indipendenti.
    """
    z_alpha = stats.norm.ppf(1 - alpha / 2)
    z_beta = stats.norm.ppf(1 - beta)

    n = ((z_alpha + z_beta)**2 * (2 * sigma**2)) / (delta**2)
    return math.ceil(n)

# Calcolo campionario
n = sample_size_two_means(alpha=0.05, beta=0.10, sigma=2, delta=0.5)

print(f"Consumatori per gruppo: {n}")
print(f"Consumatori totali:     {2*n}")

Output

Consumatori per gruppo: 337
Consumatori totali:     674

Arrotondiamo sempre per eccesso con math.ceil() sia per rispettare la natura discreta delle unità campionarie, sia per garantire che la potenza reale dell’esperimento non scenda mai al di sotto del valore target desiderato.

Analisi di scenario e trade-off sperimentali

Poiché la dimensione campionaria è inversamente proporzionale al quadrato dell’effetto minimo ([math]n \propto \frac{1}{\Delta^2}[/math]), ridurre [math]\Delta[/math] comporta un incremento quadratico del campione richiesto:

deltas = [0.5, 0.4, 0.3, 0.2]

print("Effetto minimo | n per gruppo | n totale")
print("-" * 42)

for delta in deltas:
    n = sample_size_two_means(alpha=0.05, beta=0.10, sigma=2, delta=delta)
    print(f"{delta:14.1f} | {n:12d} | {2*n:8d}")

Output

Effetto minimo | n per gruppo | n totale
------------------------------------------
           0.5 |          337 |      674
           0.4 |          526 |     1052
           0.3 |          934 |     1868
           0.2 |         2102 |     4204

Passare da [math]\Delta = 0.5[/math] a [math]\Delta = 0.2[/math] moltiplica il campione di 6.25 volte ([math]0.5^2 / 0.2^2[/math]).

Forse potrebbe interessarti anche:  Distribuzione ipergeometrica: 3 esercizi aziendali svolti con Python

Possiamo inoltre valutare i costi in termini di dimensione campionaria associati a una maggiore rigidità statistica:

scenarios = [
    ("Alpha 5%, Power 90%", 0.05, 0.10),
    ("Alpha 1%, Power 90%", 0.01, 0.10),
    ("Alpha 5%, Power 95%", 0.05, 0.05)
]

for name, alpha, beta in scenarios:
    n = sample_size_two_means(alpha=alpha, beta=beta, sigma=2, delta=0.5)
    print(f"{name}: {n} consumatori per gruppo")

Output

Alpha 5%, Power 90%: 337 consumatori per gruppo
Alpha 1%, Power 90%: 477 consumatori per gruppo
Alpha 5%, Power 95%: 416 consumatori per gruppo

La slide che non torna

Durante una presentazione, l’analista junior mostra i risultati dell’A/B test sul nuovo algoritmo di raccomandazione: +0.50 € sul carrello medio e un p-value indicato a 0.048. Il management è pronto ad approvare l’implementazione.

Verificando i calcoli con le dimensioni campionarie ([math]n = 1000[/math] per gruppo) e la varianza storica ([math]\sigma = 10[/math]), emerge che l’errore standard reale è [math]SE = 0.4472[/math], cui corrisponde uno [math]Z = 1.118[/math] e un p-value bilaterale di 0.264. Il risultato del software era errato o basato su assunzioni errate. Un secondo esperimento su 5.000 utenti per gruppo registra un incremento di soli +0.20 €, portando [math]Z = 1.000[/math] e un p-value di 0.317.

L’intervento del Data Scientist evita di adottare un algoritmo che avrebbe generato 5.000 € al mese di costi infrastrutturali senza apportare alcun beneficio economico reale.

Esercizio 3 — Verificare un A/B test ed effettuare l’audit economico

Costruiamo una funzione per eseguire il test Z bilaterale con deviazioni standard note e verificare i due esperimenti:

import numpy as np
from scipy import stats

def z_test_two_means(mean_1, mean_2, n_1, n_2, sigma_1, sigma_2):
    """
    Test Z bilaterale per due medie indipendenti con deviazioni standard note.
    """
    se = np.sqrt(sigma_1**2 / n_1 + sigma_2**2 / n_2)
    z = (mean_1 - mean_2) / se
    p_value = 2 * (1 - stats.norm.cdf(abs(z)))
    return z, p_value

# Primo esperimento
z1, p1 = z_test_two_means(52.3, 51.8, 1000, 1000, 10, 10)
print(f"Primo esperimento  -> Z: {z1:.3f}, p-value: {p1:.3f}")

# Secondo esperimento
z2, p2 = z_test_two_means(52.1, 51.9, 5000, 5000, 10, 10)
print(f"Secondo esperimento -> Z: {z2:.3f}, p-value: {p2:.3f}")

Output

Primo esperimento  -> Z: 1.118, p-value: 0.264
Secondo esperimento -> Z: 1.000, p-value: 0.317

Un principio chiave: un campione più grande non garantisce un p-value più piccolo

La statistica di test è il rapporto tra effetto osservato ed errore standard: [math]Z = \frac{\Delta}{SE}[/math]. Nel secondo esperimento, il campione aumenta da 1.000 a 5.000 osservazioni per gruppo, riducendo l’errore standard da 0.447 a 0.200. Tuttavia, l’effetto osservato sul campo scende da 0.50 € a 0.20 €. Di conseguenza, il valore di [math]Z[/math] diminuisce anziché aumentare.

Aumentare la dimensione del campione aumenta la precisione della stima, ma se l’effetto reale è modesto o nullo, il p-value non diventerà significativo.

Per rilevare in modo affidabile un incremento di [math]\Delta = 0.50 €[/math] con [math]\sigma = 10[/math], [math]\alpha = 0.05[/math] e potenza dell’80% ([math]1 – \beta = 0.80[/math]), il campione necessario è di 6.280 osservazioni per gruppo (12.560 totali):

n_required = sample_size_two_means(alpha=0.05, beta=0.20, sigma=10, delta=0.5)
print(f"Campione richiesto per gruppo: {n_required}")
print(f"Campione totale:               {2*n_required}")

Output

Campione richiesto per gruppo: 6280
Campione totale:               12560

(Nota: il calcolo analitico approssima [math]z_{0.80} \approx 0.84[/math] fornendo [math]n \approx 6272[/math], mentre SciPy utilizza la precisione floating-point [math]z_{0.80} \approx 0.8416[/math] restituendo [math]n = 6280[/math]).

Dalla statistica all’economia: Break-Even e Soglia Minima

Ipotizziamo che un nuovo A/B test confermi in modo robusto un incremento del carrello medio pari a [math]\Delta = 0.50 €[/math]. L’infrastruttura dell’algoritmo presenta un costo fisso di [math]C = 5000 €/\text{mese}[/math] e il margine netto sulle vendite è [math]m = 10\%[/math].

Il profitto incrementale per ordine è:

[math]\text{Profitto per ordine} = \Delta \times m = 0.50 € \times 0.10 = 0.05 €[/math]

Il numero di ordini mensili necessari per raggiungere il break-even è:

[math]\displaystyle N_{BE} = \frac{C}{\Delta \times m} = \frac{5000}{0.05} = 100.000 \text{ ordini/mese}[/math]

Invertendo la relazione per un e-commerce che gestisce [math]N = 500.000 \text{ ordini/mese}[/math], possiamo calcolare l’incremento minimo del carrello medio ([math]\Delta_{\min}[/math]) necessario per coprire i costi:

Forse potrebbe interessarti anche:  Catene di Markov in Python: Modellare Funnel, Retention e Churn oltre le classiche dashboard

[math]\displaystyle \Delta_{\min} = \frac{C}{N \times m} = \frac{5000}{500000 \times 0.10} = 0.10 €[/math]

Python

def minimum_economic_lift(monthly_cost, monthly_orders, margin):
    """Calcola l'incremento minimo del carrello per il break-even."""
    return monthly_cost / (monthly_orders * margin)

delta_min = minimum_economic_lift(monthly_cost=5000, monthly_orders=500_000, margin=0.10)
print(f"Incremento minimo per break-even: {delta_min:.2f} €")

Output

Incremento minimo per break-even: 0.10 €

Analisi di sensibilità del ROI

Costruiamo una simulazione per analizzare l’impatto economico al variare del lift osservato:

monthly_cost = 5000
monthly_orders = 500_000
margin = 0.10
lifts = [0.02, 0.05, 0.10, 0.20, 0.50]

print("Lift medio | Profitto incrementale | ROI netto")
print("-" * 52)

for lift in lifts:
    incremental_profit = monthly_orders * lift * margin
    net_profit = incremental_profit - monthly_cost
    roi = net_profit / monthly_cost
    print(f"{lift:9.2f} € | {incremental_profit:20,.0f} € | {roi:8.2%}")

Output

Lift medio | Profitto incrementale | ROI netto
----------------------------------------------------
     0.02 € |                1,000 € |  -80.00%
     0.05 € |                2,500 € |  -50.00%
     0.10 € |                5,000 € |    0.00%
     0.20 € |               10,000 € |  100.00%
     0.50 € |               25,000 € |  400.00%

L’analisi mostra chiaramente che un effetto statisticamente significativo inferiore a [math]0.10 €[/math] genera un ROI negativo. La valutazione statistica stabilisce la presenza del segnale, ma è l’analisi economica a determinarne il valore.

Attenzione al Peeking: automatizzare la procedura non corregge gli errori metodologici

Nel secondo esperimento abbiamo eseguito un’analisi intermedia e successivamente una seconda valutazione su un campione esteso. Ispezionare ripetutamente i dati interrompendo l’esperimento non appena [math]p < 0.05[/math] (peeking) gonfia il tasso effettivo di falsi positivi (errore di I tipo).

L’automazione in Python velocizza l’esecuzione del calcolo, ma non sostituisce la corretta pianificazione dell’esperimento (come l’adozione di test sequenziali o la correzione dei livelli di significatività).

Dal codice alla decisione: la pipeline completa

Il percorso di analisi si articola in sette passaggi sequenziali:

  1. Definizione del problema: Individuare l’effetto target e determinare la soglia di utilità per il business.
  2. Formulazione delle ipotesi: Definire un’ipotesi nulla orientata alle soglie operative ([math]H_0: \Delta \le \Delta_0[/math]).
  3. Dimensionamento del campione: Valutare [math]\alpha[/math], [math]\beta[/math], [math]\sigma[/math] e [math]\Delta[/math] prima di avviare la raccolta dati.
  4. Calcolo della statistica test: Implementare la pipeline [math]SE \rightarrow Z \rightarrow p[/math] in codice riproducibile.
  5. Stima dell’effetto: Misurare la dimensione dell’effetto e l’incertezza della stima.
  6. Valutazione della rilevanza pratica: Verificare il superamento della soglia [math]\Delta_0[/math].
  7. Valutazione economica: Verificare la sostenibilità finanziaria del progetto ([math]\text{Beneficio Incrementale} > \text{Costo}[/math]).

[math]\displaystyle \boxed{\text{Domanda di Business} \rightarrow \text{Modello Statistico} \rightarrow \text{Codice Python} \rightarrow \text{Verifica} \rightarrow \text{Stima Effetto} \rightarrow \text{Decisione}}[/math]

La difficoltà nell’analisi dei dati non risiede nella scrittura del codice, ma nella formulazione delle domande corrette prima di eseguire l’algoritmo.

Il test Z verifica se la differenza osservata sia riconducibile al caso; la stima campionaria definisce le risorse necessarie per un’indagine affidabile; l’analisi di break-even stabilisce la convenienza finanziaria dell’operazione. Integrati in un’unica pipeline in Python, questi strumenti forniscono un supporto metodologico rigoroso per guidare e motivare le decisioni strategiche nel marketing. Il ruolo della Data Science non è produrre numeri isolati, ma rendere trasparenti e razionali le scelte che i numeri suggeriscono.

📚 p-value, test t, test Z e verifica delle ipotesi

Per capire come interpretare correttamente il p-value e scegliere il test statistico più adatto, questi articoli approfondiscono test t di Student, test Z, potenza statistica e verifica delle ipotesi, con esercizi pratici e implementazioni in Python:

👉 Il p-value: una spiegazione dettagliata

👉 Esercizi sul p-value nel marketing: una guida pratica con Python

👉 Test t di Student: esercizi svolti, test appaiati e indipendenti e potenza statistica

👉 Test t in Python con SciPy: guida pratica a Student, Welch e p-value

👉 Come fare un test Z: esercizi pratici su proporzioni, p-value e varianza

👉 Verifica delle ipotesi con il test Z: 3 esercizi svolti per capire il p-value

 

Pubblicità