Um dos assuntos mais recorrentes em qualquer tipo de serviço de assinatura é como reduzir o Churn, dado que conquistar novos clientes é bem mais difícil (e caro) do que manter os antigos.
Cerca de 70% das empresas sabem que é mais barato manter um cliente do que ter que ir atrás de um novo.
Fazendo uma analogia simples, os serviços de assinatura são como uma espécie de sangue na corrente sanguínea de uma empresa e uma interrupção de qualquer natureza prejudica todo o negócio, dado que esse é um modelo de negócio que se baseia na recorrência de tarifação e não no desenvolvimento, ou mesmo venda de outros produtos.
Modelos de negócios baseados no volume de pessoas que estão dispostas a terem uma cobrança recorrente o negócio fica bem mais complicado, dado que diferentemente de produtos que tem uma eslasticidade maior o fluxo de receita é extremamente sujeito aos sabores do mercado e dos clientes.
Dentro desse cenário, todas as empresas que tem o seu fluxo de receita baseado nesse tipo de business, saber quando um cliente entrará em uma situação de saída através do cancelamento do serviço (Churn) é fundamental para criar mecanismos de retenção mais efetivos, ou mesmo criação de réguas de contato com os clientes para evitar ou minimizar a chance de um cliente sair da base de dados.
Sendo assim, qualquer mecanismo ou mesmo esforço para minimizar esse efeito é de grande valia. Nos baseamos na teoria estatística buscar respostas para a pergunta: Como diminuir o Churn? Como identificar um potencial cliente que irá entrar em uma situação de Churn? Quais estratégias seguir para minimizar esse Churn? Quais réguas de comunicação com os clientes devemos ter para entender os motivos que estão fazendo um assinante cancelar o serviço e quais são as estratégias de customer winback possíveis nesse cenário?
E pra responder essa pergunta, fomos buscar as respostas na análise de sobrevivência dado que essa área da estatística é uma das que lidam melhor em termos de probabilidade de tempo de vida, seja de materiais (e.g. tempo de falha de algum sistema mecânico), no tempo de vida de pessoas propriamente ditas (e.g. dado uma determinada posologia qual é a estimativa de um paciente sobreviver a um câncer), e no nosso caso quanto tempo de vida um assinante tem até deixar cancelar a sua assinatura.
A análise de sobreviência é uma técnica estatístisca que foi desenvolvida na medicina e tem como principal finalidade estimar o tempo de sobrevivência ou tempo de morte de um determinado paciente dentro de um horizonte do tempo.
O estimador de Kaplan-Meier (1958) utiliza uma função de sobrevivência que leva em consideração uma divisão entre o número de observações que não falharam no tempo t pelo número total de observações no estudo em que cada intervalo de tempo tem-se o número de falhas/mortes/churn distintos bem como é calculado o risco de acordo com o número de individuos restantes no tempo subsequente.
Já o estimador Nelson-Aalen (1978) é um estimador que tem as mesmas características do Kaplan-Meier, com a dferença que esse estimador trabalha com uma função de sobrevivência que é a cumulative hazard rate function.
Os elementos fundamentais para caracterização de um estudo que envolve análise de sobrevivência são, o tempo inicial, escala de medida do intervalo de tempo e se o evento de churn ocorreu.
Os principais artigos são de Aalen (1978), Kaplan-Meier (1958) e Cox (1972).
Esse post não tem como principal objetivo dar algum tipo de introdução à survival analysis, dado que tem muitas referências na internet sobre o assunto e não há nada a ser acrescentado nesse sentido.
Como a análise de cohort, a análise de sobrevivência tem como principal característica ser um estudo de natureza logituginal, isto é, os seus resultados tem uma caracterisca de temporalidade seja em aspectos de retrospecção, quanto em termos de perspectivas, isso é, tem uma resposta tipicamente temporal para um detemrminado evento de interesse.
No caso o que vamos usar como forma de comparação amostral é o comportamento longituginal de acordo com determinadas caracteristicas de amostragens diferentes ao longo do tempo, e os fatores que influenciam no churn.
As diferenciações estão claras através das covariáveis, mas que devido a questões de NDA não vamos postar aqui, mas para fins de exemplificação e comparação vamos considerar que todas as covariaveis são semelhantes.
Podemos dizer que a análise de sobrevivência aplicada em um caso de telecom, pode ajudar ter uma estimativa em forma de probabilidade em relação ao tempo em que uma assinatura vai durar até o evento de churn (cancelamento) e dessa forma elaborar estratégias para evitar esse evento, dado que adquirir um novo cliente é mais caro do que manter um novo e entra totalmente dentro de uma estratégia de Customer Winback
No nosso caso o tempo de falha ou tempo de morte, como estamos falando de serviços de assinaturas, o nosso evento de interesse seria o churn, ou cancelamento da assinatura. Em outras palavras teríamos algo do tipo Time-to-Churn. Guardem esse termo.
Usamos dados de dois produtos antigos em que os dados foram anonimizados e aplicados um hash de embaralhamento uniforme (que obedece uma distribuição específica) nos atributos por questões de privacidade, que são:
Eliminamos o efeito de censura à esquerda eliminando casos de reativações, dado que queriamos entender a jornada do assinante como um todo sem nenhum tipo de viés relativo a questões de customer winback. Em relação à censura à direita temos que já se passaram alguns meses desde que essa base de dados foi extraída.
Um aspecto técnico importante a ser considerado é que esses dois produtos estão em categorias de comparabilidade, dado que sem isso nenhum tipo de caractericação seria nula.
No fim dessa implementação teremos uma tabela de vida de cada um desses produtos.
Primeiramente vamos importar as bibilotecas: Pandas (para manipulação de dados), matplotlib (para a geração de gráficos), e lifelines para aplicação da análise de sobrevivência
In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import lifelines
Após realizar a importação das bibliotecas, vamos ajustar o tamanho das imagens para uma melhor visualização.
In [2]:
%pylab inline
pylab.rcParams['figure.figsize'] = (14, 9)
Vamos realizar o upload da nossa base de dados criando um objecto chamado df e usando a classe read_csv do pandas.
In [3]:
df = pd.read_csv('https://raw.githubusercontent.com/fclesio/learning-space/master/Datasets/07%20-%20Survival/survival_data.csv')
Vamos checar a nossa base de dados
In [4]:
df.head()
Out[4]:
Então como podemos ver temos as 7 variáveis na nossa base de dados.
Na sequência vamos importar a biblioteca do lifeniles, em especial o estimador de KaplanMaier
In [5]:
from lifelines import KaplanMeierFitter
kmf = KaplanMeierFitter()
Após realizar a importação da classe relativa ao estimador de Kaplan Meier no objeto kmf, vamos atribuir as nossas variáveis de tempo (T) e evento de interesse (C)
In [6]:
T = df["t"]
C = df["c"]
O que foi feito anteriormente é que buscamos no dataframe df o array t e atribuímos no objeto T, e buscamos o array da coluna c no dataframe e atribuímos no objeto C.
Agora vamos chamar o método fit usando esses dois objetos no snippet abaixo.
In [7]:
kmf.fit(T, event_observed=C )
Out[7]:
Objeto ajustado, vamos agora ver o gráfico relativo a esse objeto usando o estimador de Kaplan Meier.
In [8]:
kmf.survival_function_.plot()
plt.title('Survival function of Service Valued Add Products');
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan of the subscription (in days)')
Out[8]:
Como podemos ver no gráfico, temos algumas observações pertinentes, quando tratamos a probabilidade de sobreviência desses dois produtos no agregado que são:
No entando, vamos plotar a mesma função de sobreviência considerando os intervalos de confiança estatística.
In [9]:
kmf.plot()
plt.title('Survival function of Service Valued Add Products - Confidence Interval in 85-95%');
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan of the subscription')
Out[9]:
Contudo nesse modelo inicial temos duas limitações claras que são:
Para isso, vamos começar a entrar no detalhe em relação a cada uma das dimensões e ver o que cada uma tem de influência em relação à função de sobrevivência.
Vamos começar realizando a quebra pela dimensão que determina se o cliente entrou via gratruídade ou não (free_user).
In [10]:
ax = plt.subplot(111)
free = (df["free_user"] == 1)
kmf.fit(T[free], event_observed=C[free], label="Free Users")
kmf.plot(ax=ax, ci_force_lines=True)
kmf.fit(T[~free], event_observed=C[~free], label="Non-Free Users")
kmf.plot(ax=ax, ci_force_lines=True)
plt.ylim(0,1);
plt.title("Lifespans of different subscription types");
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan')
Out[10]:
Este gráfico apresenta algumas informações importantes para os primeiros insights em relação a cada uma das curvas de sobreviência em relação ao tipo de gratuídade oferecida como fator de influência para o churn que são:
Dado essa análise incial das curvas de sobreviência, vamos avaliar agora as probabilidades de sobrevivência de acordo com o produto.
In [11]:
ax = plt.subplot(111)
product = (df["product"] == "A")
kmf.fit(T[product], event_observed=C[product], label="Product A")
kmf.plot(ax=ax, ci_force_lines=True)
kmf.fit(T[~product], event_observed=C[~product], label="Product B")
kmf.plot(ax=ax, ci_force_lines=True)
plt.ylim(0,1);
plt.title("Survival Curves of different Products");
plt.ylabel('Probability of Living (%)')
plt.xlabel('Lifespan')
Out[11]:
Este gráfico apresenta a primeira distinção entre os dois produtos de uma forma mais clara.
Mesmo com os intervalos de confiança com uma variação de 5%, podemos ver que o produto A (linha azul) tem uma maior probabilidade de sobrevivência com uma diferença percentual de mais de 15%; diferença essa amplificada depois do vigésimo dia.
Em outras palavras: Dado um determinada safra de usuários, caso o usuário entre no produto A o mesmo tem uma probabilidade de retenção de cerca de 15% em relação a um usuário que por ventura entre no produto B, ou o produto A apresenta uma cauda de retenção superior ao produto B.
Empiricamente é sabido que um dos principais fatores de influência de produtos SVA são os canais de mídia os quais esses produtos são oferecidos.
O canal de mídia é o termômetro em que podemos saber se estamos oferencendo os nossos produtos para o público alvo correto.
No entanto para um melhor entendimento, vamos analisar os canais nos quais as assinaturas são originadas.
A priori vamos normalizar a variável channel para realizar a segmentação dos canais de acordo com o conjunto de dados.
In [12]:
df['channel'] = df['channel'].astype('category');
channels = df['channel'].unique()
Após normalização e transformação da variável para o tipo categórico, vamos ver como está o array.
In [13]:
channels
Out[13]:
Aqui temos a representação de 11 canais de mídia os quais os clientes entraram no serviço.
Com esses canais, vamos identificar a probabilidade de sobrevivência de acordo com o canal.
In [14]:
for i,channel_type in enumerate(channels):
ax = plt.subplot(3,4,i+1)
ix = df['channel'] == channel_type
kmf.fit( T[ix], C[ix], label=channel_type )
kmf.plot(ax=ax, legend=True)
plt.title(channel_type)
plt.xlim(0,40)
if i==0:
plt.ylabel('Probability of Survival by Channel (%)')
plt.tight_layout()
Fazendo uma análise sobre cada um desses gráficos temos algumas considerações sobre cada um dos canais:
Agora que já sabemos um pouco da dinâmica de cada canal, vamos criar uma tabela de vida para esses dados.
A tabela de vida nada mais é do que uma representação da função de sobrevivencia de forma tabular em relação aos dias de sobrevivência. (Melhorar essa parte).
Para isso vamos usar a biblioteca utils do lifelines para chegarmos nesse valor.
In [15]:
from lifelines.utils import survival_table_from_events
Biblioteca importada, vamos usar agora as nossas váriaveis T e C novamente para realizar o ajuste da tabela de vida.
In [16]:
lifetable = survival_table_from_events(T, C)
Tabela importada, vamos dar uma olhada no conjunto de dados.
In [17]:
print (lifetable)
Diferentemente do R que possuí a tabela de vida com a porcentagem relativa à probabilidade de sobrevivência, nesse caso vamos ter que fazer um pequeno ajuste para obter a porcentagem de acordo com o atributo entrance e at_risk.
O ajuste se dará da seguinte forma:
In [18]:
survivaltable = lifetable.at_risk/np.amax(lifetable.entrance)
Ajustes efetuados, vamos ver como está a nossa tabela de vida.
In [19]:
survivaltable
Out[19]:
Vamos transformar a nossa tabela de vida em um objeto do pandas para melhor manipulação do conjunto de dados.
In [20]:
survtable = pd.DataFrame(survivaltable)
Para casos de atualização de Churn-at-Risk podemos definir uma função que já terá a tabela de vida e poderá fazer a atribuição da probabilidade de sobreviência de acordo com os dias de sobreviência.
Para isso vamos fazer uma função simples usando o próprio python.
In [21]:
def survival_probability( int ):
survtable["at_risk"].iloc[int]
print ("The probability of Survival after", int, "days is", survtable["at_risk"].iloc[int]*100, "%")
return;
Nesse caso vamos ver a chance de sobreviência usando o nosso modelo Kaplan-Meier já ajustado para uma assinatura que tenha 22 dias de vida.
In [22]:
survival_probability(22)
Ou seja, essa assinatura tem apenas 11.95% de probabilidade de estar ativa, o que significa que em algum momento muito próximo ela pode vir a ser cancelada.
Como podemos ver acima, usando análise de sobrevivência podemos tirar insights interessantes em relação ao nosso conjunto de dados, em especial para descobrirmos a duração das assinaturas em nossa base de dados, e estimar um tempo até o evento de churn.
Os dados utilizados refletem o comportamento de dois produtos reais, porém, que foram anonimizados por questões óbvias de NDA. Contudo nada impede a utilização e a adaptação desse código para outros experimentos. Um ponto importante em relação a essa base de dados é que como pode ser observado temos uma censura à direita muito acentuada o que limita um pouco a visão dos dados a longo prazo, principalmente se houver algum tipo de cauda longa no evento de churn.
Como coloquei no [São Paulo Big Data Meetup de Março] (http://www.slideshare.net/esportesocial/sp-big-data-meetup-march16) há uma série de arquiteturas que podem ser combinadas com esse tipo de análise, em especial métodos de Deep Learning que podem ser um endpoint de um pipeline de predição.
Espero que tenham gostado e quaisquer dúvidas mandem uma mensagem para flavioclesio at gmail.com
PS: Agradecimentos especiais aos meus colegas e revisores Eiti Kimura, Gabriel Franco e Fernanda Eleuterio.