Oltre la previsione del churn e l’aumento del churn
Churn prediction and churn increase
SCIENZA DEI DATI CAUSALE
Come indirizzare al meglio le politiche in presenza di churn

Una delle attività più comuni nella scienza dei dati è la previsione del churn. Tuttavia, prevedere il churn è spesso solo un passo intermedio e raramente l’obiettivo finale. Di solito, ciò che ci interessa realmente è ridurre il churn, che è un obiettivo separato, non necessariamente correlato. Infatti, ad esempio, sapere che i clienti a lungo termine sono meno propensi a churn rispetto ai nuovi clienti non è un’informazione utile poiché non possiamo aumentare la durata dei clienti. Ciò che vorremmo sapere, invece, è come un (o più) trattamento influisce sul churn. Questo è spesso indicato come incremento del churn.
In questo articolo, andremo oltre sia la previsione del churn che l’incremento del churn e considereremo invece l’obiettivo finale delle campagne di prevenzione del churn: aumentare il ricavo. Prima di tutto, una politica che riduce il churn potrebbe anche avere un impatto sul ricavo, il quale dovrebbe essere preso in considerazione. Tuttavia, e ancora più importante, l’aumento del ricavo è rilevante solo se il cliente non churna. Viceversa, la diminuzione del churn è più rilevante per i clienti ad alto ricavo. Questa interazione tra churn e ricavo è fondamentale per comprendere la redditività di qualsiasi campagna di trattamento e non dovrebbe essere trascurata.
Regali e Abbonamenti
Per il resto dell’articolo, useremo un esempio giocattolo per illustrare l’idea principale. Supponiamo di essere un’azienda interessata a ridurre il churn dei nostri clienti e, in definitiva, aumentare il nostro ricavo. Supponiamo di aver deciso di testare una nuova idea: inviare un regalo di 1$ ai nostri utenti. Al fine di testare se il trattamento funziona, lo abbiamo inviato casualmente solo a un sottoinsieme della nostra base clienti.
costo = 1
Diamo un’occhiata ai dati che abbiamo a disposizione. Importo il processo di generazione dei dati dgp_gift()
da src.dgp
. Importo anche alcune funzioni di tracciamento e librerie da src.utils
.
- Esplicabilità dei modelli di apprendimento automatico aumentare la fiducia e la comprensione nei sistemi di intelligenza artificiale
- 5 Modi per Creare una Cultura Responsabile ed Efficace Basata sull’IA nella Tua Organizzazione
- Primi incontri annunciati per ODSC APAC 2023
from src.utils import *from src.dgp import dgp_giftdgp = dgp_gift(n=100_000)df = dgp.generate_data()df.head()

Abbiamo informazioni su 100_000
clienti per i quali osserviamo il numero di mesi
in cui sono stati clienti attivi, il ricavo generato nell’ultimo mese (rev_old
), il cambiamento di ricavo tra l’ultimo mese e quello precedente (rev_change
), se sono stati inviati casualmente un regalo
e i due risultati di interesse: churn
, ovvero se non sono più clienti attivi, e il ricavo
generato nel mese corrente. Indichiamo i risultati con la lettera Y, il trattamento con la lettera W e le altre variabili con la lettera X.
Y = ['churn', 'ricavo']W = 'regalo'X = ['mesi', 'rev_old', 'rev_change']
Nota che, per semplicità, consideriamo uno snapshot a singolo periodo dei dati e riassumiamo la struttura a pannelli dei dati in un paio di variabili. Di solito, avremmo una serie temporale più lunga ma anche un orizzonte temporale più lungo per quanto riguarda l’outcome (ad es. valore a vita del cliente).
Possiamo rappresentare il processo di generazione dei dati sottostante con il seguente Grafo Aciclico Diretto (DAG). I nodi rappresentano le variabili e le frecce rappresentano le potenziali relazioni causali. Ho evidenziato in verde le due relazioni di interesse: l’effetto del regalo
su churn
e ricavo
. Nota che churn
è correlato al ricavo poiché i clienti churnati, per definizione, generano un ricavo pari a zero.

È importante notare che il fatturato passato e la variazione del fatturato sono predittori di churn
e fatturato
, ma non sono correlati alla nostra azione. Al contrario, l’azione influenza churn
e fatturato
in modo differenziale a seconda del numero totale di mesi
attivi del cliente.
Pur essendo semplice, questo processo generatore di dati mira a catturare una considerazione importante: le variabili che sono buoni predittori di churn
o fatturato
non sono necessariamente variabili che predicono l’aumento di churn
o fatturato
. Vedremo in seguito come ciò influisce sulla nostra analisi.
Iniziamo esplorando i dati.
Analisi esplorativa dei dati
Iniziamo con il churn
. Quanti clienti ha perso l’azienda il mese scorso?
df.churn.mean()
0.19767
L’azienda ha perso quasi il 20% dei suoi clienti il mese scorso! Il regalo
ha aiutato a prevenire il churn?
Vogliamo confrontare la frequenza di churn dei clienti che hanno ricevuto il regalo con la frequenza di churn dei clienti che non hanno ricevuto il regalo. Poiché il regalo è stato assegnato in modo casuale, l’estimatore della differenza tra le medie è un estimatore non distorto per l’effetto medio del trattamento (ATE) del regalo
sul churn
.

Calcoliamo la stima della differenza tra le medie tramite la regressione lineare. Includiamo anche altre covariate per migliorare l’efficienza dell’estimatore.
smf.ols("churn ~ " + W + " + " + " + ".join(X), data=df).fit().summary().tables[1]

Sembra che il regalo
abbia ridotto il churn di circa 11 punti percentuali, cioè quasi un terzo del livello di base del 32%! Ha avuto anche un impatto sul fatturato
?
Per quanto riguarda il churn, facciamo una regressione del fatturato
sul regalo
, la nostra variabile di trattamento, per stimare l’effetto medio del trattamento.
smf.ols("revenue ~ " + W + " + " + " + ".join(X), data=df).fit().summary().tables[1]

Sembra che il regalo
abbia aumentato il fatturato in media di 0,63$, il che significa che non è stato redditizio. Significa che dovremmo smettere di inviare regali ai nostri clienti? Dipende. In realtà, il regalo potrebbe essere efficace per determinati segmenti di clienti. Dobbiamo solo identificarli.
Politiche di targeting
In questa sezione, cercheremo di capire se c’è un modo redditizio basato sui dati per inviare il regalo
, mirando a clienti specifici. In particolare, confronteremo diverse politiche di targeting con l’obiettivo di aumentare il fatturato.
In questa sezione, avremo bisogno di alcuni algoritmi per prevedere il fatturato
, o il churn
, o la probabilità di ricevere il regalo
. Utilizzeremo modelli di alberi ad aumento di gradiente della libreria lightgbm
. Utilizzeremo gli stessi modelli per tutte le politiche in modo da non poter attribuire le differenze nelle prestazioni all’accuratezza delle previsioni.
from lightgbm import LGBMClassifier, LGBMRegressor
Per valutare ogni politica indicata con τ, confrontiamo i suoi profitti con la politica Π⁽¹⁾, con i suoi profitti senza la politica Π⁽⁰⁾, per ogni singolo individuo, in un dataset di validazione separato. Si noti che questo di solito non è possibile poiché, per ogni cliente, osserviamo solo uno dei due risultati potenziali, con o senza il regalo
. Tuttavia, poiché stiamo lavorando con dati sintetici, possiamo fare una valutazione oracolare. Se desideri saperne di più su come valutare i modelli di potenziamento con dati reali, ti consiglio il mio articolo introduttivo.
Valutazione dei modelli di potenziamento
Una delle applicazioni più diffuse di inferenza causale nell’industria è il modello di potenziamento, comunemente noto come stima…
towardsdatascience.com
Prima di tutto, definiamo i profitti Π come il ricavo netto R quando il cliente non si disiscrive C.

Pertanto, l’effetto complessivo sui profitti per gli individui trattati è dato dalla differenza tra i profitti quando trattati Π⁽¹⁾ meno i profitti quando non trattati Π⁽⁰⁾.

L’effetto per gli individui non trattati è zero.
def valuta_politica(politica): dati = dgp.genera_dati(seed_dati=4, seed_assegnazione=5, keep_po=True) dati['profitti'] = (1 - dati.churn) * dati.ricavo base = (1-dati.churn_c) * dati.ricavo_c effetto = politica(dati) * (1-dati.churn_t) * (dati.ricavo_t-spese) + (1-politica(dati)) * (1-dati.churn_c) * dati.ricavo_c return np.sum(effetto - base)
1. Obiettivo: Clienti che si disiscrivono
Una prima politica potrebbe essere quella di mirare solo ai clienti che si disiscrivono. Supponiamo di inviare il regalo
solo ai clienti con una probabilità di disiscrizione prevista superiore alla media.
model_disiscrizione = LGBMClassifier().fit(X=df[X], y=df['disiscrizione'])politica_disiscrizione = lambda df : (model_disiscrizione.predict_proba(df[X])[:,1] > df.disiscrizione.mean())valuta_politica(politica_disiscrizione)
-5497.46
La politica non è redditizia e porterebbe a una perdita complessiva di oltre 5000$.
Potresti pensare che il problema sia la soglia arbitraria, ma non è questo il caso. Di seguito riporto il grafico dell’effetto complessivo per tutte le possibili soglie di politica.
x = np.linspace(0, 1, 100)y = [valuta_politica(lambda df : (model_disiscrizione.predict_proba(df[X])[:,1] > p)) for p in x]fig, ax = plt.subplots(figsize=(10, 3))sns.lineplot(x=x, y=y).set(xlabel='Soglia di politica per la disiscrizione', title='Effetto complessivo');ax.axhline(y=0, c='k', lw=3, ls='--');

Come possiamo vedere, non importa quale soglia viene utilizzata, è praticamente impossibile ottenere un profitto.
Il problema è che il fatto che un cliente è probabile che si disiscriva non implica che il regalo
avrà un impatto sulla sua probabilità di disiscrizione. Le due misure non sono completamente indipendenti (ad esempio, non possiamo ridurre la probabilità di disiscrizione dei clienti che hanno una probabilità di disiscrizione del 0%), ma non sono la stessa cosa.
2. Clienti obiettivo con alto reddito
Proviamo ora una politica diversa: inviamo il regalo solo ai clienti ad alto reddito. Ad esempio, potremmo inviare il regalo solo ai primi 10% dei clienti per reddito. L’idea è che se la politica effettivamente diminuisce il churn, questi sono i clienti per i quali diminuire il churn è più redditizio.
modello_reddito = LGBMRegressor().fit(X=df[X], y=df['reddito'])politica_reddito = lambda df : (modello_reddito.predict(df[X]) > np.quantile(df.reddito, 0.9))valuta_politica(policies_reddito)
-4730.82
Anche questa politica non è redditizia, portando a perdite sostanziali. Come prima, questo non è un problema di selezione della soglia, come possiamo vedere nel grafico qui sotto. Il meglio che possiamo fare è impostare una soglia così alta da non trattare nessuno e ottenere profitti pari a zero.
x = np.linspace(0, 100, 100)y = [valuta_politica(lambda df : (modello_reddito.predict(df[X]) > c)) for c in x]fig, ax = plt.subplots(figsize=(10, 3))sns.lineplot(x=x, y=y).set(xlabel='Soglia Politica Reddito', title='Effetto aggregato');ax.axhline(y=0, c='k', lw=3, ls='--');

Il problema è che, nel nostro contesto, la probabilità di churn dei clienti ad alto reddito non diminuisce abbastanza per rendere il regalo
redditizio. Questo è anche in parte dovuto al fatto, spesso osservato nella realtà, che i clienti ad alto reddito sono anche quelli meno propensi a churnare inizialmente.
Consideriamo ora un insieme di politiche più rilevanti: politiche basate sull’uplift.
3. Clienti obiettivo con uplift di churn
Un approccio più sensato sarebbe quello di mirare ai clienti la cui probabilità di churn
diminuisce di più quando ricevono il regalo
da 1$. Stimiamo l’uplift di churn utilizzando il doppio stima robusto, uno dei modelli di uplift con migliori performance. Se non sei familiare con i meta-apprendisti, ti consiglio di iniziare dal mio articolo introduttivo.
Comprensione dei Meta-Apprendisti
In molti contesti, non siamo solo interessati a stimare un effetto causale, ma anche a capire se questo effetto è…
towardsdatascience.com
Importiamo il meta-apprendista robusto da econml, una libreria di Microsoft.
from econml.dr import DRLearnerDR_learner_churn = DRLearner(model_regression=LGBMRegressor(), model_propensity=LGBMClassifier(), model_final=LGBMRegressor())DR_learner_churn.fit(df['churn'], df[W], X=df[X]);
Ora che abbiamo stimato l’uplift di churn, potremmo essere tentati di mirare solo ai clienti con un uplift negativo elevato (negativo, poiché vogliamo diminuire il churn). Ad esempio, potremmo inviare il regalo
a tutti i clienti con un uplift stimato maggiore della media di churn.
policy_churn_lift = lambda df : DR_learner_churn.effect(df[X]) < - np.mean(df.churn)evaluate_policy(policy_churn_lift)
-3925.24
Anche questa politica non è redditizia, portando a perdite di quasi 4000$.
Il problema è che non abbiamo considerato il costo della politica. Infatti, diminuire la probabilità di churn è redditizio solo per i clienti ad alto reddito. Prendiamo il caso estremo: evitare il churn di un cliente che non genera alcun reddito non vale nessuna intervento.
Quindi, inviamo il regalo
solo ai clienti la cui probabilità di churn, ponderata per il reddito, diminuisce più del costo del regalo.
model_revenue_1 = LGBMRegressor().fit(X=df.loc[df[W] == 1, X], y=df.loc[df[W] == 1, 'reddito'])policy_churn_lift = lambda df : - DR_learner_churn.effect(df[X]) * model_revenue_1.predict(df[X]) > costevaluate_policy(policy_churn_lift)
318.03
Questa politica è finalmente redditizia!
Tuttavia, non abbiamo ancora considerato un canale: l’intervento potrebbe influire anche sulle entrate dei clienti esistenti.
4. Aumento delle entrate target dei clienti
Un approccio simmetrico a quello precedente sarebbe considerare solo l’impatto sulle entrate
, ignorando l’impatto sul churn. Potremmo stimare l’aumento delle entrate
per i clienti che non churnano e considerare solo i clienti il cui effetto incrementale sulle entrate, al netto del churn, è maggiore del costo del regalo
.
DR_learner_netrevenue = DRLearner(model_regression=LGBMRegressor(), model_propensity=LGBMClassifier(), model_final=LGBMRegressor())DR_learner_netrevenue.fit(df.loc[df.churn==0, 'revenue'], df.loc[df.churn==0, W], X=df.loc[df.churn==0, X]);model_churn_1 = LGBMClassifier().fit(X=df.loc[df[W] == 1, X], y=df.loc[df[W] == 1, 'churn'])policy_netrevenue_lift = lambda df : DR_learner_netrevenue.effect(df[X]) * (1-model_churn_1.predict(df[X])) > costevaluate_policy(policy_netrevenue_lift)
50.80
Anche questa politica è redditizia ma ignora l’effetto sul churn. Come possiamo combinare questa politica con quella precedente?
5. Aumento delle entrate target dei clienti
Il modo migliore per combinare efficientemente l’effetto sul churn e l’effetto sulle entrate nette è semplicemente stimare l’aumento totale delle entrate. La politica ottimale implicata è trattare i clienti il cui aumento totale delle entrate è maggiore del costo del regalo
.
DR_learner_revenue = DRLearner(model_regression=LGBMRegressor(), model_propensity=LGBMClassifier(), model_final=LGBMRegressor())DR_learner_revenue.fit(df['revenue'], df[W], X=df[X]);policy_revenue_lift = lambda df : (DR_learner_revenue.effect(df[X]) > cost)evaluate_policy(policy_revenue_lift)
2028.21
Sembra che questa sia di gran lunga la migliore politica, generando un profitto aggregato di oltre 2000$!
Il risultato è sorprendente se confrontiamo tutte le diverse politiche.
policies = [policy_churn, policy_revenue, policy_churn_lift, policy_netrevenue_lift, policy_revenue_lift] df_results = pd.DataFrame()df_results['policy'] = ['churn', 'revenue', 'churn_L', 'netrevenue_L', 'revenue_L']df_results['value'] = [evaluate_policy(policy) for policy in policies]fig, ax = plt.subplots()sns.barplot(df_results, x='policy', y='value').set(title='Overall Incremental Effect')plt.axhline(0, c='k');

Intuizione e scomposizione
Se confrontiamo le diverse politiche, è chiaro che puntare direttamente sui clienti ad alto valore di entrate o ad alta probabilità di churn sono le scelte peggiori. Questo non è necessariamente sempre il caso, ma è accaduto nei nostri dati simulati a causa di due fatti che sono anche comuni in molti scenari reali:
- Le entrate e la probabilità di churn sono correlate in modo negativo
- L’effetto del
regalo
sulchurn
(o sulleentrate
) non era fortemente correlato in modo negativo (o positivo per leentrate
) con i valori di base
Uno qualsiasi di questi due fatti può essere sufficiente per rendere una cattiva strategia puntare sulle entrate o sul churn. Ciò su cui bisognerebbe invece concentrarsi sono i clienti con un alto effetto incrementale. Ed è meglio utilizzare direttamente come risultato la variabile di interesse, le entrate
in questo caso, quando disponibile.
Per capire meglio il meccanismo, possiamo scomporre l’effetto aggregato di una politica sui profitti in tre parti.

Ciò implica che ci sono tre canali che rendono redditizio trattare un cliente.
- Se è un cliente ad alto fatturato e il trattamento diminuisce la sua probabilità di churn
- Se è un cliente non che abbandona il servizio e il trattamento aumenta il suo fatturato
- Se il trattamento ha un forte impatto sia sul suo fatturato che sulla probabilità di churn
Il targeting basato sull’aumento del churn sfrutta solo il primo canale, il targeting basato sull’aumento del fatturato netto sfrutta solo il secondo canale e il targeting basato sull’aumento del fatturato totale sfrutta tutti e tre i canali, rendendolo il metodo più efficace.
Bonus: ponderazione
Come evidenziato da Lemmens, Gupta (2020), a volte potrebbe valere la pena ponderare le osservazioni durante la stima dell’aumento del modello. In particolare, potrebbe valere la pena dare più peso alle osservazioni vicine alla soglia della politica di trattamento.
L’idea è che la ponderazione generalmente diminuisce l’efficienza dell’estimatore. Tuttavia, non ci interessa avere stime corrette per tutte le osservazioni, ma ci interessa piuttosto stimare correttamente la soglia della politica. Infatti, che si stimi un profitto netto di 1$ o di 1000$ non importa: la politica implicata è la stessa: inviare il regalo
. Tuttavia, stimare un profitto netto di 1$ invece di -1$ inverte le implicazioni della politica. Pertanto, una grande perdita di precisione lontano dalla soglia talvolta vale un piccolo guadagno di precisione alla soglia.
Proviamo a utilizzare pesi esponenziali negativi, decrescenti in base alla distanza dalla soglia.
DR_learner_revenue_w = DRLearner(model_regression=LGBMRegressor(), model_propensity=LGBMClassifier(), model_final=LGBMRegressor())w = np.exp(1 + np.abs(DR_learner_revenue.effect(df[X]) - cost))DR_learner_revenue_w.fit(df['revenue'], df[W], X=df[X], sample_weight=w);policy_revenue_lift_w = lambda df : (DR_learner_revenue_w.effect(df[X]) > cost)evaluate_policy(policy_revenue_lift_w)
1398.19
Nel nostro caso, la ponderazione non vale la pena: la politica implicita è ancora redditizia ma meno di quella ottenuta con il modello non ponderato, 2028$.
Conclusione
In questo articolo, abbiamo visto perché e come si dovrebbe andare oltre la previsione del churn e la modellazione dell’aumento del churn. In particolare, si dovrebbe concentrare sull’obiettivo aziendale finale di aumentare la redditività. Ciò implica spostare l’attenzione dalla previsione all’aumento ma anche combinare il churn e il fatturato in un unico risultato.
Una nota importante riguarda la dimensione dei dati disponibili. Abbiamo utilizzato un dataset di esempio che semplifica molto il problema almeno in due dimensioni. Innanzitutto, all’indietro, normalmente disponiamo di serie storiche più lunghe che possono (e dovrebbero) essere utilizzate sia per scopi di previsione che di modellazione. In secondo luogo, in avanti, si dovrebbe combinare il churn con una stima a lungo termine della redditività del cliente, di solito indicata come valore a vita del cliente.
Riferimenti
- Kennedy (2022), “Towards Optimal Doubly Robust Estimation of Heterogeneous Causal Effects”
- Bonvini, Kennedy, Keele (2021), “Minimax Optimal Subgroup Identification”
- Lemmens, Gupta (2020), “Managing Churn to Maximize Profits”
Articoli correlati
- Valutazione dei modelli di aumento
- Comprensione dei meta-learner
- Comprensione di AIPW, l’estimatore doppiamente robusto
Codice
Puoi trovare il Jupyter Notebook originale qui:
Blog-Posts/notebooks/beyond_churn.ipynb at main · matteocourthoud/Blog-Posts
Codice e notebook per i miei articoli sul blog VoAGI. Contribuisci allo sviluppo di matteocourthoud/Blog-Posts creando un…
github.com
Grazie per la lettura!
Lo apprezzo davvero! 🤗 Se ti è piaciuto l’articolo e vuoi vedere altro, considera di seguirmi. Pubblico una volta alla settimana su argomenti legati all’inferenza causale e all’analisi dei dati. Cerco di mantenere i miei articoli semplici ma precisi, fornendo sempre codice, esempi e simulazioni.
Inoltre, una piccola disclaimer: scrivo per imparare, quindi gli errori sono la norma, anche se faccio del mio meglio. Per favore, quando li individui, fammelo sapere. Apprezzo anche suggerimenti su nuovi argomenti!