Previsão de churn para empresa de telecomunicações
Partindo do princípio que o objetivo de qualquer negócio é aumentar o número de clientes, é muito importante entender que para que isto aconteça, não basta apenas atrair novos clientes, é necessário manter os que já estão ali.
Manter os clientes pode ser mais fácil que atrair novos, já que estes podem estar menos interessados nos serviços oferecidos e não possuem um relacionamento prévio com a empresa. Além do mais, manter um cliente custa menos para a empresa que atrair novos.
Ao prever a rotatividade de clientes existentes, ou seja, ao realizar o churn prediction, é possível antecipar quando um cliente pretende cancelar um serviço e com isso, reagir a tempo para mantê-lo (criando ofertas especiais, por exemplo). Isso só é possível pois já temos dados desses cliente para analisarmos, ao contrário de novos clientes que queremos atrair que não sabemos nada sobre ele.
O desafio é prever e tomar medidas para reduzir ao máximo o churn, garantindo assim clientes satisfeitos e que não pretendem deixar de assinar o produto.
Obtenção dos Dados
Os dados utilizados nesse projeto foram obtidos do Telecom Users Dataset disponível do Kaggle, que contém dados de uma empresa de telecomunicações.
O dataset possui informações de quase 6 mil usuários, suas características demográficas, serviços utilizados, duração de utilização dos serviços, valor e forma de pagamento.
Análise Exploratória dos Dados
Vamos começar a conhecer nosso dataset através da Análise Exploratória dos Dados, passo extremamente importante do projeto.
Variáveis e entradas
Vamos explorar as variáveis e entradas para já mapear os próximos passos a serem realizados na etapa de preparação dos dados. Para isso, vamos verificar:
- o tamanho do dataframe, ou seja, quantas variáveis e quantas entradas possui
Entradas: 5986
Variáveis: 22
- as primeiras entradas do dataframe
- os nomes e tipos das variáveis (colunas)
Tipos de Variáveis:
Unnamed: 0 int64
customerID object
gender object
SeniorCitizen int64
Partner object
Dependents object
tenure int64
PhoneService object
MultipleLines object
InternetService object
OnlineSecurity object
OnlineBackup object
DeviceProtection object
TechSupport object
StreamingTV object
StreamingMovies object
Contract object
PaperlessBilling object
PaymentMethod object
MonthlyCharges float64
TotalCharges object
Churn object
dtype: object
- Valores únicos das colunas
Valores únicos:Unnamed: 0 5986
customerID 5986
gender 2
SeniorCitizen 2
Partner 2
Dependents 2
tenure 73
PhoneService 2
MultipleLines 3
InternetService 3
OnlineSecurity 3
OnlineBackup 3
DeviceProtection 3
TechSupport 3
StreamingTV 3
StreamingMovies 3
Contract 3
PaperlessBilling 2
PaymentMethod 4
MonthlyCharges 1526
TotalCharges 5611
Churn 2
dtype: int64
Temos muitas variáveis do tipo object
representando variáveis categóricas binárias e múltiplas que precisarão ser transformadas em numéricas discretas.
É possível observar também que a variável TotalCharges
deveria ser do tipo float
e está no formato object
, então precisaremos transformá-la.
- a lista das variáveis e suas descrições
customerID
: ID do usuário
gender
: gênero do cliente (male / female)
SeniorCitizen
: se o cliente é aposentado (1, 0)
Partner
: se o cliente é casado (Yes, No)
tenure
: há quantos meses é cliente da empresa
PhoneService
: se o serviço de telefonia está conectado (Yes, No)
MultipleLines
: se tem multiplas linhas telefônicas conectadas (Yes, No, No phone service)
InternetService
: provedor de serviço de internet do cliente (DSL, Fiber optic, No)
OnlineSecurity
: se o serviço de segurança online está conectado (Yes, No, No internet service)
OnlineBackup
: se o serviço de backup online está ativo (Yes, No, No internet service)
DeviceProtection
: se o cliente possui seguro do equipamento (Yes, No, No internet service)
TechSupport
: se o suporte técnico está conectado (Yes, No, No internet service)
StreamingTV
: se o serviço de streaming para TV está conectado (Yes, No, No internet service)
StreamingMovies
: se o serviço de streaming de filmes está ativo (Yes, No, No internet service)
Contract
: tipo de contrato (Month-to-month, One year, Two year)
PaperlessBilling
: se o cliente recebe a conta por meios digitais, sem uso de papel (Yes, No)
PaymentMethod
: forma de pagamento (Electronic check, Mailed check, Bank transfer (automatic), Credit card (automatic))
MonthlyCharges
: valor do pagamento mensal atual
TotalCharges
: valor total pago pelo cliente pelos serviços durante todo o tempo
Churn
: se houve cancelamento, ou seja, se houve churn (Yes, No)
Por fim, vale ressaltar que a nossa variável alvo é a Churn
.
Valores ausentes
A proporção de valores ausentes do dataset tem relação direta com a qualidade do mesmo.
Vamos imprimir a proporção de valores ausentes em cada coluna:
Unnamed: 0 0.0
MonthlyCharges 0.0
PaymentMethod 0.0
PaperlessBilling 0.0
Contract 0.0
StreamingMovies 0.0
StreamingTV 0.0
TechSupport 0.0
DeviceProtection 0.0
OnlineBackup 0.0
OnlineSecurity 0.0
InternetService 0.0
MultipleLines 0.0
PhoneService 0.0
tenure 0.0
Dependents 0.0
Partner 0.0
SeniorCitizen 0.0
gender 0.0
customerID 0.0
TotalCharges 0.0
Churn 0.0
dtype: float64
Conforme visto, o dataset não possui nenhuma coluna com valores ausentes. Isso indica ótima qualidade do dataset, porém também indica que devemos ficar atentos pois essa pode não ser a realidade.
Iremos investigar isso através da variável TotalCharges
que identificamos como object
e deve ser tipo float
. Pode ser que ela tenha valores nulos e que não seja possível identificar sem antes transformá-la.
Tipo da variável TotalCharges: float64Total de dados ausentes em TotalCharges após transformação para float: 10
Após a transformação da variável TotalCharges
vimos que a mesma possui alguns valores ausentes.
Vamos substituir esses valores ausentes pela mediana da coluna.
Total de dados ausentes em TotalCharges (final): 0
Pronto, agora identificamos e tratamos o tipo de variável e seus dados ausentes.
Resumo estatístico
Vamos imprimir o resumo estatístico das variáveis numéricas:
A variável SeniorCitizen
é composta de 1 e 0 para aposentado e não aposentado respectivamente. É possivel perceber que a grande maioria dos clientes não são aposentados.
A variável tenure
(tempo que a pessoa é cliente da empresa) nos mostra valor máximo de 6 anos (72 meses), 75% dos clientes com tempo acima de 4 anos e meio (56 meses) e uma média de 2 anos e 8 meses (32 meses). Esses valores parecem coerentes e também podem demonstrar satisfação dos clientes a longo prazo.
As variáveis MonthlyCharges
e TotalCharges
apresentam valores coerentes e bons, já que a maioria dos clientes (75%) efetua pagamento mensal e total superior à média.
A princípio, não foram identificados outliers, vamos confirmar isto plotando os boxplots.
Pelos boxplots acima é possível confirmar que não há outliers, já que nenhuma das variáveis apresenta valores fora dos limites superior e inferior.
Distribuição da variável alvo
Temos como variável alvo, ou seja, aquela que queremos prever, a Churn
.
Para entender como está a distribuição dela no dataset, vamos plotar um gráfico de barras para visualizar o balanceamento dessa variável.
É possível perceber desbalanceamento leve da variável alvo.
A princípio, vamos manter os dados com a distribuição real. Com isto poderemos avaliar o quanto esse desbalanceamento afetará o desempenho do modelo de Machine Learning. Após treinar o modelo avaliaremos se será melhor balanceá-los.
Pelos histogramas plotados acima, observamos que:
- a maior parte dos clientes não são aposentados, confirmando o que observamos no resumo estatístico;
- existe um grande número de clientes novos (mais de 800 clientes com menos de 5 meses) e outro grande número de clientes bem antigos (cerca de 600 clientes com aproximadamente 70 meses -mais de 5 anos e meio-);
- muitos clientes efetuam pagamento mensal baixo, de aproximadamente 20 (moeda local);
- muitos clientes tem baixo valor total pago (abaixo de 2000), e poucos clientes tem alto valor total pago.
Correlação entre variáveis
Por último, vamos explorar a relação entre as variáveis numéricas.
Através do coeficiente de correlação iremos criar uma matriz de correlação e gerar um heatmap para visualizarmos.
É possível estabelecer algumas correlações entre as variáveis, porem a maioria das variáveis ainda não estão em formato numérico, o que nos impede de visualizar a correlação verdadeira em forma de matriz.
Iremos transformar essas variáveis categóricas em numéricas na fase seguinte (preparação dos dados) para então obtermos novamente a matriz de correlação e sermos capazes de avaliá-la.
Preparação dos dados
A partir da Análise Exploratória feita acima, vamos resumir o que identificamos a ser feito nessa etapa de preparação dos dados para que possam ser utilizados para construir o modelo de Machine Learning:
- Transformar variáveis categóricas em numéricas discretas;
- Padronizar as variáveis
tenure
,MonthlyCharges
eTotalCharges
; - Selecionar as variáveis que são interessantes e eliminar as que não serão úteis ou são menos relevantes ao modelo;
Inicialmente iremos construir e treinar o modelo com os dados desbalanceados, iremos avaliar a performance do modelo para termos uma base comparativa e se necessário, vamos balancear os dados e então treinar o modelo novamente.
Transformação das variáveis categóricas
Faremos a transformação das variáveis categóricas de duas formas: utilizando get_dummies
para as variáveis categóricas múltiplas e utilizando o LabelEncoder
para as demais variáveis.
Padronização das variáveis
Conforme observamos na análise exploratória, além das variáveis categóricas que acabamos de transformar em numéricas discretas, temos duas variáveis numéricas que precisam ser padronizadas.
Faremos isso com o StandardScaler
para as variáveis tenure
, MonthlyCharges
e TotalCharges
.
Vamos agora plotar novamente um heatmap da matriz de correlação das variáveis, pois agora conseguiremos avaliá-la.
Seleção de variáveis
Nem todas as variáveis do dataset são interessantes e/ou relevantes para construirmos o modelo de Machine Learning.
Iremos desconsiderar as variáveis Unnamed: 0
e customerID
pois não nos trazem nenhuma informação relevante.
Além disso, vamos desconsiderar também as variáveis MultipleLines_Yes
, MultipleLines_No phone service
, PhoneService
e gender
por serem as variáveis que correlacionam pouco com a variável alvo.
Fazendo isto, diminuimos as chances de overfitting dos modelos e buscamos aumentar seus desempenhos.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5986 entries, 0 to 5985
Data columns (total 27 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 SeniorCitizen 5986 non-null int64
1 Partner 5986 non-null int64
2 Dependents 5986 non-null int64
3 PaperlessBilling 5986 non-null int64
4 Churn 5986 non-null int64
5 InternetService_Fiber optic 5986 non-null uint8
6 InternetService_No 5986 non-null uint8
7 OnlineSecurity_No internet service 5986 non-null uint8
8 OnlineSecurity_Yes 5986 non-null uint8
9 OnlineBackup_No internet service 5986 non-null uint8
10 OnlineBackup_Yes 5986 non-null uint8
11 DeviceProtection_No internet service 5986 non-null uint8
12 DeviceProtection_Yes 5986 non-null uint8
13 TechSupport_No internet service 5986 non-null uint8
14 TechSupport_Yes 5986 non-null uint8
15 StreamingTV_No internet service 5986 non-null uint8
16 StreamingTV_Yes 5986 non-null uint8
17 StreamingMovies_No internet service 5986 non-null uint8
18 StreamingMovies_Yes 5986 non-null uint8
19 Contract_One year 5986 non-null uint8
20 Contract_Two year 5986 non-null uint8
21 PaymentMethod_Credit card (automatic) 5986 non-null uint8
22 PaymentMethod_Electronic check 5986 non-null uint8
23 PaymentMethod_Mailed check 5986 non-null uint8
24 scaled_tenure 5986 non-null float64
25 scaled_MonthlyCharges 5986 non-null float64
26 scaled_TotalCharges 5986 non-null float64
dtypes: float64(3), int64(5), uint8(19)
memory usage: 485.3 KB
Nosso dataset está pronto para passar para próxima etapa: construção do modelo de Machine Learning!
Modelo de Machine Learning
Neste primeiro momento da fase de desenvolvimento do modelo de Machine Learning, optamos por não balancear o dataset.
Iremos treinar o modelo sem balancear o dataset e observar seu desempenho.
A métrica a ser avaliada será recall
pois o que nos interessa mais são as previsões positivas corretas, no caso, quantos churns foram identificados corretamente.
Divisão do dataset
Vamos dividir nosso dataset entre treino e teste.
Baseline para estimar desempenho
Vamos criar um baseline com o Random Forest sem nenhuma alteração nos parâmetros, utilizando apenas os dados de treino.
Faremos a validação cruzada (cross-validation) utilizando a técnica K-fold para estimar o erro.
Recall (baseline): 0.47 (+/-0.05)
Já temos um parâmetro de 0.47 de recall do baseline feito com os dados desbalanceados. Vamos agora balancear o dataset para treinar diversos modelos.
Balanceamento dos dados
Faremos esse balanceamento com o SMOTE (Synthetic Minority Oversampling Technique) e vamos plotar novo gráfico de barras para visualizar o novo balanceamento.
Recall (após balanceamento): 0.86 (+/-0.25)
Conforme previsto, o balanceamento dos dados com o SMOTE aumentou para 0.86 de recall
, desempenho significativamente melhor que antes do balanceamento.
Escolha do modelo
Vamos fazer a validação cruzada para vários modelos de classificação afim de obter seus desempenhos (através da métrica recall
) e então escolher o mais adequado.
Os modelos são:
- Random Forest
- Decision Tree
- SGD
- SVC
- Regressão Logistica
- XGBoost
- LGBM
Obtivemos bons desempenhos para os modelos com os dados de treino. Vamos treinar e testar 3 modelos afim de ter como compará-los.
Vamos adotar os 3 modelos:
- Modelo 1 — Random Forest
- Modelo 2 — Logistic Regression
- Modelo 3 — XGBoost
Vale lembrar que a métrica levada em consideração aqui é recall
, premissa deste projeto explicada anteriormente.
Agora vamos treinar os modelos finais e finalmente verificar seus desempenhos com os dados de teste que até então estavam reservados e ainda não foram usados.
Modelo 1 — Random Forest
O modelo Random Forest não apresentou bom desempenho com os dados de teste. A taxa recall
de 0.86 (treino) foi para 0.57 (teste), o que indica que o modelo não generalizou bem para os novos dados.
Modelo 2 — Logistic Regression
O modelo de regressão logística apresentou desempenho similar com os dados de treino e teste. A taxa recall
de 0.81 (treino) foi para 0.79 (teste), podemos afirmar que esse desempenho é satisfatório.
Modelo 3 — XGBoost
Hiperparâmetros
Vamos agora testar e otimizar os parâmetros do XGBoost, afim de obtermos uma performance ainda melhor.
1- n_estimators
O primeiro hiperparâmetro que vamos ajustar é o n_estimators
.
Para isto, vamos fazer um grid search com learning_rate=0.1
.
Melhor: 0.872693 usando {'n_estimators': 50}
2- max_depth
Agora vamos ajustar o hiperparâmetro max_depth
.
Para isto, vamos fazer um grid search com learning_rate=0.1
e n_estimators=50
.
Melhor: 0.873297 usando {'max_depth': 9}
3- min_child_weight
Agora vamos ajustar o hiperparâmetro min_child_weight
.
Para isto, vamos fazer um grid search com learning_rate=0.1
, n_estimators=50
e max_depth=9
.
Melhor: 0.874207 usando {'min_child_weight': 2}
4- gamma
Agora vamos ajustar o hiperparâmetro gamma
.
Para isto, vamos fazer um grid search com learning_rate=0.1
, n_estimators=50
, max_depth=9
e min_child_weight=2
.
Melhor: 0.876027 usando {'gamma': 0.2}
5- subsample e colsample_bytree
Agora vamos ajustar os hiperparâmetros subsample
e colsample_bytree
.
Para isto, vamos fazer um grid search com learning_rate=0.1
, n_estimators=50
, max_depth=9
, min_child_weight=2
e gamma=0.2
.
Melhor: 0.876938 usando {'colsample_bytree': 0.9, 'subsample': 0.9}
6- lambda e alpha
Agora vamos ajustar os hiperparâmetros de regularização lambda
e alpha
.
Para isto, vamos fazer um grid search com learning_rate=0.1
, n_estimators=50
, max_depth=9
, min_child_weight=2
, gamma=0.2
, colsample_bytree=0.9
e subsample=0.9
.
Melhor: 0.876939 usando {'reg_alpha': 0.07, 'reg_lambda': 0.05}
7- learning rate
Agora que já ajustamos vários hiperparâmetros, vamos ajustar a learning_rate
que foi fixada no início.
Para isto, vamos fazer um grid search com todos os hiperparâmetros ajustados anteriormente.
Melhor: 0.876332 usando {'learning_rate': 0.07}
Com vários hiperparâmetros ajustados (vale ressaltar que o XGBoost possui mais dos que abordamos aqui), chegamos em uma recall
de 0.876, levemente mais alta que a inicial de 0.855. Vamos então treinar o modelo 3 final.
O modelo XGBoost, apesar de todo o processo de hyperparameter tunning não apresentou bom desempenho com os dados de teste. A taxa recall
de 0.855 (treino) foi para 0.62 (teste), o que indica que o modelo não generalizou bem para os novos dados.
Conclusão
Após concluir todas as etapas de um projeto de Machine Learning é possível perceber que dentre os modelos treinados, o modelo de regressão logística foi o modelo que apresentou desempenho mais satisfatório, considerando a métrica recall
adotada como premissa para este projeto.
Os modelos Random Forest e XGBoost apresentaram bom desempenho com os dados de treino, porém não generalizaram, ou seja, não mantiveram seus desempenhos com os dados de teste.
É possível experimentar outros métodos de padronização e balanceamento dos dados afim de melhorar os desempenhos. O modelo XGBoost especialmente tem muito potencial para obter desempenho acima até do modelo de regressão logística, devido a grande quantidade de hiperparâmetros que podem ser ajustados.
Obrigada !
Obrigada pelo seu tempo!
Espero que tenha gostado! Se tiver qualquer dúvida, feedback ou sugestão vou adorar que você entre em contato comigo pelo LinkedIn.
Para ver esse e outros projetos completos, acesse meu portfólio completo no GitHub.
Valeu!