
Case Study: End-to-End Marketing Data Science
Practical Project: Predict churn and build a data-driven retention strategy.
What you will learn
- Costruire 20 feature di churn da transazioni, utilizzo, attrito e tenure
- Addestrare un Gradient Boosting e incrociare rischio e valore per assegnare azioni
- Presentare precisione nel segmento critico e ROI in euro al decisore
Case Study: End-to-End Marketing Data Science
È l’ultima lezione del modulo, e la tratto come si tratta una consegna vera: un progetto completo di retention, dalla prima feature all’ultima slide per il decisore. Tutto il lavoro si svolge su tabelle di feature, modelli tabellari e pipeline di scoring, e l’obiettivo è un ROI in euro, non un AUC da esibire. Seguiamo il caso fase per fase, una settimana alla volta.
Di cosa parla il caso
Il caso end-to-end collega feature, modello di churn, segmentazione per valore e attivazione quotidiana per decidere dove investire il budget di retention.
La sequenza di lavoro in cinque passaggi
Prima di entrare nel dettaglio, ecco la mappa del progetto. La seguiremo fase per fase, una settimana alla volta.
- Ingegnerizza 20 feature da transazioni, utilizzo, attrito e tenure.
- Addestra un Gradient Boosting per la probabilità di churn a 30 giorni.
- Incrocia rischio e valore per assegnare azione, canale e budget.
- Attiva scoring giornaliero con soglia di degrado AUC e Reverse ETL.
- Presenta precisione nel segmento critico e ROI in euro al decisore.
Fase 1: feature engineering in settimana 1
Si parte costruendo 20 feature da dati transazionali, navigazione, supporto e marketing. L’obiettivo è leggere il caso come una consegna di lavoro completa. Ogni feature deve spiegare quale comportamento cattura e quale segnale di rischio porta con sé.
# Features from transactional data
df['recency_days'] = (df['today'] - df['last_order_date']).dt.days
df['frequency_total'] = df.groupby('customer_id')['order_id'].transform('count')
df['monetary_total'] = df.groupby('customer_id')['amount'].transform('sum')
df['aov'] = df['monetary_total'] / df['frequency_total']
# Usage trends
df['active_days_last_14'] = df.groupby('customer_id')['active_day'].transform(
lambda x: x.rolling(14).sum())
df['active_days_prev_14'] = df.groupby('customer_id')['active_day'].transform(
lambda x: x.shift(14).rolling(14).sum())
df['usage_trend'] = df['active_days_last_14'] / df['active_days_prev_14'].clip(1)
# Churn signals
df['has_payment_failure'] = (df['failed_payments_30d'] > 0).astype(int)
df['support_tickets_30d'] = df['support_tickets_30d']
df['avg_session_minutes_trend'] = df['avg_session_7d'] / df['avg_session_prev_7d'].clip(1)
# Engagement depth
features_list = ['feature_try_on', 'feature_style_quiz', 'feature_wishlist',
'feature_reviews', 'feature_referral']
df['engagement_depth'] = df[features_list].sum(axis=1)
# Temporal signals
df['tenure_months'] = (df['today'] - df['signup_date']).dt.days / 30
df['is_month_1'] = (df['tenure_months'] <= 1).astype(int)
Fase 2: modello predittivo in settimana 2
Con le feature pronte, addestriamo un Gradient Boosting per stimare la probabilità di churn nel mese successivo. Definiamo il churn come assenza di ordini nei 30 giorni seguenti. Teniamo d’occhio quali feature pesano di più nella decisione del modello.
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import roc_auc_score, classification_report
feature_cols = ['recency_days', 'frequency_total', 'monetary_total', 'aov',
'usage_trend', 'has_payment_failure', 'support_tickets_30d',
'avg_session_minutes_trend', 'engagement_depth', 'tenure_months',
'is_month_1', 'days_since_last_login', 'email_open_rate_30d']
X = df[feature_cols].fillna(0)
y = df['churned_next_month'] # definition: no orders in the next 30 days
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y)
model = GradientBoostingClassifier(
n_estimators=200, max_depth=4, learning_rate=0.05, random_state=42)
model.fit(X_train, y_train)
probs = model.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, probs)
print(f"AUC: {auc:.3f}") # Target: >0.82
# Feature importance
importances = pd.Series(model.feature_importances_, index=feature_cols)
print(importances.sort_values(ascending=False).head(10))
Expected result: AUC intorno a 0.84, con recency_days, usage_trend e tenure_months tra le tre feature più importanti.
Fase 3: segmentazione per azione in settimana 3
Un buon punteggio di rischio non basta. Dividi i clienti in quattro quadranti incrociando Churn Risk e Customer Value. La stessa probabilità di abbandono richiede risposte diverse a seconda del valore del cliente.
df['churn_risk'] = model.predict_proba(df[feature_cols].fillna(0))[:, 1]
df['risk_tier'] = pd.qcut(df['churn_risk'], 4, labels=['low','medium','high','critical'])
df['value_tier'] = pd.qcut(df['monetary_total'], 4, labels=['low','medium','high','vip'])
segments = df.groupby(['risk_tier', 'value_tier']).agg(
customers=('customer_id', 'count'),
avg_ltv=('monetary_total', 'mean')
).reset_index()
A ogni quadrante corrisponde un’azione, un canale e un budget stimato.
| Rischio per Valore | Segment | Action | Channel | Estimated budget |
|---|---|---|---|---|
| Critical per VIP | Save our stars | Personal customer success call within 24h | Phone, dedicated email | 50-100 euro per cliente |
| Critical per Low | Auto-save | Automated email sequence with offer | Email, push | 5-10 euro per cliente |
| High per VIP | Proactive love | Exclusive invitation, sneak peek of new collections | Email, direct mail | 20-30 euro per cliente |
| Low per VIP | Loyalty reward | Referral program, early access to sales | Email, in-app | 10-15 euro per cliente |
| Low-Medium per Low | Nurture | Educational content, social proof | Email, in-app | 2-5 euro per cliente |
Verdetto: il quadrante a rischio critico e valore VIP riceve contatto umano entro 24 ore, tutto il resto va in automazione pesata per costo.
Fase 4: deploy e monitoring in settimana 4
Il modello vive solo se entra in un flusso quotidiano e se qualcuno si accorge quando smette di funzionare. La pipeline assegna gli score ogni mattina. Un controllo mensile verifica che l’AUC non scenda sotto la soglia di degrado.
# Pipeline di scoring giornaliera
def daily_churn_scoring():
new_data = extract_features_from_warehouse()
new_data['churn_risk'] = model.predict_proba(new_data[feature_cols])[:, 1]
write_to_activation_table(new_data[['customer_id', 'churn_risk', 'risk_tier']])
schedule.every().day.at("06:00").do(daily_churn_scoring)
# Monitoring AUC drift mensile
def check_auc_drift():
recent_actuals = get_recent_outcomes()
recent_preds = model.predict_proba(recent_actuals[feature_cols])[:, 1]
current_auc = roc_auc_score(recent_actuals['churned'], recent_preds)
if current_auc < 0.78: # soglia di degrado
send_alert(f"AUC drift detected: {current_auc:.3f}")
The table mrt_activation__customer_health viene sincronizzata via Reverse ETL su Braze e Salesforce. I customer success manager vedono la lista Save our stars direttamente nel CRM.
Fase 5: presentazione al decisore in 2 slide
La prima slide dimostra che il modello funziona. L’AUC di 0.84 significa che nell’84 per cento dei casi una coppia churner e non-churner viene ordinata correttamente. Prendendo il 20 per cento più a rischio catturiamo il 64 per cento dei churner reali. Nel segmento Critical la precision è del 71 per cento: chi predici come critical churna davvero nel 71 per cento dei casi.
La seconda slide traduce tutto in valore. La popolazione Critical per VIP conta 1800 clienti con LTV medio di 2400 euro. Salvandone il 40 per cento con intervento proattivo si preservano 720 clienti per 2400 euro, cioè 1.73 milioni di LTV. Il costo del programma è di 180000 euro all’anno, tra un FTE di customer success e i tool, per un ROI di 9.6x nel primo anno.
Quality control
Prima di usare questo lavoro in una decisione, controlla sempre completezza, duplicati, timezone, definizioni cambiate e segmenti esclusi. Molte analisi apparentemente sofisticate falliscono perché il dato di partenza misura un comportamento diverso da quello che il team crede di osservare. Chiediti infine: se il risultato fosse instabile, quale scelta sbaglieresti? Se la risposta non è concreta, manca ancora il collegamento tra analisi e azione.
Un caso che fa riflettere: Netflix
Nel primo trimestre 2022 Netflix perse 200000 abbonati a livello globale, il primo calo netto dopo oltre un decennio di crescita continua. La società attribuì il calo a saturazione, concorrenza e condivisione delle password fuori nucleo. La risposta fu operativa: stretta sulla condivisione account e lancio del piano con pubblicità. Il caso mostra un progetto retention end-to-end: segnale di churn, segmentazione per valore e azione commerciale misurabile.
Domande per l’autoverifica
- Quale soglia di rischio attiva il contatto umano entro 24 ore?
- Quale feature pesa di più tra recency, trend di utilizzo e tenure?
- Quale degrado di AUC fa scattare l’allarme di retraining?
- Quale ROI in euro giustifica il programma sul segmento a valore VIP?
Bloccato su questo argomento o vuoi applicarlo al tuo caso? Prenota una call di 15 minuti con un analista esperto.
Related Path
Lessons to read together
Questi collegamenti portano la lezione dentro il resto del corso: basi da riprendere, passaggi successivi e connessioni tematiche tra moduli.