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

Cover, immagine di Autore

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.

from src.utils import *from src.dgp import dgp_giftdgp = dgp_gift(n=100_000)df = dgp.generate_data()df.head()
Istantanea dei dati, immagine di Autore

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.

DAG del processo generatore dei dati, immagine di Author

È 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.

Effetto medio del trattamento, immagine di Author

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]
Tabella di regressione del churn, immagine di Author

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]
Tabella di regressione del fatturato, immagine di Author

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.

Formula del profitto, immagine dell'Autore

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

Formula di incremento dei profitti, immagine dell'autore

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='--');
Effetto complessivo in base alla soglia di disiscrizione, immagine dell'Autore

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='--');
Effetto aggregato per soglia di reddito, immagine di Autore

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');
Comparing policies, image by Author

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:

  1. Le entrate e la probabilità di churn sono correlate in modo negativo
  2. L’effetto del regalo sul churn (o sulle entrate) non era fortemente correlato in modo negativo (o positivo per le entrate) 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.

Decomposizione dell'aumento del profitto, immagine dell'autore

Ciò implica che ci sono tre canali che rendono redditizio trattare un cliente.

  1. Se è un cliente ad alto fatturato e il trattamento diminuisce la sua probabilità di churn
  2. Se è un cliente non che abbandona il servizio e il trattamento aumenta il suo fatturato
  3. 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”
  • 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!