Churn Rate Predict

Churn Rate Predict

06-Sep-2020    

Churn Rate

Se você oferece o mesmo serviço que seu concorrente, e ainda, por um preço melhor, por que o cliente não fica com o seu serviço?

Entender por que seus clientes abandonam o seu produto ou serviço é vital para conquistar um crescimento sustentável e lucrativo.

O Churn Rate pode lhe dar boas pistas sobre as escolhas dos clientes, mas afinal o que é isso???

Em uma tradução simples Churn Rate é a taxa de cancelamento, ou de abandono, registrada em sua base de clientes. por exemplo, para setores de serviço significa o cancelamento do serviço.

Churn

Embora tenha como principal função medir o percentual de clientes que abandonam um serviço, também serve para evidenciar o impacto negativo desses cancelamentos no caixa. Para alguns setores, esta é uma métrica básica para avaliar o sucesso do negócio, já que apresenta impacto direto no faturamento. Este é o caso dos serviços de assinatura.

É óbvio que a permanência ou não do cliente na empresa está relacionada a uma série de fatores. Mas a obrigação de todo gestor é partir do princípio de que o abandono foi causado por algum problema do seu lado do contrato.

Entender o Churn também pode auxiliar ao gestor identificar potenciais cancelamentos, com um tempo de antecedência, e promover ações direcionadas para tentar reter tais clientes. Ou seja, um alto valor para o churn rate é o que não desejamos.

Como o Churn tem um efeito negativo na receita de uma empresa, entender o que é esse indicador e como trabalhar para mitigar essa métrica é algo crítico para o sucesso de muitos negócios.

Esse estudo é uma provocação feita no curso Data Science na Prática onde fui desafiado a tentar explicar os passos e ferramentas aplicadas durante a evolução do material. Todo o material a ser desenvolvido no curso será centralizado no meu portfolio de projetos. Mais sobre o curso pode ser visto em: https://sigmoidal.ai.

1. O estudo

Nesse notebook elaborei um passo a passo detalhado para que seja possível replicar a análise dos dados disponível e recriar os modelos aqui demonstrados.

Foram utilizados diferentes modelos de Aprendizado de Máquinas. Primeiramente foi realizada verificação de equilíbrio e correções necessárias das variáveis e dados do dataset.

1.1 Suposições Inicias

Apesar de não haver informações explícitas disponíveis, os nomes das colunas nos permitem algumas suposições:

  • A variável alvo, que classifica se o cliente cancelou a assinatura é a Churn ;

  • A coluna CustomerID representa o chave única do cliente na base de dados e pode ser excluída, pois não interfere nossa análise;

  • A variável tenure está relacionada ao tempo que um cliente permanece com a assinatura do serviço. Em outras palavras, pode-se dizer que é um indicativo de fidelidade;

  • Apesar de não haver nenhuma documentação, assumo que a unidade de tempo utilizada é “mês”;

  • Podemos observar ainda outras informações do cadastro do cliente como outros serviços contratados, dados sobre a forma de pagamento, tipo de contrato, valores da última fatura e o total acumulado;

No notebook é possível se aprofundar nos estudos desses dados

Por exemplo: Os passos para plotarmos o gráfico de distribuição das cobranças totais:

Ou como assumi algumas classificações para dividirmos a variável Ternure

Então foi possível verificarmos a correlação entre as variáveis e a taxa de Churn :

2. Diferentes modelos de Machine Learning

2.1 Decision Tree

Primeiramente foi utilizado um modelo de Árvore de Decisões, pois não seria necessário realizar outras intervenções (padronizar e normalizar o dataset) e assim seria possível para obter um paramêtro para verificar em relação aos demais modelos.

#1. Escolha do modelo
from sklearn.ensemble import RandomForestClassifier

#2. Instanciar o modelo e optimizar hiper parâmetros
model_rf = RandomForestClassifier(max_depth=4, random_state=42)

#Separando os dados em features matrix e target vector

#3.1 Dividir o dataset entre treino e teste
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

#4. Fit do modelo
model_rf.fit(X_train, y_train)

#5. Previsões
y_pred_rf = model_rf.predict(X_test)

com esse modelo foi possível obter:

2.2 Demais modelos e o método Ensemble

Com os resultados obtidos no modelo inicial, os próximos passos seriam desenvolver novos modelos e compará-los com o modelo base a fim de comparar e verificar o desempenho.

Como explicado no post utilizei o método Ensemble para desenvolver paralelamente diversos modelos. A partir foi utilizado a métrica de votação hard para obtermos o resultado final desta etapa:

Deixarei no post o detalhamento utilizado da método:

# 1. importando bibliotecas necessárias

from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.linear_model import SGDClassifier
from xgboost import XGBClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier

# 2. Instanciar os modelos e definir hyperparametros

model_xgbc = XGBClassifier()
model_sgd = SGDClassifier()
model_svc = SVC()
model_lr = LogisticRegression()
model_dt = DecisionTreeClassifier()
voting_clf = VotingClassifier(estimators =[('xgbc', model_xgbc), ('sgd', model_sgd), ('svc', model_svc), ('dt', model_dt), ('lr', model_lr)], voting='hard')

# 3. Separar os dados

#os dados já foram separados anteiormente

# 3.1 Padronizar os dados

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test)

# 4. Fit do modelo

for model in (model_xgbc, model_sgd, model_svc, model_dt, model_lr, voting_clf):
  model.fit(X_train_scaled, y_train)

# 5. Fazer previsões em cima dos dados

model = []
accuracy = []

for clf in (model_xgbc, model_sgd, model_svc, model_dt, model_lr, voting_clf):
  y_pred = clf.predict(X_test_scaled)
  model.append(clf.__class__.__name__)
  accuracy.append(accuracy_score(y_test, y_pred))

# 6. Verificando Acurácia dos modelos

col = ['Acuracia']
ac = pd. DataFrame(data=accuracy, index=model, columns=col)
ac

A partir do modelo acima os resultados de acurácia obtidos foram:

Acurácia

2.3 Validação Cruzada

Após a elaboração dos modelos paralelos foi utilizado o processo de Validação Cruzada para otimizar a amostra de dados e tentar obter uma melhora nos resultados

# Aplicando Validação Cruzada com K-Fold

from sklearn.model_selection import cross_val_score
accuracies = cross_val_score(estimator = voting_clf, X = X_train_scaled, y = y_train, cv = 10, verbose=3)
print("Acurácia: {:.2f} %".format(accuracies.mean()*100))

Com a acurácia do modelo em torno de 80% os próximos passos foram otimizar os hiper parâmetros.

3. Otimização do Modelo

Nos próximos passos utilizei o modelo XGBoost Classifier e executei algumas rotinas de otimização dos hiper parâmetros.

O método Grid Search é uma abordagem de força bruta que testa todas as combinações de hiper parâmetros para encontrar o melhor modelo. Seu tempo de execução explode com o número de valores (e combinações dos mesmos) para testar.

Ao executar os testes durante a elaboração deste notebook exagerei nas combinações possíveis e levei quase 2 horas para que chegasse no valor final.

# 1. importar bibliotecas necessárias

from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV

# 2. Instanciar o modelo

xgb = XGBClassifier()

# 3. definir intervalos para otimização

# 3.1 Cross validation
kfold = StratifiedKFold(n_splits=5, shuffle=True)
#3.2 Intervalos para otimização
param_grid = {

    'xgb__n_components': [0, 1, 5],
    'xgb__n_estimators': range(0,200,50),
    'xgb__learning_rate': [0.001, 0.01, 0.05],
    'xgb__max_depth': [1, 2, 5],
    'xgb__gamma': [0.01, 0.1, 1]

}

# 3.3 gerar o modelo

grid = GridSearchCV(xgb, param_grid, n_jobs=-1, scoring='recall', cv=kfold, verbose=3)

# 4. Fit do modelo

grid_result = grid.fit(X_train_scaled, y_train)

# 5. ver resultados

print("Melhor acurácia: {} para {}".format(grid_result.best_score_, grid_result.best_params_))

Além do método de Grid Search, podemos utilizar a biblioteca Scikit-Optimize é uma biblioteca Python de código aberto que fornece uma implementação de Otimização Bayesiana que pode ser usada para ajustar os hiperparâmetros de modelos de machine learning da biblioteca Python scikit-Learn.

Em contraste com GridSearchCV, nem todos os valores de parâmetro são testados, mas em vez disso, um número fixo de configurações de parâmetros é amostrado a partir do especificado distribuições.

O Bayes Search é a automação para tunning dos hiper parâmetros. É uma biblioteca relativamente nova que está em desenvolvimento e que facilita a nossa busca no refinamento dos hiper parâmetros.

# 1. importar as bibliotecas necessárias

import skopt
from skopt import BayesSearchCV

# 2. Definir intervalos de otimziação

bayes = BayesSearchCV(

    estimator = XGBClassifier(
        n_jobs = 1,
        eval_metric = 'auc',
        silent=1,
        tree_method='auto'
    ),
    # 2.1 Definindo intervalos de otimização
    search_spaces = {
        'learning_rate': (0.001, 1.0, 'log-uniform'),
        'min_child_weight': (range(1,5,1)),
        'max_depth': (0, 50),
        'max_delta_step': (0, 20),
        'subsample': (0.01, 1.0, 'uniform'),
        'colsample_bytree': (0.01, 1.0, 'uniform'),
        'colsample_bylevel': (0.01, 1.0, 'uniform'),
        'reg_lambda': (1e-9, 1000, 'log-uniform'),
        'reg_alpha': (1e-9, 1.0, 'log-uniform'),
        'gamma': (1e-9, 0.5, 'log-uniform'),
        'min_child_weight': (0, 5),
        'n_estimators': (50, 100),
        'scale_pos_weight': (1e-6, 500, 'log-uniform')
    },    
    # 2. definindo método de avaliação
    scoring = 'roc_auc',
    cv = StratifiedKFold(
        n_splits=3,
        shuffle=True,
        random_state=42
    ),
    n_jobs = 3,
    n_iter = 6,   
    verbose = 0,
    refit = True,
    random_state = 42

)

# callback handler

def status_print_bayes(optim_result):

    bayes_resultado = bayes.best_score_
    print("Melhor resultado: %s", np.round(bayes_resultado,4))
    if bayes_resultado >= 0.98:
        print('Suficiente!')
        return True

# 4. Fit no modelo

otmizador = bayes.fit(X_train_scaled, y_train, callback=status_print_bayes)

A partir da otimização Bayes os parâmetros que resultaram na melhor classificação foram: (‘colsample_bylevel’, 0.4160029192647807), (‘colsample_bytree’, 0.7304484857455519), (‘gamma’, 0.13031389926541354), (‘learning_rate’, 0.00885928719200224), (‘max_delta_step’, 13), (‘max_depth’, 21), (‘min_child_weight’, 2), (‘n_estimators’, 87), (‘reg_alpha’, 5.497557739289786e-07), (‘reg_lambda’, 648), (‘scale_pos_weight’, 275), (‘subsample’, 0.13556548021189216)

4. Modelo Final

Os resultados obtidos nos dois passos anteriores serviram para definir o modelo final

from scikitplot.metrics import plot_confusion_matrix, plot_roc
from sklearn.metrics import roc_auc_score, roc_curve

# modelo final

modelo_final = XGBClassifier(

    learning_rate= 0.2700390206185342 , 
    n_estimators= 83,
    max_delta_step = 18, 
    max_depth= 36, 
    min_child_weight= 2, 
    gamma= 3.811128976537413e-05,
    colsample_bylevel = 0.8015579071911014,
    colsample_bytree = 0.44364889457651413,
    reg_lambda= 659,
    reg_alpha= 1.5057560255472018e-06,
    )

modelo_final.fit(X_train_rus, y_train_rus)

# fazer a previsão

X_test = scaler.transform(X_test)
y_pred = modelo_final.predict(X_test)

# Classification Report

print(classification_report(y_test, y_pred))

# imprimir a área sob a curva

print("AUC: {:.4f}\n".format(roc_auc_score(y_test, y_pred)))

# plotar matriz de confusão

plot_confusion_matrix(y_test, y_pred, normalize=True)
plt.show()

Com os dados desse modelo foi possível obter os seguintes resultados:

Optei por classificador ‘XGBClassifier’ devido as diversas possibilidades de refinamento.

Se fosse necessário o deploy desse classificador, bastaria utilizarmos o mesmo modelo com as otimizações que aqui criamos, validamos e testamos.

O classificador XGBoost otimizado obteve a performance com um Recall de 0.83%. No entanto, vale destacar que o Precision ficou baixo com um valor de 0.53. Isso significa que identificaríamos 83% dos clientes que realizariam churn, porém, também classificaríamos equivocadamente 36% dos clientes que não cancelariam os serviços. Ou seja, o trade-off entre Precision e Recall não ficou bom.

5. Verificando a Influência de cada Variável no modelo

SHAP (SHapley Additive exPlanations) é uma abordagem teórica de jogos para explicar a saída de qualquer modelo de Machine Learning.

Com o SHAP é possível verificar o peso que cada variável tem no classificador final. Com essa ferramenta é possível o gestor direcionar esforços nos itens que mais impactam o Churn Rate .

Na expliação oficial: essa abordagem conecta a alocação de peso ideal com explicações locais usando os valores clássicos de Shapley da teoria dos jogos e suas extensões relacionadas.

link para documentação oficial

Utilizando a biblioteca SHAP podemos obsevar que as variáveis que mais influenciaram no nosso classificador são:

  • Coluna33. Tipo de Contrato: Month to Month - contrato mensal
  • Coluna15. Online Security: No - o cliente não possui serviço adicional de segurança online
  • Coluna24. Tech Suport: No - o cliente não possui serviço adicional de suporte técnico
  • Coluna4. Ternure: conforme já observado novos clientes estão mais propensos a cancelar o serviço

No notebook elaborado demonstro os passos de utilização dessa ferramenta incrível.

Conclusão

Compreender o Churn Rate , facilita que gestores e equipes atuem antecipadamente procurando reduzir os índices de cancelamentos e, assim, ampliar a sua base de clientes.

No modelo matemático criado a partir da base de dados fornecida foi possível observar que clientes com contratos mensais, sem serviços adicionais como segurança online e suporte técnico são mais suscetíveis ao cancelamento do serviço. Com os insights obtidos a empresa pode direcionar seus esforços e custos em pontos críticos e assim modificar o Churn Rate .