Customer Churn em Operadoras de Telecom

18 de agosto, 2020

1. Descrição geral do problema


Santander Customer Satisfaction

Customer Churn (ou Rotatividade de Clientes, em uma tradução livre) refere-se a uma decisão tomada pelo cliente sobre o término do relacionamento comercial. Refere-se também à perda de clientes. A fidelidade do cliente e a rotatividade de clientes sempre somam 100%. Se uma empresa tem uma taxa de fidelidade de 60%, então a taxa de perda de clientes é de 40%. De acordo com a regra de lucratividade do cliente 80/20, 20% dos clientes estão gerando 80% da receita. Portanto, é muito importante prever os usuários que provavelmente abandonarão o relacionamento comercial e os fatores que afetam as decisões do cliente.

Neste projeto, iremos prever o Customer Churn em uma Operadora de Telecom.

Objetivo: Utilizar a linguagem Python, para criar um modelo de aprendizagem de máquina que possa prever se um cliente pode ou não cancelar seu plano, e qual a probabilidade disso ocorrer.


2. Carregando Dados

2.1 Importando bibliotecas necessárias

Começaremos nosso projeto, importanto todas as bilbiotecas necessárias, para a realização das fases iniciais de exploração, e transformação dos dados (Data Munging).

In [ ]:
# Importando biblioteca, para ocultar Future Warnings.

import warnings

warnings.simplefilter(action = 'ignore', category = FutureWarning)

# Importando bibliotecas, para a manipulação e exploração dos conjuntos de dados.

import numpy as np

import pandas as pd

# Importando bibliotecas, para tarefas de Data Munging.

from sklearn.feature_selection import VarianceThreshold

# Importando bibliotecas, para a plotagem de gráficos interativos com o plotly.

import plotly.offline as py

import plotly.graph_objs as go

import plotly.figure_factory as ff

py.init_notebook_mode(connected = False)

# Importando bibliotecas, para a plotagem de gráficos com o Seaborn e Matplotlib.

import seaborn as sns

import matplotlib.pyplot as plt

# Importando classes e bibliotecas, para a etapa de pré-processamento dos dados.

from sklearn import preprocessing

from sklearn.preprocessing import MinMaxScaler, PowerTransformer, normalize, LabelEncoder, StandardScaler

# Importando classes para calcular algumas estatísticas.

from scipy.stats import kurtosis, skew

# Importando biliotecas, para a fase de Feature Selection.

from sklearn.decomposition import PCA

from mlxtend.feature_selection import SequentialFeatureSelector as SFS

from sklearn.feature_selection import SelectKBest, SelectPercentile, mutual_info_classif, f_classif, RFE, chi2

# Importando bibliotecas, para a etapa de modelagem preditiva.

import xgboost as xgb

from sklearn import tree

from sklearn.svm import SVC

from sklearn.naive_bayes import GaussianNB

from sklearn.tree import DecisionTreeClassifier

from sklearn.neighbors import KNeighborsClassifier

from sklearn.linear_model import LogisticRegression

from sklearn.model_selection import KFold, cross_val_score

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, AdaBoostClassifier, GradientBoostingClassifier

# Importando classe, para carregar e salvar modelos preditivos em arquivos externos.

import pickle

# Importando classe, para fazer a busca dos melhores parâmetros, a serem utilizados em cada um dos modelos treinados.

from sklearn.model_selection import GridSearchCV

# Importando classes, para calcular as métricas de avaliação dos modelos preditivos.

from sklearn.metrics import accuracy_score, balanced_accuracy_score, average_precision_score, precision_score
from sklearn.metrics import recall_score, f1_score, roc_auc_score, cohen_kappa_score

2.2 Carregando Dados

In [ ]:
# Carregando dados de treino.

dataTrain = pd.read_csv('/content/datasets/projeto4_telecom_treino.csv')
In [ ]:
# Exibindo as primeiras linhas do DataFrame.

dataTrain.head()
Out[ ]:
Unnamed: 0 state account_length area_code international_plan voice_mail_plan number_vmail_messages total_day_minutes total_day_calls total_day_charge total_eve_minutes total_eve_calls total_eve_charge total_night_minutes total_night_calls total_night_charge total_intl_minutes total_intl_calls total_intl_charge number_customer_service_calls churn
0 1 KS 128 area_code_415 no yes 25 265.1 110 45.07 197.4 99 16.78 244.7 91 11.01 10.0 3 2.70 1 no
1 2 OH 107 area_code_415 no yes 26 161.6 123 27.47 195.5 103 16.62 254.4 103 11.45 13.7 3 3.70 1 no
2 3 NJ 137 area_code_415 no no 0 243.4 114 41.38 121.2 110 10.30 162.6 104 7.32 12.2 5 3.29 0 no
3 4 OH 84 area_code_408 yes no 0 299.4 71 50.90 61.9 88 5.26 196.9 89 8.86 6.6 7 1.78 2 no
4 5 OK 75 area_code_415 yes no 0 166.7 113 28.34 148.3 122 12.61 186.9 121 8.41 10.1 3 2.73 3 no
In [ ]:
# Carregando dados de teste.

dataTest = pd.read_csv('/content/datasets/projeto4_telecom_teste.csv')
In [ ]:
# Exibindo as primeiras linhas do DataFrame.

dataTest.head()
Out[ ]:
Unnamed: 0 state account_length area_code international_plan voice_mail_plan number_vmail_messages total_day_minutes total_day_calls total_day_charge total_eve_minutes total_eve_calls total_eve_charge total_night_minutes total_night_calls total_night_charge total_intl_minutes total_intl_calls total_intl_charge number_customer_service_calls churn
0 1 HI 101 area_code_510 no no 0 70.9 123 12.05 211.9 73 18.01 236.0 73 10.62 10.6 3 2.86 3 no
1 2 MT 137 area_code_510 no no 0 223.6 86 38.01 244.8 139 20.81 94.2 81 4.24 9.5 7 2.57 0 no
2 3 OH 103 area_code_408 no yes 29 294.7 95 50.10 237.3 105 20.17 300.3 127 13.51 13.7 6 3.70 1 no
3 4 NM 99 area_code_415 no no 0 216.8 123 36.86 126.4 88 10.74 220.6 82 9.93 15.7 2 4.24 1 no
4 5 SC 108 area_code_415 no no 0 197.4 78 33.56 124.0 101 10.54 204.5 107 9.20 7.7 4 2.08 2 no

3. Data Munging - Preparando dados para a análise exploratória

3.1 Criando funções auxiliares

Iremos definir algumas funções, para facilitar a execução das etapas de Data Munging.

In [ ]:
# Definindo uma função, para converter variáveis para o tipo categórico, e criar suas respectivas versões dummy.

def categoryToDummyVariables(data, columnsName):

    # Criando um dicionário vazio.

    newTypes = dict()
    
    # Criando o nome das variáveis dummy.

    newColumnsName = [n + '_dummy' for n in columnsName]

    # Definindo que cada variável especificada, deve ser convertida para o tipo de dado categórico.

    for i in range(0, len(columnsName)):
        newTypes.update({columnsName[i]: 'category'}) 

    # Convertendo o tipo de dado das variáveis especificadas.

    data = data.astype (newTypes)

    # Criando variáveis dummy.

    for i in range(0, len(columnsName)):
        data[newColumnsName[i]] = data[columnsName[i]].cat.codes

    # Retornando o DataFrame modificado.
    
    return data
In [ ]:
# Definindo uma função, para realizar as tarefas de Data Munging, necessárias para o conjunto de dados em análise.

def organizeData(data):
    
    # Extraindo o código de área da variável area_code.

    data['area_code'] = data['area_code'].apply(lambda e: e.split('_')[2]).astype('category')
    
    # Criando variáveis dummy para o conjunto de dados.

    data = categoryToDummyVariables(data = data, columnsName = data.select_dtypes(include = np.object).columns)

    # Eliminando a variável de índice do conjunto de dados.

    data = data.drop(columns = data.columns[0], axis = 1)

    # Retornando o DataFrame modificado.
    
    return data

3.2 Visão geral dos dados

3.2.1 De treino

In [ ]:
# Verificando as dimensões do dataset de treino.

dataTrain.shape
Out[ ]:
(3333, 21)

Verificamos a existência de 21 variáveis, e 3.333 observações dentro do dataset de treino.

In [ ]:
# Verificando o número de registros duplicados.

dataTrain.duplicated().sum()
Out[ ]:
0

Não há registros duplicados no dataset de treino.

In [ ]:
# Verificando o número de NAs existentes dentro do dataset de treino.

dataTrain.isna().sum()
Out[ ]:
Unnamed: 0                       0
state                            0
account_length                   0
area_code                        0
international_plan               0
voice_mail_plan                  0
number_vmail_messages            0
total_day_minutes                0
total_day_calls                  0
total_day_charge                 0
total_eve_minutes                0
total_eve_calls                  0
total_eve_charge                 0
total_night_minutes              0
total_night_calls                0
total_night_charge               0
total_intl_minutes               0
total_intl_calls                 0
total_intl_charge                0
number_customer_service_calls    0
churn                            0
dtype: int64

Não foi detectado nenhum valor NA dentro do conjunto de dados.

In [ ]:
# Verificando o tipo de dados das variáveis do dataset.

dataTrain.dtypes.value_counts()
Out[ ]:
float64    8
int64      8
object     5
dtype: int64
In [ ]:
# Contabilizando o número de valores únicos, em cada variável do dataset de treino.

info = dataTrain.nunique().sort_values()

# Determinando o tipo de dado, de cada uma das variáveis, do dataset de treino.

info = pd.DataFrame(info.values, index = info.index, columns = ['NUniques'])

# Atribuindo informações, sobre o tipo de dado das variáveis, ao DataFrame.

info['dtypes'] = dataTrain.dtypes

# Exibindo DataFrame.

info
Out[ ]:
NUniques dtypes
churn 2 object
international_plan 2 object
voice_mail_plan 2 object
area_code 3 object
number_customer_service_calls 10 int64
total_intl_calls 21 int64
number_vmail_messages 46 int64
state 51 object
total_day_calls 119 int64
total_night_calls 120 int64
total_eve_calls 123 int64
total_intl_charge 162 float64
total_intl_minutes 162 float64
account_length 212 int64
total_night_charge 933 float64
total_eve_charge 1440 float64
total_night_minutes 1591 float64
total_eve_minutes 1611 float64
total_day_minutes 1667 float64
total_day_charge 1667 float64
Unnamed: 0 3333 int64

3.2.2 De teste

In [ ]:
# Verificando as dimensões do dataset de teste.

dataTest.shape
Out[ ]:
(1667, 21)

Verificamos a existência de 21 variáveis, e 1.667 observações dentro do dataset de teste.

In [ ]:
# Verificando o número de IDs duplicados.

dataTest.duplicated().sum()
Out[ ]:
0

Não há registros duplicados no dataset de teste.

In [ ]:
# Verificando o número de NAs existentes dentro do dataset de teste.

dataTest.isna().sum()
Out[ ]:
Unnamed: 0                       0
state                            0
account_length                   0
area_code                        0
international_plan               0
voice_mail_plan                  0
number_vmail_messages            0
total_day_minutes                0
total_day_calls                  0
total_day_charge                 0
total_eve_minutes                0
total_eve_calls                  0
total_eve_charge                 0
total_night_minutes              0
total_night_calls                0
total_night_charge               0
total_intl_minutes               0
total_intl_calls                 0
total_intl_charge                0
number_customer_service_calls    0
churn                            0
dtype: int64

Não foi detectado nenhum valor NA dentro do conjunto de dados.

In [ ]:
# Verificando o tipo de dados das variáveis do dataset.

dataTest.dtypes.value_counts()
Out[ ]:
float64    8
int64      8
object     5
dtype: int64

3.3 Alterando tipos de dados das variáveis dos datasets

Iremos extrair o código de área para a variável area_code e convertê-la para o tipo de dado numérico. Também criaremos variáveis dummy para aquelas que forem do tipo categórico. Por fim, eliminaremos a coluna de índices do conjunto de dados.

Aplicaremos este processo para os dados de treino e de teste.

In [ ]:
# Limpando e organizando o conjunto de dados de treino.

dataTrain = organizeData(data = dataTrain)
In [ ]:
# Limpando e organizando o conjunto de dados de teste.

dataTest = organizeData(data = dataTest)

4. Análise exploratória dos dados

4.1 Criando funções auxiliares

Iremos definir algumas funções, para padronizar as plotagens de gráficos que criaremos.

4.1.1 Para a plotagem de gráficos interativos

Para fazer plotagens offline com o plotly (isto é, em ambientes como o Google Colab, Azure, Kaggle, Nteract, etc.), precisamos definir a função a seguir, e chamá-la sempre que formos gerar um gráfico.

In [ ]:
# Definindo uma função, para plotar gráficos interativos, em um ambiente jupyter não-padrão.

def configure_plotly_browser_state():
  
  import IPython
  
  display(IPython.core.display.HTML('''
        <script src="/static/components/requirejs/require.js"></script>
        <script>
          requirejs.config({
            paths: {
              base: '/static/base',
              plotly: 'https://cdn.plot.ly/plotly-1.43.1.min.js?noext',
            },
          });
        </script>
        '''))

4.1.2 Gráficos de Distribuição

Os gráficos de distribuição são usados para mostrar como as variáveis são distribuídas ao longo do tempo, ajudando a identificar valores discrepantes e tendências.

In [ ]:
# Definindo uma função, para criar gráficos de Boxplot interativos com o plotly.

def plotBoxplot(data, name = '', col = '', target = '', title = '', yaxis = '', xaxis = '', kind = 'normal',
                color = ['#8783D1', '#FADF63', '#06D6A0', '#662E9B'], opacity = 0.65, template = 'plotly_white', 
                orientation = 'v'):
    
    # Realizando as pré-configurações necessárias, para a plotagem do gráfico interativo.

    configure_plotly_browser_state()

    # Plota gráficos de um ou mais boxplots simples.

    if kind == 'normal': 

        # Plota gráficos verticais.

        if orientation == 'v':

            # Definindo os dados, a cor, o nome e a transparência que serão utilizados para criar cada um dos boxplots.
            
            dataTrace = [
                go.Box(
                    y      = data[l], 
                    name   = l if name == '' else name, 
                    marker = {'color': color[2], "opacity": opacity}
                ) for l in data.columns
            ]
        
        # Plota gráficos horizontais.

        else:

            # Definindo os dados, a cor, o nome e a transparência que serão utilizados para criar cada um dos boxplots.

            dataTrace = [
                go.Box(
                    x      = data[l], 
                    name   = l if name == '' else name, 
                    marker = {'color': color[3], "opacity": opacity}
                ) for l in data.columns
            ]

    # Plota gráficos boxplot, para uma variável numérica, agrupada por uma variável categórica.

    elif kind == 'groups':
        
        # Captura os registros pertencentes a cada categoria, da variável categórica.

        g = [data[data[target] == cat] for cat in data[target].cat.categories]

        # Converte a primeira letra do nome de cada categoria para maiúscula.

        name = [n.capitalize() for n in data[target].cat.categories]

        # Plota gráficos verticais.

        if orientation == 'v':
            
            # Definindo os dados, a cor, o nome e a transparência que serão utilizados para criar cada um dos boxplots.

            dataTrace = [
                go.Box(
                    y      = g[l][col], 
                    name   = name[l], 
                    marker = {'color': color[l], "opacity": opacity}
                ) for l in range(0, len(g))
            ]
        
        # Plota gráficos horizontais.

        else:

            # Definindo os dados, a cor, o nome e a transparência que serão utilizados para criar cada um dos boxplots.

            dataTrace = [
                go.Box(
                    x      = g[l][col], 
                    name   = name[l], 
                    marker = {'color': color[l], "opacity": opacity}
                ) for l in range(0, len(g))
            ]
    
    # Defindo as configurações de layout.

    layout = go.Layout (
        title    = title,
        yaxis    =  {'title': yaxis},
        xaxis    =  {'title': xaxis},
        template = template
    )

    # Criando uma Figure, com os dados e o layout defindos.

    fig = go.Figure(data = dataTrace, layout = layout)

    # Plotando o Figure com o pyplot.

    py.iplot(fig)
In [ ]:
# Definindo uma função, para criar Histogramas interativos com o plotly.

def plotHist(data, col = '', target = '', title = '', yaxis = '', xaxis = '', 
             groups = False, color = ['#8783D1', '#FADF63', '#EF476F'], opacity = 0.65, 
             template = 'plotly_white'):
    
    # Realizando as pré-configurações necessárias, para a plotagem do gráfico interativo.

    configure_plotly_browser_state()
    
    # Criando um histograma para um conjunto de dados.

    if groups == False:

        # Definindo os dados, a cor, e a transparência que serão utilizados para criar o Histograma.

        dataTrace = go.Histogram(x = data, marker = {'color': color[2], "opacity": opacity})

    # Criando um histograma para um conjunto de dados agrupado por categorias.

    else:
        
        # Captura os registros pertencentes a cada categoria, da variável categórica.

        g = [data[data[target] == cat] for cat in data[target].cat.categories]

        # Definindo os dados, a cor, e a transparência que serão utilizados para criar cada um dos Histogramas.

        dataTrace = [
            go.Histogram(
                x      = g[cat][col], 
                name   = data[target].cat.categories[cat].capitalize(), 
                marker = {'color': color[cat], "opacity": opacity}
            ) for cat in range(0,len(g))
        ]

    # Defindo as configurações de layout.

    layout = go.Layout(
        title    = title,
        yaxis    =  {'title': yaxis},
        xaxis    =  {'title': xaxis},
        bargap   = 0.05,
        template = template
    )

    # Criando uma Figure, com os dados e o layout defindos.

    fig = go.Figure(data = dataTrace, layout = layout)

    # Plotando o Figure com o pyplot.

    py.iplot(fig)
In [ ]:
# Definindo uma função, para criar gráficos de Densidade interativos com o plotly.

def plotDensity(data, col = '', target = '', title = '', xaxis = '', group = False,
                yaxis = 'Densidade', color = ['#8783D1', '#FADF63', '#3AAED8'], 
                template = 'plotly_white'):
    
    # Realizando as pré-configurações necessárias, para a plotagem do gráfico interativo.

    configure_plotly_browser_state()

    # Criando um gráfico de Densidade para o conjunto de dados.

    if group == False:

        # Definindo os dados, a cor, e os labels que serão utilizados para criar o gráfico de Densidade.

        fig = ff.create_distplot([data], group_labels = [xaxis], colors = [color[2]], show_hist = False, show_rug = False)

    # Criando um gráfico de Densidade para o conjunto de dados agrupado por categorias.

    else:

        # Captura os registros pertencentes a cada categoria, da variável categórica.

        g = [data[data[target] == cat][col] for cat in data[target].cat.categories]
        
        # Definindo os dados, a cor, e os labels que serão utilizados para criar cada um dos gráficos de Densidade.

        fig = ff.create_distplot(
            g, 
            group_labels = [n.capitalize() for n in data[target].cat.categories], 
            colors       = color, 
            show_hist    = False, show_rug = False
        )

    # Defindo as configurações de layout.

    fig.update_layout (
        title_text = title, 
        yaxis      = {"title_text": yaxis}, 
        xaxis      = {"title_text": xaxis}, 
        template   = template
    )

    # Plotando o Figure com o pyplot.

    fig.show()

4.1.3 Gráficos de Comparação

Os gráficos de comparação são usados para comparar um ou mais conjuntos de dados. Eles podem comparar itens ou mostrar diferenças ao longo do tempo.

In [ ]:
# Definindo uma função, para criar gráficos de Barra interativos com o plotly.

def plotBar(data, col = '', target = '', title = '', yaxis = '', xaxis = '', kind = 'normal', 
            color = ['#8783D1', '#FADF63', '#FF9F43', '#EE6352', '#FC7A1E'], opacity = 0.65, 
            template = 'plotly_white', orientation = 'v', barmode = 'group'):
    
    # Realizando as pré-configurações necessárias para a plotagem do gráfico interativo.

    configure_plotly_browser_state()

    # Criando gráficos na vertical.

    if orientation == 'v':

        # Plotando gráfico de barras simples.

        if kind == 'normal':
            
            # Definindo os dados, a cor, orientação e a transparência que serão utilizados para criar as barras.

            dataTrace = go.Bar(
                x           = data.index, 
                y           = data.values, 
                marker      = {'color': color[2], "opacity": opacity}, 
                orientation = orientation
            ) 
        
        # Plotando gráfico de barras agrupado por uma variável categórica.

        elif kind == 'groups':
            
            # Captura os registros pertencentes a cada categoria, da variável categórica.

            g = [data[data[target] == cat] for cat in data[target].cat.categories]

            # Definindo os dados, a cor, orientação e a transparência que serão utilizados para criar as barras.

            dataTrace = [
                go.Bar(
                    x           = g[cat][col], 
                    y           = g[cat]['count'], 
                    name        = data[target].cat.categories[cat].capitalize(),
                    marker      = {'color': color[cat], "opacity": opacity}, 
                    orientation = orientation
                ) for cat in range(0,len(g))
            ]
    
    # Criando gráficos na horizontal.

    else:

        # Plotando gráfico de barras simples.
        
        if kind == 'normal':
            
            # Definindo os dados, a cor, orientação e a transparência que serão utilizados para criar as barras.

            dataTrace = go.Bar(
                x           = data.values, 
                y           = data.index, 
                marker      = {'color': color[3], "opacity": opacity}, 
                orientation = orientation
            ) 
        
        # Plotando gráfico de barras agrupado por uma variável categórica.

        elif kind == 'groups': 

            # Captura os registros pertencentes a cada categoria, da variável categórica.

            g = [data[data[target] == cat] for cat in data[target].cat.categories]

            # Definindo os dados, a cor, orientação e a transparência que serão utilizados para criar as barras.

            dataTrace = [
                go.Bar(
                    x           = g[cat]['count'], 
                    y           = g[cat][col], 
                    name        = data[target].cat.categories[cat].capitalize(), 
                    marker      = {'color': color[cat], "opacity": opacity}, 
                    orientation = orientation
                ) for cat in range(0,len(g))
            ]

    # Defindo as configurações de layout.

    layout = go.Layout(
        title       = title,
        yaxis       =  {'title': yaxis},
        xaxis       =  {'title': xaxis},
        template    = template
    )

    # Criando uma Figure, com os dados e o layout defindos.

    fig = go.Figure(data = dataTrace, layout = layout)

    # Definindo que as barras devem ser dispostas uma ao lado da outra caso estejam agrupadas por categoria.
    # Para criar Stacked Bars, utilize: 'stack'.

    fig.update_layout(barmode = barmode)  

    # Plotando o Figure com o pyplot.

    fig.show()

4.1.4 Gráficos de Composição

Os gráficos de composição são usados para exibir partes de um todo e mudar ao longo do tempo.

In [ ]:
# Definindo uma função, para realizar a plotagem de gráficos de pizza.

def plotPie(data, title = ''):

    # Realizando as pré-configurações necessárias, para a plotagem do gráfico interativo.

    configure_plotly_browser_state()

    # Defindo as configurações de layout.

    layout = go.Layout (
        title = title
    )

    # Criando uma Figure, com os dados e o layout defindos.

    fig = go.Figure (
        data   = [go.Pie(labels = [i[0].upper() + i[1:] for i in dataCounts.index], values = dataCounts.values, hole = .1)],
        layout = layout
    )

    # Adicionando uma borda branca em cada uma das fatias da pizza.

    fig.update_traces (
        marker = dict (
            line = dict (
                color = '#FFFFFF', 
                width = 1
            )
        )
    )

    # Plotando a Figure com o pyplot.

    fig.show()

4.1.5 Gráficos de Relacionamento

Os gráficos de relacionamento são usados para mostrar uma conexão ou correlação entre duas ou mais variáveis.

In [ ]:
# Definindo uma função, para realizar a plotagem de gráficos de correlação.

def plotCorr(corr, figsize = (18, 18), cmap = 'Blues', title = 'Gráfico de Correlação entre as variáveis do Data Frame'):
    
    # Criando uma máscara, com as mesmas dimensões da matriz de correlação.

    mask = np.zeros_like(corr)

    # Selecionando a matriz triangular inferior da máscara.

    mask[np.triu_indices_from(mask)] = True

    # Definindo as dimensões do gráfico a ser plotado.

    _, ax = plt.subplots(figsize = figsize)

    # Criando o gráfico Heatmap.

    ax = sns.heatmap (
        data       = corr, 
        mask       = mask, 
        vmax       = .3, 
        linewidths = .5,
        square     = True, 
        cmap       = cmap,
        annot      = True
    )

    # Definindo o título do gráfico.

    ax = ax.set_title(title)

4.1.6 Funções para calcular estatísticas

Criaremos uma função, para padronizar as estatísticas que calcularemos, em cada uma das variáveis a serem estudadas.

In [ ]:
# Definindo uma função, para gerar um dataframe, com as estatísticas de uma variável do dataset.

def varStats(col, data, target = ''):

    if target == '':

        # Criando um dataframe, com as estatísticas da variável especificada.
        
        stats = pd.DataFrame({
            'min'   : data[col].min(),
            'Q1'    : data[col].quantile(.25),
            'Median': data[col].median(),
            'Mean'  : data[col].mean(),
            'Q3'    : data[col].quantile(.75),
            'SD'    : data[col].std(),
            'Sk'    : skew(data[col]),
            'Ck'    : kurtosis(data[col])
        }, index = [col])

    else:

        # Criando um dataframe, com as estatísticas da variável especificada, agrupada pela variável target.

        stats = pd.concat([
            dataTrain[[col, target]].groupby(target).min(),
            dataTrain[[col, target]].groupby(target).quantile(.25),
            dataTrain[[col, target]].groupby(target).median(),
            dataTrain[[col, target]].groupby(target).mean(),
            dataTrain[[col, target]].groupby(target).quantile(.75),
            dataTrain[[col, target]].groupby(target).std(),
            dataTrain[[col, target]].groupby(target).skew(),
            dataTrain[[col, target]].groupby(target).apply(lambda group: kurtosis(group)[0])

        ], axis = 1)

        # Renomeando as colunas do DataFrame.

        stats.columns = ['min', 'Q1', 'Median', 'Mean', 'Q3', 'SD', 'Sk', 'Ck']

    # Retornando os resultados obtidos.
    
    return stats

O coeficente de Assimetria (Skewness), indica como os dados estão distribuídos, e para interpretar seu resultado podemos olhar a tabela a seguir:

Índice de Assimetria Descrição
SK ≈ 0 Os dados são simétricos. Tanto a cauda do lado direito, quanto a do lado esquerdo da função densidade de probabilidade, são iguais;
SK < 0 A assimetria é negativa. A cauda do lado esquerdo da função densidade de probabilidade, é maior que a do lado direito e;
SK > 0 A assimetria é positiva. A cauda do lado direito da função densidade de probabilidade, é maior que a do lado esquerdo.

O coeficiente de Curtose (Kurtosis), é uma medida que caracteriza o achatamento da curva da função de distribuição, e para interpretar seu resultado, podemos olhar a tabela a seguir:

Índice de Curtose Descrição
CK ≈ 0 A distribuição é normal, e é chamada de Curtose Mesocúrtica;
CK < 0 A Cauda é mais leve que a normal. Para um coeficiente de Curtose negativo, tem-se uma Curtose Platicúrtica e;
CK > 0 A Cauda é mais pesada que a normal. Para um coeficiente de Curtose positivo, tem-se uma Curtose Leptocúrtica.

4.2 Explorando a distribuição de cada variável individualmente

4.2.1 Variável state

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'state'

# Definindo a descrição da variável nos gráficos.

label = 'Estados'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data = dataCounts,
    title = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis = 'Frequência Absoluta', 
    xaxis = label
)

O estado da Virginia (West Virginia - WV), nos Estados Unidos, é o mais frequente dentro do conjunto de dados, e o estado da California (California - CA), é o menos frequente.

In [ ]:
# Plotando um gráfico de pizza para a variável especificada.

plotPie (
    data  = dataCounts, 
    title = 'Frequência relativa das categorias da feature ' + col
)

A proporção de registros do estado da Virginia (West Virginia - WV), é aproximadamente 3 vezes maior do que a da California (California - CA).

4.2.2 Variável account_length

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'account_length'

# Definindo a descrição da variável nos gráficos.

label = 'Tamanho da Conta'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

O tamanho da conta parece ter uma distribuição aproximadamente normal.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do tamanho da conta discrepantes dentro do conjunto de dados. Vemos que os outliers estão localizados no limite superior do gráfico, ou seja, há registros em que o tamanho da conta é muito maior do que os demais e foge do padrão.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
account_length 1 74.0 101.0 101.064806 127.0 39.822106 0.096563 -0.109474

Destacamos que:

  • A média e a mediana do tamanho da conta apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é mais leve do que a normal, ou seja, temos uma curtose platicúrtica.

4.2.3 Variável area_code

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'area_code'

# Definindo a descrição da variável nos gráficos.

label = 'Código da Área'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Alterando índices do DataFrame.

dataCounts.index = [i + ' Code' for i in dataCounts.index]

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data  = dataCounts,
    title = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis = 'Frequência Absoluta', 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de pizza para a variável especificada.

plotPie (
    data  = dataCounts, 
    title = 'Frequência relativa das categorias da feature ' + col
)

A proporção do código de área 415 é aproximadamente 2 vezes maior do que os códigos 510 e 408.

4.2.4 Variável international_plan

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'international_plan'

# Definindo a descrição da variável nos gráficos.

label = 'Plano Internacional'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data  = dataCounts,
    title = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis = 'Frequência Absoluta', 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de pizza para a variável especificada.

plotPie (
    data  = dataCounts, 
    title = 'Frequência relativa das categorias da feature ' + col
)

Os registros são predominantemente de indivíduos que não possuem um plano internacional.

4.2.5 Variável voice_mail_plan

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'voice_mail_plan'

# Definindo a descrição da variável nos gráficos.

label = 'Plano de Correio de Voz'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data  = dataCounts,
    title = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis = 'Frequência Absoluta', 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de pizza para a variável especificada.

plotPie (
    data  = dataCounts, 
    title = 'Frequência relativa das categorias da feature ' + col
)

Os registros são predominantemente de indivíduos que não possuem um plano de mensagem de voz.

4.2.6 Variável number_vmail_messages

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'number_vmail_messages'

# Definindo a descrição da variável nos gráficos.

label = 'Número de mensagens vmail'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data  = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros são predominantemente de indivíduos que não enviam mensagens vmail.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do número de mensagens vmail discrepantes dentro do conjunto de dados. Vemos que os outliers estão localizados no limite superior do gráfico, ou seja, há registros em que o número de mensagens vmail é muito maior do que os demais e foge do padrão.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
number_vmail_messages 0 0.0 0.0 8.09901 20.0 13.688365 1.264254 -0.052852

Destacamos que:

  • A média e a mediana do número de mensagens vmail apresentam valores muito discrepantes;
  • O coeficiente de assimetria (Sk) confirma a assimetria à direita dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose Mesocúrtica.

4.2.7 Variável total_day_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_minutes'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos Diários'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total de minutos diários entre 137 e 228 minutos.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de minutos diários discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_day_minutes 0.0 143.7 179.4 179.775098 216.4 54.467389 -0.029064 -0.02171

Destacamos que:

  • A média e a mediana do total de minutos diários apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose Mesocúrtica.

4.2.8 Variável total_day_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_calls'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas diárias'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data  = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 80 e 119 chamadas diárias.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de chamadas diárias discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_day_calls 0 87.0 101.0 100.435644 114.0 20.069084 -0.111736 0.241017

Destacamos que:

  • A média e a mediana do total de chamadas diárias apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma a assimetria à esquerda dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada do que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.9 Variável total_day_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_charge'

# Definindo a descrição da variável nos gráficos.

label = 'Carga diária Total'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta uma carga diária total entre 19,5 e 39,5.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros da carga diária total discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_day_charge 0.0 24.43 30.5 30.562307 36.79 9.259435 -0.02907 -0.021582

Destacamos que:

  • A média e a mediana da carga diária total apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose Mesocúrtica.

4.2.10 Variável total_eve_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_minutes'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos à Tarde'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 147,5 e 247,5 minutos à tarde.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de minutos à tarde discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_eve_minutes 0.0 166.6 201.4 200.980348 235.3 50.713844 -0.023867 0.023792

Destacamos que:

  • A média e a mediana do total de minutos à tarde apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose Mesocúrtica.

4.2.11 Variável total_eve_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_calls'

# Definindo a descrição da variável nos gráficos.

label = 'Ligações totais à Tarde'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data  = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 80 e 123 ligações à tarde.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros das ligações totais à tarde discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_eve_calls 0 87.0 100.0 100.114311 114.0 19.922625 -0.055538 0.204048

Destacamos que:

  • A média e a mediana das ligações totais à tarde apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.12 Variável total_eve_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_charge'

# Definindo a descrição da variável nos gráficos.

label = 'Carga total à Tarde'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta uma carga total entre 12,25 e 21,75 minutos à tarde.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros das ligações totais à tarde discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_eve_charge 0.0 14.16 17.12 17.08354 20.0 4.310668 -0.023847 0.02365

Destacamos que:

  • A média e a mediana das ligações totais à tarde apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose Mesocúrtica.

4.2.13 Variável total_night_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_minutes'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos à Noite'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data  = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 137,5 e 252,5 minutos à noite.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de minutos à noite discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_night_minutes 23.2 167.0 201.2 200.872037 235.3 50.573847 0.008917 0.083888

Destacamos que:

  • A média e a mediana do total de minutos à noite apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose mesocúrtica.

4.2.14 Variável total_night_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_calls'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas Noturnas'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data  = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 80 e 123 chamadas à noite.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de chamadas noturnas discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_night_calls 33 87.0 100.0 100.107711 113.0 19.568609 0.032485 -0.073711

Destacamos que:

  • A média e a mediana do total de chamadas noturnas apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose mesocúrtica.

4.2.15 Variável total_night_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_charge'

# Definindo a descrição da variável nos gráficos.

label = 'Carga noturna Total'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta uma carga noturna total entre 6 e 12.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros da carga noturna total discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_night_charge 1.04 7.52 9.05 9.039325 10.59 2.275873 0.008882 0.083735

Destacamos que:

  • A média e a mediana da carga noturna total apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma que os dados são aproximadamente simétricos;
  • O coeficiente de curtose (Ck) evidência que a calda é aproximadamente normal, ou seja, temos uma curtose mesocúrtica.

4.2.16 Variável total_intl_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_minutes'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos Internacionais'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta um total entre 6,8 e 13,7 minutos internacionais.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de minutos internacionais discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_intl_minutes 0.0 8.5 10.3 10.237294 12.1 2.79184 -0.245026 0.606472

Destacamos que:

  • A média e a mediana do total de minutos internacionais apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma a assimetria à esquerda dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada do que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.17 Variável total_intl_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_calls'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas Internacionais'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data        = dataCounts,
    title       = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis       = label, 
    xaxis       = 'Frequência Absoluta',
    orientation = 'h'
)

A maior parte dos registros apresenta um total de 2 à 6 chamadas internacionais.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do total de chamadas internacionais discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser assimétrica à direita.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_intl_calls 0 3.0 4.0 4.479448 6.0 2.461214 1.320883 3.077165

Destacamos que:

  • A média e a mediana do total de chamadas internacionais apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma a assimetria à direita dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada do que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.18 Variável total_intl_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_charge'

# Definindo a descrição da variável nos gráficos.

label = 'Carga Internacional Total'

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data = dataTrain[col],
    title = 'Histograma para a variável ' + col,
    xaxis = label,
    yaxis = 'Frequência Absoluta'
)

A maior parte dos registros apresenta uma carga internacional total entre 1,7 e 3,6.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros da carga internacional total discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser aproximadamente normal em torno da mediana.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
total_intl_charge 0.0 2.3 2.78 2.764581 3.27 0.753773 -0.245176 0.606897

Destacamos que:

  • A média e a mediana da carga internacional total apresentam valores próximos;
  • O coeficiente de assimetria (Sk) confirma a assimetria à esquerda dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada do que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.19 Variável number_customer_service_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'number_customer_service_calls'

# Definindo a descrição da variável nos gráficos.

label = 'Número de chamadas de atendimento ao Cliente'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data        = dataCounts,
    title       = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis       = label, 
    xaxis       = 'Frequência Absoluta',
    orientation = 'h'
)

O número de chamadas de atendimento ao cliente mais frequente é 1.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data  = dataTrain[col], 
    title = 'Gráfico de Densidade para a variável ' + col, 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data  = dataTrain[[col]],
    title = 'Boxplot para a variável ' + col,
    xaxis = 'Variável',
    name  = label
)

O boxplot nos indica que, há registros do número de chamadas de atendimento ao cliente discrepantes dentro do conjunto de dados. A distribuição dos dados aparenta ser simétrica à direita.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col, data = dataTrain)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
number_customer_service_calls 0 1.0 1.0 1.562856 2.0 1.315491 1.090868 1.726518

Destacamos que:

  • A média e a mediana da carga internacional total apresenta valores próximos;
  • O coeficiente de assimetria (Sk) confirma a assimetria à direita dos dados e;
  • O coeficiente de curtose (Ck) evidência que a calda é mais pesada do que a normal, ou seja, temos uma curtose leptocúrtica.

4.2.20 Variável churn

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Churn'

# Contabilizando a frequência absoluta, de cada categoria presente na variável especificada.

dataCounts = dataTrain[col].value_counts()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data  = dataCounts,
    title = 'Frequência absoluta das categorias da Feature ' + col, 
    yaxis = 'Frequência Absoluta', 
    xaxis = label
)
In [ ]:
# Plotando um gráfico de pizza para a variável especificada.

plotPie (
    data  = dataCounts, 
    title = 'Frequência relativa das categorias da feature ' + col
)

Apenas 14,5 % dos registros referem-se a clientes que efetuaram o churn.

4.3 Explorando a distribuição de cada Feature a partir da variável Target

4.3.1 Variável state

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'state'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Estados'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data   = data,
    col    = col,
    target = target,
    title  = 'Churn dos clientes por ' + label,
    yaxis  = 'Frequência Absoluta',
    xaxis  = label, 
    kind   = 'groups'
)

Os estados do Texas (TX), New Jersey (NJ) e Maryland (MD) são os que apresentam as maiores quantidades de registros de clientes que realizaram o churn.

4.3.2 Variável account_length

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'account_length'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Tamanho da Conta'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)
In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

A distribuição do tamanho da conta, para os indivíduos que fizeram ou não o churn, é aproximandamente igual.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O tamanho mediano, das contas dos indivíduos que realizaram o churn, é ligeiramente maior do que o daqueles que não o efeturam.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 1 73.0 100 100.793684 127.0 39.88235 0.090523 -0.135657
yes 1 76.0 103 102.664596 127.0 39.46782 0.137132 0.043640

Destacamos que:

  • A média e a mediana, do tamanho da conta dos grupos, apresentam valores ligeiramente diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que efetuaram o churn, tem uma assimetria à direita ligeiramente maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, é aproximadamente mais normal.

4.3.3 Variável area_code

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'area_code'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Código de Área'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Transformando variável alvo em String.

data[col] = data[col].apply(lambda c: str(c) + ' Code')

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data   = data,
    col    = col,
    target = target,
    title  = 'Churn dos clientes por ' + label,
    yaxis  = 'Frequência Absoluta',
    xaxis  = label, 
    kind   = 'groups'
)

O código de área 415, é o que apresenta o maior número de registros para os clientes que realizaram o churn.

4.3.4 Variável international_plan

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'international_plan'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Plano Internacional'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Capitalizando valores da variável alvo.

data[col] = data[col].apply(lambda c: c.capitalize())

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data   = data,
    col    = col,
    target = target,
    title  = 'Churn dos clientes por ' + label,
    yaxis  = 'Frequência Absoluta',
    xaxis  = label, 
    kind   = 'groups'
)

A proporção de clientes que utilizam o plano internacional, e fizeram ou não o churn, é aproximadamente igual.

4.3.5 Variável voice_mail_plan

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'voice_mail_plan'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Plano de Correio de Voz'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Capitalizando valores da variável alvo.

data[col] = data[col].apply(lambda c: c.capitalize())

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data   = data,
    col    = col,
    target = target,
    title  = 'Churn dos clientes por ' + label,
    yaxis  = 'Frequência Absoluta',
    xaxis  = label, 
    kind   = 'groups'
)

Os clientes que utilizam o plano de correio de voz, predominantemente, não realizam o churn.

4.3.6 Variável number_vmail_messages

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'number_vmail_messages'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Número de mensagens vmail'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

Os clientes que efeturam o churn, predominantemente, não enviam mensagens vmail.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de indivíduos que não realiza o churn, apresenta uma variação maior. Também observamos, que aqueles indivíduos que realizam o churn e utilizam as mensagens vmail, são outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0 0.0 0 8.604561 22.0 13.913125 1.167435 -0.292254
yes 0 0.0 0 5.115942 0.0 11.860138 2.040412 2.524606

Destacamos que:

  • A média do tamanho da conta dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que efetuaram o churn, tem uma assimetria à direita maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.3.7 Variável total_day_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_minutes'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos Diários'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

O grupo dos clientes que não realizaram o churn, aparenta ter uma distribuição normal, para o total de minutos diários.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

O grupo dos clientes que realizaram o churn, aparenta ter duas modas: uma em torno de 160 minutos e outra em torno de 265 minutos.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de indivíduos que realizaram o churn, apresentam um total de minutos diários maior.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.0 142.825 177.2 175.175754 210.30 50.181655 -0.226378 -0.006024
yes 0.0 153.250 217.6 206.914079 265.95 68.997792 -0.199223 -0.813099

Destacamos que:

  • A média e a mediana, do total de minutos diários dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais leve que a normal.

4.3.8 Variável total_day_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_calls'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas diárias'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

O total de chamadas diárias, parece ter uma distribuição aproximadamente normal, para o grupo de clientes que não realizaram o churn.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

O total de chamadas diárias, para o grupo dos indivíduos que realizaram o churn, aparenta ser levemente maior.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

Há mais outliers no grupo dos clientes que não realizaram o churn.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0 87.0 100 100.283158 114.0 19.801157 -0.063296 0.124995
yes 0 87.5 103 101.335404 116.5 21.582307 -0.353392 0.709753

Destacamos que:

  • A média e a mediana, do total de chamadas diárias dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.3.9 Variável total_day_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_day_charge'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Carga diária Total'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

A carga diária total, do grupo de indivíduos que não realizaram o churn, aparenta ter uma distribuição normal.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

A carga diária total, do grupo de indivíduos que realizaram o churn, aparenta ter duas modas: uma em torno de 26 e outra em torno de 45.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

A variação e a mediana da carga diária total, do grupo de indivíduos que realizaram o churn, é maior.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.0 24.2825 30.12 29.780421 35.75 8.530835 -0.226405 -0.005913
yes 0.0 26.0550 36.99 35.175921 45.21 11.729710 -0.199209 -0.813006

Destacamos que:

  • A média e a mediana, do total de chamadas diárias dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais leve que a normal.

4.3.10 Variável total_eve_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_minutes'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos à Tarde'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

O total de minutos à tarde, para o grupo de clientes que não realizaram o churn, aparenta ter uma distribuição normal.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

O total de minutos à tarde, para o grupo de clientes que realizaram o churn, aparenta ter um valor mediano maior.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.0 164.5 199.6 199.043298 233.20 50.292175 -0.043140 0.032215
yes 70.9 177.1 211.3 212.410145 249.45 51.728910 0.033129 -0.103768

Destacamos que:

  • A média e a mediana, do total de minutos à tarde dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais leve que a normal.

4.3.11 Variável total_eve_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_calls'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Ligações totais à Tarde'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

Os dois grupos de clientes, aparentam ter uma distribuição normal, para o número de ligações totais à tarde.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

A distribuição de densidade dos grupos é muito semelhante.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0 87.0 100 100.038596 114.0 19.958414 -0.069217 0.246968
yes 48 87.0 101 100.561077 114.0 19.724711 0.029856 -0.073037

Destacamos que:

  • A média e a mediana, do total de minutos à tarde dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que não efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.3.12 Variável total_eve_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_eve_charge'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Carga total à Tarde'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)
In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

A carga total à tarde dos grupos, parece ser normalmente distribuida.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.00 13.980 16.97 16.918909 19.820 4.274863 -0.043103 0.032068
yes 6.03 15.055 17.96 18.054969 21.205 4.396762 0.033144 -0.103840

Destacamos que:

  • A média e a mediana, da carga total à tarde dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais leve que a normal.

4.3.13 Variável total_night_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_minutes'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos à Noite'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)
In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

O total de minutos à noite para os grupos, tem uma distribuição aproximadamente normal.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 23.2 165.90 200.25 200.133193 234.90 51.105032 0.017230 0.097274
yes 47.4 171.25 204.80 205.231677 239.85 47.132825 -0.005073 -0.089390

Destacamos que:

  • A média e a mediana, do total de minutos à noite dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que os grupos são aproximadamente simétricos e;
  • O coeficiente de curtose (Ck), evidência que os grupos tem um distribuição aproximadamente normal.

4.3.14 Variável total_night_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_calls'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas Noturnas'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

O total de chamadas noturnas para os grupos, aparenta ser normalmente distribuído.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 33 87.0 100 100.058246 113.0 19.506246 0.026949 -0.018217
yes 49 85.0 100 100.399586 115.0 19.950659 0.061494 -0.380671

Destacamos que:

  • A média e a mediana, do total de chamadas noturnas dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que os grupos são aproximadamente simétricos;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que efetuaram o churn, apresenta uma calda mais leve que a normal.

4.3.15 Variável total_night_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_night_charge'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Carga noturna Total'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)

A carga noturna total para os grupos, aparenta ser normalmente distribuída.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 1.04 7.470 9.01 9.006074 10.570 2.299768 0.017240 0.097067
yes 2.13 7.705 9.22 9.235528 10.795 2.121081 -0.005464 -0.088868

Destacamos que:

  • A média e a mediana, do total de chamadas noturnas dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que os grupos são aproximadamente simétricos e;
  • O coeficiente de curtose (Ck), evidência que os grupos tem um distribuição aproximadamente normal.

4.3.16 Variável total_intl_minutes

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_minutes'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de minutos Internacionais'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)
In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.0 8.4 10.2 10.158877 12.0 2.784489 -0.293780 0.683283
yes 2.0 8.8 10.6 10.700000 12.8 2.793190 0.021392 -0.012325

Destacamos que:

  • A média e a mediana, do total de minutos internacionais dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que não efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.3.17 Variável total_intl_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_calls'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Total de chamadas Internacionais'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data        = data,
    col         = col,
    target      = target,
    title       = 'Churn dos clientes por ' + label,
    yaxis       = 'Frequência Absoluta',
    xaxis       = label, 
    kind        = 'groups',
    orientation = 'h'
)

Os grupos de cliente, possuem uma assimetria à direita.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)
In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0 3.0 4 4.532982 6.0 2.441984 1.259044 2.798436
yes 1 2.0 4 4.163561 5.0 2.551575 1.714231 4.821751

Destacamos que:

  • A média e a mediana, do total de chamadas internacionais dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que os grupos dos clientes que realizaram o churn, tem uma assimetria à direita maior e;
  • O coeficiente de curtose (Ck), evidência que os grupos dos clientes que realizaram o churn, apresentam uma calda mais pesada que a normal.

4.3.18 Variável total_intl_charge

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'total_intl_charge'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Carga Internacional Total'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de histograma para a variável especificada.

plotHist (
    data   = dataTrain[[col, target]],
    title  = 'Histograma para a variável ' + col,
    xaxis  = label,
    yaxis  = 'Frequência Absoluta',
    col    = col,
    target = target,
    groups = True
)
In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

A carga internacional total para os grupos, tem uma distribuição aproximadamente normal.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0.00 2.27 2.75 2.743404 3.24 0.751784 -0.293902 0.683996
yes 0.54 2.38 2.86 2.889545 3.46 0.754152 0.021009 -0.013370

Destacamos que:

  • A média e a mediana, da carga internacional total dos grupos, apresenta valores próximos;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à esquerda maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que não efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.3.19 Variável number_customer_service_calls

In [ ]:
# Definindo o nome da variável a ser analisada.

col = 'number_customer_service_calls'

# Definindo o nome da variável Target.

target = 'churn'

# Definindo a descrição da variável nos gráficos.

label = 'Número de chamadas de atendimento ao Cliente'

# Capturando variáveis especificadas do Dataset.

data = dataTrain[[col, target]]

# Criando uma variável count para contabilizar as ocorrências de cada registro.

data['count'] = 1

# Agrupando dados e contabilizando o número de ocorrências.

data = data.groupby(by = [target, col]).sum()

# Reorganizando DataFrame. 

data = data.reset_index()

# Plotando um gráfico de barras para a variável especificada.

plotBar (
    data        = data,
    col         = col,
    target      = target,
    title       = 'Churn dos clientes por ' + label,
    yaxis       = 'Frequência Absoluta',
    xaxis       = label, 
    kind        = 'groups',
    orientation = 'h'
)

Os grupos possuem uma assimetria à direita.

In [ ]:
# Criando um gráfico de Densidade para a variável especificada.

plotDensity (
    data   = dataTrain[[col, target]], 
    title  = 'Gráfico de Densidade para a variável ' + col, 
    xaxis  = label,
    col    = col,
    target = target,
    group  = True
)

O grupo dos clientes que não realizaram o churn, apresentam 4 modas para o número de chamadas de atendimento ao cliente: 1, 2, 3 e 4.

In [ ]:
# Plotando um gráfico de boxplot para a variável especificada.

plotBoxplot (
    data   = dataTrain[[col, target]],
    title  = 'Boxplot para a variável ' + col,
    yaxis  = label,
    xaxis  = target.capitalize(),
    col    = col,
    target = target,
    kind   = 'groups'
)

O grupo de clientes que não realizaram o churn, apresenta mais outliers.

In [ ]:
# Calculando algumas estatísticas para a variável especificada.

varStats(col = col, data = dataTrain, target = target)
Out[ ]:
min Q1 Median Mean Q3 SD Sk Ck
churn
no 0 1.0 1 1.449825 2.0 1.163883 0.886801 1.210349
yes 0 1.0 2 2.229814 4.0 1.853275 0.703604 -0.109937

Destacamos que:

  • A média e a mediana, do número de chamadas de atendimento ao cliente dos grupos, apresenta valores diferentes;
  • O coeficiente de assimetria (Sk), evidência que o grupo dos clientes que não efetuaram o churn, tem uma assimetria à direita maior;
  • O coeficiente de curtose (Ck), evidência que o grupo dos clientes que não efetuaram o churn, apresenta uma calda mais pesada que a normal.

4.4 Analisando a correlação entre as variáveis

Nesta etapa, desejamos verificar como as variáveis se correlacionam, ou seja, como uma variável ajuda a prever o valor de outra variável no dataset.

In [ ]:
# Criando uma matriz de correlação.

corr = dataTrain.corr()

# Selecionando o triângulo superior da matriz de correlação.

upper = corr.abs().where(np.triu(np.ones(corr.shape), k = 1).astype(np.bool))

# Capturando o nome das variáveis que apresentam uma correlação maior do que 0.95.

to_drop = [column for column in upper.columns if any(upper[column] > 0.95)]

# Exibindo o nome das variáveis altamente correlacionadas.

pd.DataFrame(data = to_drop, columns = ['Highly correlated'])
Out[ ]:
Highly correlated
0 total_day_charge
1 total_eve_charge
2 total_night_charge
3 total_intl_charge
4 voice_mail_plan_dummy
In [ ]:
# Plotando a matriz de correlação entre as variáveis do DataFrame.

plotCorr(corr)

Detectamos a existência de variáveis altamente correlacionadas, dentre elas, 4 estão perfeitamente correlacionadas e deverão ser eliminadas dos conjuntos de dados.

Observamos que as variáveis total_day_minutes, total_day_charge, number_customer_service_calls e international_plan_dummy são as que apresentam as correlações mais fortes com a variável a ser prevista.

5. Feature Selection

5.1 Criando funções auxiliares

Iremos definir algumas funções, para aplicar diferentes escalas aos dados e para utilizar a técnica PCA.

In [ ]:
# Definindo uma função para para aplicar as transformações MimMaxScaler, StandardScaler, 
# Yeo-Johnson e Normalize as features de um conjunto de dados de treino e de teste.

def dataTransform(train, test, transform = 'MM'):

    # Criando cópias dos DataFrames.

    trainFeatures = train.copy()
    testFeatures  = test.copy()
    
    # Criando um variável para identificar os dados de treino e de teste.

    trainFeatures['train'] = 1
    testFeatures['train']  = 0

    # Unindo dados de treino e de teste em um único conjunto de dados.

    features = pd.concat([trainFeatures, testFeatures])

    # Capturando a variável que identifica os dados de treino e de teste.

    train = features['train']

    # Eliminando a variável de identificação do Dataframe.

    features = features.drop('train', axis = 1)

    # Criando variável para armazenar o conjunto de dados transformado.

    featuresTransformed = None

    # Aplicando a transformação selecionada.

    if transform == 'MM':
        
        # Criando um objeto da classe MinMaxScaler().

        scaler = MinMaxScaler()

        # Aplicando a escala nas Features e capturando o resultado obtido.

        featuresTransformed = scaler.fit_transform(features)

        # Criando um DataFrame com os resultados obtidos.

        featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = features.columns)
    
    elif transform == 'SS':

        # Criando um objeto da classe StandardScaler().

        scaler = StandardScaler()

        # Aplicando a escala nas Features e capturando o resultado obtido.

        featuresTransformed = scaler.fit_transform(features)

        # Criando um DataFrame com os resultados obtidos.

        featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = features.columns)
    
    elif transform == 'ND':

        # Criando um objeto da classe StandardScaler().

        scaler = StandardScaler()

        # Aplicando a escala nas Features e capturando o resultado obtido.

        featuresTransformed = scaler.fit_transform(features)

        # Criando um DataFrame com os resultados obtidos.

        featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = features.columns)

        # Criando um objeto da classe PowerTransformer().

        scaler = PowerTransformer(method = 'yeo-johnson', standardize = False)

        # Aplicando a escala nas Features e capturando o resultado obtido.

        featuresTransformed = scaler.fit_transform(featuresTransformed)

        # Criando um DataFrame com os resultados obtidos.

        featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = features.columns)
    
    elif transform == 'N':

        # Normalizando cada feature para uma unidade uniforme (vetor unitário).

        featuresTransformed = normalize(features, axis = 0)

        # Criando um DataFrame com os resultados obtidos.

        featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = features.columns)

    # Atribuindo variável identificadora ao DataFrame transformado.

    featuresTransformed['train'] = train.values

    # Separando dados de treino e de teste transformados.

    trFeatures  = featuresTransformed[featuresTransformed['train'] == 1].drop('train', axis = 1)
    tstFeatures = featuresTransformed[featuresTransformed['train'] == 0].drop('train', axis = 1)

    # Retornando dados de treino e teste transformados.

    return trFeatures, tstFeatures
In [ ]:
# Definindo função para aplicar a técnica PCA a um conjunto de dados de treino e teste.

def pcaTransform(train, test, nComponents = 10):

    # Criando cópias dos DataFrames.

    trainFeatures = train.copy()
    testFeatures  = test.copy()
    
    # Criando um variável para identificar os dados de treino e de teste.

    trainFeatures['train'] = 1
    testFeatures['train']  = 0

    # Unindo dados de treino e de teste em um único conjunto de dados.

    features = pd.concat([trainFeatures, testFeatures])

    # Capturando a variável que identifica os dados de treino e de teste.

    train = features['train']

    # Eliminado a variável de identificação do Dataframe.

    features = features.drop('train', axis = 1)

    # Criando variável para armazenar o conjunto de dados transformados.

    featuresTransformed = None

    # Normalizando cada feature para uma unidade uniforme (vetor unitário).

    featuresTransformed = normalize(features, axis = 0)

    # Instanciando um objeto da classe PCA para criar os componentes.

    pca = PCA(n_components = nComponents)

    # Capturando os componentes.

    featuresTransformed = pca.fit_transform(featuresTransformed)

    # Criando um DataFrame com os resultados obtidos.

    featuresTransformed = pd.DataFrame(data = featuresTransformed, columns = ['PCA_' + str(i) for i in range(0, nComponents)])

    # Atribuindo variável identificadora ao DataFrame transformado.

    featuresTransformed['train'] = train.values

    # Separando dados de treino e de teste transformados.

    trFeatures  = featuresTransformed[featuresTransformed['train'] == 1].drop('train', axis = 1)
    tstFeatures = featuresTransformed[featuresTransformed['train'] == 0].drop('train', axis = 1)

    # Retornando dados de treino e teste transformados.

    return trFeatures, tstFeatures

5.2 Extraindo Features dos conjuntos de dados

Iremos separar as variáveis preditoras, da variável a ser prevista, dentro do conjunto de dados.

In [ ]:
# Eliminando as variáveis altamente correlacionadas dos conjuntos de dados de treino e de teste.

dataTrain = dataTrain.drop(to_drop, axis = 1)
dataTest  = dataTest.drop(to_drop, axis = 1)
In [ ]:
# Capturando o nome das colunas do tipo categórico presentes no DataFrame.

categ = dataTrain.select_dtypes(['category']).columns

# Capturando as variáveis targets do conjunto de dados de treino e de teste.

trainTarget = dataTrain['churn_dummy']
testTarget  = dataTest['churn_dummy']

# Eliminando as variáveis target do conjunto de dados de treino e de teste.

trainFeatures = dataTrain.drop(labels = 'churn_dummy', axis = 1)
testFeatures  = dataTest.drop(labels = 'churn_dummy', axis = 1)

# Eliminando as variáveis categóricas do conjunto de dados de treino e de teste.

trainFeatures = trainFeatures.drop(labels = categ, axis = 1)
testFeatures  = testFeatures.drop(labels = categ, axis = 1)
In [ ]:
# Verificando as novas dimensões do DataFrame de treino.

trainFeatures.shape
Out[ ]:
(3333, 13)
In [ ]:
# Verificando as novas dimensões do DataFrame de teste.

testFeatures.shape
Out[ ]:
(1667, 13)

5.3 Aplicando diferentes escalas as Features de Treino e de Teste

Nesta etapa, iremos aplicar diferentes transformações, nas variáveis preditoras dos conjuntos de dados de treino e de teste.

In [ ]:
# Aplicando a transformação MinMaxScaler, as Features do conjunto de dados de treino e de teste.

trainFeaturesMM, testFeaturesMM = dataTransform (
    train     = trainFeatures,
    test      = testFeatures,
    transform = 'MM'
)
In [ ]:
# Aplicando a transformação StandardScaler, as Features do conjunto de dados de treino e de teste.

trainFeaturesSS, testFeaturesSS = dataTransform (
    train     = trainFeatures,
    test      = testFeatures,
    transform = 'SS'
)
In [ ]:
# Aplicando a transformação Yeo-Johnson, as Features do conjunto de dados de treino e de teste.

trainFeaturesNormDistribuition, testFeaturesNormDistribuition = dataTransform (
    train     = trainFeatures,
    test      = testFeatures,
    transform = 'ND'
)
In [ ]:
# Aplicando a transformação Normalize, as Features do conjunto de dados de treino e de teste.

trainFeaturesNormalized, testFeaturesNormalized = dataTransform (
    train     = trainFeatures,
    test      = testFeatures,
    transform = 'N'
)

5.4 Aplicando técnicas de Features Selection

Aplicaremos diferentes técnicas de Feature Selection, para determinar qual é a melhor combinação de variáveis preditoras a ser utilizada.

5.4.1 SelectKBest

Este método seleciona recursos de acordo com as k pontuações mais altas.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe SelectKBest, para selecionar as melhores variáveis preditoras.

skb = SelectKBest(chi2, k = 13)

# Capturando os scores das variáveis preditoras.

bestFeatuesSKB = skb.fit_transform(tFeatures, trainTarget)

# Capturando o nome das variáveis preditoras.

bfSkb = tFeatures.columns[skb.get_support()]

# Exibindo o nome das variáveis preditoras.

bfSkb
Out[ ]:
Index(['account_length', 'number_vmail_messages', 'total_day_minutes',
       'total_day_calls', 'total_eve_minutes', 'total_eve_calls',
       'total_night_minutes', 'total_night_calls', 'total_intl_minutes',
       'total_intl_calls', 'number_customer_service_calls', 'state_dummy',
       'international_plan_dummy'],
      dtype='object')
In [ ]:
# Criando um DataFrame, com os scores obtidos para cada uma das Features, segundo a técnica utilizada.

sc = pd.Series(skb.scores_, index = tFeatures.columns)

# Capturando os scores das variáveis preditoras.

sc = sc[skb.get_support()]

# Ordenando o Dataframe com os scores.

sc = sc.sort_values(ascending = False)
In [ ]:
# Plotando um gráfico de barras, dos scores gerados para as features, a partir da técnica utilizada.

plotBar (
    data        = sc,
    title       = 'Scores das melhores features com o SelectKBest', 
    yaxis       = 'Features', 
    xaxis       = 'Scores',
    orientation = 'h'
)

5.4.2 Information Gain

O Information gain ou Mutual information mede quanta informação a presença / ausência de um recurso contribui para fazer a previsão correta da variável target.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe mutual_info_classif.

bestFeatuesIG = mutual_info_classif(tFeatures, trainTarget, discrete_features = 'auto', n_neighbors = 3)

# Inserindo Scores obtidos em uma Série temporal.

scoreFeatures = pd.Series(bestFeatuesIG, index = tFeatures.columns)

# Capturando os scores das variáveis preditoras em ordem decrescente.

bfIg = scoreFeatures.sort_values(ascending = False)
In [ ]:
# Plotando um gráfico de barras, dos scores gerados para as features, a partir da técnica utilizada.

plotBar (
    data        = bfIg,
    title       = 'Scores das melhores features com o Information Gain', 
    yaxis       = 'Features', 
    xaxis       = 'Scores', 
    orientation = 'h'
)
In [ ]:
# Capturando o nome das variáveis preditoras.

bfIg = bfIg.index

# Exibindo o nome das variáveis preditoras.

bfIg
Out[ ]:
Index(['total_day_minutes', 'number_customer_service_calls',
       'international_plan_dummy', 'total_day_calls', 'total_intl_calls',
       'total_intl_minutes', 'number_vmail_messages', 'total_night_calls',
       'state_dummy', 'total_night_minutes', 'total_eve_calls',
       'total_eve_minutes', 'account_length'],
      dtype='object')

5.4.3 ANOVA F-value

Se os recursos forem categóricos, calcularemos uma estatística qui-quadrado entre cada recurso e a variável target. No entanto, se os recursos forem quantitativos, calcularemos a ANOVA F-Value entre cada recurso e a variável target.

As pontuações do F-Value examinam se, quando agrupamos a característica numérica pela variável target, as médias para cada grupo se tornam significativamente diferentes.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe SelectKBest para selecionar as melhores variáveis preditoras a partir dos
# scores ANOVA F-Values.

skb = SelectKBest(f_classif, k = 13)

# Capturando as variáveis preditoras.

bestFeatuesANOVA = skb.fit_transform(tFeatures, trainTarget)

# Capturando o nome das variáveis preditoras.

bfAnova = tFeatures.columns[skb.get_support()]

# Exibindo o nome das variáveis preditoras.

bfAnova
Out[ ]:
Index(['account_length', 'number_vmail_messages', 'total_day_minutes',
       'total_day_calls', 'total_eve_minutes', 'total_eve_calls',
       'total_night_minutes', 'total_night_calls', 'total_intl_minutes',
       'total_intl_calls', 'number_customer_service_calls', 'state_dummy',
       'international_plan_dummy'],
      dtype='object')
In [ ]:
# Criando uma Série Temporal com os scores obtidos para cada uma das Features segundo a técnica utilizada.

sc = pd.Series(skb.scores_, index = tFeatures.columns)

# Capturando os scores das variáveis preditoras.

sc = sc[skb.get_support()]

# Ordenando a Série Temporal em ordem decrescente dos scores.

sc = sc.sort_values(ascending = False)
In [ ]:
# Plotando um gráfico de barras, dos scores gerados para as features, a partir da técnica utilizada.

plotBar (
    data        = sc,
    title       = 'Scores das melhores features com o ANOVA F-value', 
    yaxis       = 'Features', 
    xaxis       = 'Scores', 
    orientation = 'h'
)

5.4.4 Forward Selection

O Forward Selection é um método iterativo, no qual começamos sem ter nenhum recurso no modelo. A cada iteração, adicionamos uma variável que melhora o modelo e efetuamos este procedimento até que a performance do modelo pare de evoluir.

A seleção de recursos começa avaliando todas as variáveis individualmente, e seleciona aquela que gera o algoritmo com o melhor desempenho, de acordo com um critério de avaliação predefinido. Em seguida, se avalia todas as combinações possíveis das variáveis já selecionadas e dos recursos ainda não escolhidos para definir a combinação que produz o algoritmo com a melhor performance, com base nos mesmos critérios predefinidos.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe SFS para selecionar as melhores variáveis preditoras segundo sua acurácia, 
# utilizando o algoritmo XGBClassifer.

sfs = SFS (
    estimator  = xgb.XGBClassifier(), 
    k_features = 13,
    forward    = True, 
    floating   = False, 
    verbose    = 2,
    scoring    = 'accuracy',
    cv         = 3
)

# Capturando as variáveis preditoras.

sfs = sfs.fit(
    X = tFeatures, 
    y = trainTarget
)
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.2s remaining:    0.0s
[Parallel(n_jobs=1)]: Done  13 out of  13 | elapsed:    2.4s finished

[2020-10-02 01:43:13] Features: 1/13 -- score: 0.8661866186618662[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.2s remaining:    0.0s
[Parallel(n_jobs=1)]: Done  12 out of  12 | elapsed:    2.9s finished

[2020-10-02 01:43:16] Features: 2/13 -- score: 0.885988598859886[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.3s remaining:    0.0s
[Parallel(n_jobs=1)]: Done  11 out of  11 | elapsed:    2.8s finished

[2020-10-02 01:43:18] Features: 3/13 -- score: 0.9051905190519052[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.3s remaining:    0.0s
[Parallel(n_jobs=1)]: Done  10 out of  10 | elapsed:    3.0s finished

[2020-10-02 01:43:21] Features: 4/13 -- score: 0.9201920192019202[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.3s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   9 out of   9 | elapsed:    2.9s finished

[2020-10-02 01:43:24] Features: 5/13 -- score: 0.922892289228923[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.4s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   8 out of   8 | elapsed:    2.9s finished

[2020-10-02 01:43:27] Features: 6/13 -- score: 0.9240924092409241[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.4s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   7 out of   7 | elapsed:    2.7s finished

[2020-10-02 01:43:30] Features: 7/13 -- score: 0.9225922592259227[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.4s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   6 out of   6 | elapsed:    2.4s finished

[2020-10-02 01:43:32] Features: 8/13 -- score: 0.924992499249925[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.4s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   5 out of   5 | elapsed:    2.1s finished

[2020-10-02 01:43:34] Features: 9/13 -- score: 0.9231923192319232[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.4s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   4 out of   4 | elapsed:    1.8s finished

[2020-10-02 01:43:36] Features: 10/13 -- score: 0.9351935193519352[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.5s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   3 out of   3 | elapsed:    1.4s finished

[2020-10-02 01:43:37] Features: 11/13 -- score: 0.9522952295229522[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.5s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   2 out of   2 | elapsed:    1.0s finished

[2020-10-02 01:43:38] Features: 12/13 -- score: 0.9522952295229524[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.5s remaining:    0.0s
[Parallel(n_jobs=1)]: Done   1 out of   1 | elapsed:    0.5s finished

[2020-10-02 01:43:39] Features: 13/13 -- score: 0.9525952595259527
In [ ]:
# Capturando o nome das variáveis preditoras.

bfSfs = tFeatures.columns[list(sfs.k_feature_idx_)]

# Exibindo o nome das variáveis preditoras.

bfSfs
Out[ ]:
Index(['account_length', 'number_vmail_messages', 'total_day_minutes',
       'total_day_calls', 'total_eve_minutes', 'total_eve_calls',
       'total_night_minutes', 'total_night_calls', 'total_intl_minutes',
       'total_intl_calls', 'number_customer_service_calls', 'state_dummy',
       'international_plan_dummy'],
      dtype='object')
In [ ]:
# Capturando os resultados obtidos pela Técnica Forward Selection.

sc = pd.DataFrame(sfs.get_metric_dict())

# Capturando os scores e o nome das Features, gerados a cada busca.

sc = sc.loc[['cv_scores', 'feature_names'], :].transpose()

# Capturando o nome das features utilizadas em cada avaliação.

featureNames = sc.feature_names

# Criando índices com o número de Features utilizadas em cada avaliação.

columns = [str(i) + ' Feature' if i == 1 else str(i) + ' Features' for i in range(1, sc.shape[0] + 1)]

# Remodelando os dados do DataFrame para serem plotados.

fs = pd.DataFrame()

for i in range(1, sc.shape[0] + 1):

    # Atribui os primeiros scores ao DataFrame, caso esteja vazio.

    if sc.empty:
        fs = pd.DataFrame(sc['cv_scores'][i], columns = [columns[i - 1]])
    else:
        fs[columns[i - 1]] = sc['cv_scores'][i]
In [ ]:
# Plotando os scores da acurácia, obtida pelas features selecionadas em cada fase de busca, segundo a técnica Forward Selection.

plotBoxplot (
    data   = fs,
    title  = 'Acurácia das melhores Features encontradas pelo técnica Forward Selection',
    xaxis  = 'Features selecionadas'
)
In [ ]:
# Transpondo a Série Temporal.

fs = fs.transpose()

# Criando uma nova coluna, com os nomes das Features utilizadas, em cada avaliação no DataFrame.

fs['featuresNames'] = [', '.join(f) for f in featureNames]

# Exibindo o nome das features utilizadas em cada avaliação.

fs[['featuresNames']]
Out[ ]:
featuresNames
1 Feature total_day_minutes
2 Features total_day_minutes, number_customer_service_calls
3 Features total_day_minutes, total_eve_minutes, number_c...
4 Features number_vmail_messages, total_day_minutes, tota...
5 Features number_vmail_messages, total_day_minutes, tota...
6 Features number_vmail_messages, total_day_minutes, tota...
7 Features number_vmail_messages, total_day_minutes, tota...
8 Features number_vmail_messages, total_day_minutes, tota...
9 Features number_vmail_messages, total_day_minutes, tota...
10 Features number_vmail_messages, total_day_minutes, tota...
11 Features number_vmail_messages, total_day_minutes, tota...
12 Features number_vmail_messages, total_day_minutes, tota...
13 Features account_length, number_vmail_messages, total_d...

5.4.5 Extra Trees Classifier

O Extremely Randomized Trees Classifier (Extra Trees Classifier) é um tipo de técnica de aprendizagem de conjunto que agrega os resultados de várias árvores de decisão descorrelacionadas coletadas em uma “floresta” para produzir seu resultado de classificação. Em conceito, é muito semelhante a um Classificador Random Forest e só difere na forma de construção das árvores de decisão na floresta.

Cada árvore de decisão na floresta de árvores extras é construída a partir da amostra de treinamento original. Então, em cada nó de teste, cada árvore é fornecida com uma amostra aleatória de k recursos do conjunto de recursos a partir do qual cada árvore de decisão deve selecionar o melhor recurso para dividir os dados com base em alguns critérios matemáticos (normalmente o índice de Gini). Essa amostra aleatória de recursos leva à criação de várias árvores de decisão não correlacionadas.

Para realizar a seleção de características usando a estrutura de floresta acima, durante a construção da floresta, para cada característica, a redução total normalizada nos critérios matemáticos usados ​​na decisão da característica de divisão (Índice de Gini se o Índice de Gini for usado na construção de floresta) é computado. Esse valor é chamado de Importância Gini do recurso. Para realizar a seleção de recursos, cada recurso é ordenado em ordem decrescente de acordo com a Importância Gini de cada recurso e o usuário seleciona os k principais recursos de acordo com sua escolha.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe ExtraTreesClassifier.

modelETC = ExtraTreesClassifier()

# Computando os scores de cada feature.

modelETC.fit (
    X = tFeatures, 
    y = trainTarget
)

# Inserindo Scores obtidos em uma Série Temporal.

featuresImpETC = pd.Series(data = modelETC.feature_importances_, index = tFeatures.columns)

# Ordenando o nome das variáveis preditoras segundo seu score em ordem decrescente.

bfEtc = featuresImpETC.sort_values(ascending = False)
In [ ]:
# Plotando um gráfico de barras, dos scores gerados para as features, a partir da técnica utilizada.

plotBar (
    data        = bfEtc, 
    title       = 'Scores das melhores features com o Extra Trees Classifier', 
    yaxis       = 'Features', 
    xaxis       = 'Scores', 
    orientation = 'h'
)
In [ ]:
# Capturando o nome das variáveis preditoras.

bfEtc = bfEtc.index

# Exibindo o nome das variáveis preditoras.

bfEtc
Out[ ]:
Index(['total_day_minutes', 'number_customer_service_calls',
       'total_eve_minutes', 'international_plan_dummy', 'total_intl_minutes',
       'total_intl_calls', 'total_night_minutes', 'total_day_calls',
       'account_length', 'total_night_calls', 'total_eve_calls', 'state_dummy',
       'number_vmail_messages'],
      dtype='object')

5.4.6 Random Forest Importance

O Random Forest, é um dos algoritmos de aprendizado de máquina mais populares. É um dos mais bem-sucedidos porque fornece, em geral, um bom desempenho preditivo, baixo overfitting e é de fácil interpretabilidade.

Essa interpretabilidade é dada pela facilidade de se derivar a importância de cada variável na árvore de decisão. Em outras palavras, é fácil calcular o quanto cada variável está contribuindo para a decisão do modelo.

O Random Forest consiste em 4-12 centenas de árvores de decisão, cada uma delas construída sobre uma extração aleatória das observações do conjunto de dados e uma extração aleatória das características. Nem toda árvore vê todas as características ou todas as observações, e isso garante que as árvores sejam descorrelacionadas e, portanto, menos sujeitas a sobreajuste. Cada árvore também é uma sequência de perguntas sim-não com base em um único recurso ou em uma combinação de recursos. Em cada nó (isto é em cada questão), os três dividem o conjunto de dados em 2 depósitos, cada um deles hospedando observações que são mais semelhantes entre si e diferentes das do outro bloco. Portanto, a importância de cada recurso é derivada do quão "puro" cada um dos blocos é.

Para classificação, a medida de impureza é a impureza de Gini ou o ganho / entropia de informação. Para regressão, a medida de impureza é a variância. Portanto, ao treinar uma árvore, é possível calcular o quanto cada recurso diminui a impureza. Quanto maior for a diminuição da impureza que um recurso gerar, mais importante ele será. Em florestas aleatórias, a diminuição da impureza de cada recurso pode ser calculada em média entre as árvores para determinar a importância final da variável.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe RandomForestClassifier.

rfImp = RandomForestClassifier (
    n_estimators = 200,
    random_state = 0
)

# Treinando o classificador com o conjunto de dados de treino.

rfImp.fit(
    X = tFeatures, 
    y = trainTarget
)

# Prevendo os scores das features dos dados de treino.

pred = rfImp.predict(tFeatures)

# Convertendo os scores para um DataFrame.

featuresImpRf = pd.Series(data = rfImp.feature_importances_, index = tFeatures.columns)

# Capturando os scores de cada uma das features.

bfRf = featuresImpRf.nlargest(13)
In [ ]:
# Plotando um gráfico de barras, dos scores gerados para as features, a partir da técnica utilizada.

plotBar (
    data        = bfRf,
    title       = 'Scores das melhores features com o Random Forest', 
    yaxis       = 'Features', 
    xaxis       = 'Scores', 
    orientation = 'h'
)
In [ ]:
# Capturando o nome das variáveis preditoras.

bfRf = bfRf.index

# Exibindo o nome das variáveis preditoras.

bfRf
Out[ ]:
Index(['total_day_minutes', 'number_customer_service_calls',
       'total_eve_minutes', 'international_plan_dummy', 'total_intl_minutes',
       'total_intl_calls', 'total_night_minutes', 'account_length',
       'total_day_calls', 'total_night_calls', 'total_eve_calls',
       'state_dummy', 'number_vmail_messages'],
      dtype='object')

5.4.7 PCA

A Análise de componente principal (Principal Component Analysis - PCA) é uma técnica de redução de dimensionalidade linear que pode ser utilizada para extrair informações de um espaço de alta dimensão projetando-as em um subespaço de dimensão inferior. Ele tenta preservar as partes essenciais que têm mais variação dos dados e remover as partes não essenciais com menos variação. As dimensões nada mais são do que recursos que representam os dados.

Uma coisa importante a se notar sobre o PCA é que é uma técnica de redução de dimensionalidade não supervisionada. Você pode agrupar os pontos de dados semelhantes com base na correlação de recursos entre eles sem qualquer supervisão (ou rótulos).

In [ ]:
# Aplicando a técnica PCA, para criar 10 Componentes, a partir dos dados de treino e de teste.

trainFeaturesPCA, testFeaturesPCA =  pcaTransform(train = trainFeatures, test = testFeatures)

5.4.8 RFE

RFE é um algoritmo de seleção de recurso do tipo wrapper. Isso significa que um algoritmo de aprendizado de máquina diferente é fornecido e usado no núcleo do método, é empacotado pelo RFE e usado para ajudar a selecionar recursos. Isso contrasta com as seleções de recursos com base em filtro que pontuam cada recurso e selecionam os recursos com a maior (ou menor) pontuação.

Tecnicamente, o RFE é um algoritmo de seleção de recursos no estilo wrapper que também usa a seleção de recursos com base em filtro internamente.

O RFE funciona procurando por um subconjunto de recursos começando com todos os recursos no conjunto de dados de treinamento e removendo com sucesso os recursos até que o número desejado permaneça.

Isso é obtido ajustando-se o algoritmo de aprendizado de máquina usado no núcleo do modelo, classificando os recursos por importância, descartando os recursos menos importantes e reajustando o modelo. Este processo é repetido até que um determinado número de recursos permaneça.

Os recursos são pontuados usando o modelo de aprendizado de máquina fornecido ou usando um método estatístico.

In [ ]:
# Definindo qual conjunto de dados, já escalado, deve ser utilizado.

tFeatures = trainFeaturesMM

# Instanciando um objeto da classe RFE para selecionar as melhores variáveis preditoras, utilizando o algoritmo XGBClassifer.

rfe = RFE (
    estimator            = xgb.XGBClassifier(), 
    n_features_to_select = 13
)

# Capturando as melhores variáveis preditoras.

rfeFit = rfe.fit (
    X = tFeatures, 
    y = trainTarget
)
In [ ]:
# Capturando o nome das variáveis preditoras.

bfRfe = tFeatures.columns[rfeFit.support_]

# Exibindo o nome das variáveis preditoras.

bfRfe
Out[ ]:
Index(['account_length', 'number_vmail_messages', 'total_day_minutes',
       'total_day_calls', 'total_eve_minutes', 'total_eve_calls',
       'total_night_minutes', 'total_night_calls', 'total_intl_minutes',
       'total_intl_calls', 'number_customer_service_calls', 'state_dummy',
       'international_plan_dummy'],
      dtype='object')

6. Modelagem Preditiva

6.1 Criando funções auxiliares

Iremos definir algumas funções, para executar as etapas de modelagem preditiva.

In [ ]:
# Definindo uma função, para treinar diferentes algoritmos, e prever a variável Target de um conjunto de dados.

def classifiersTraining(features, tTarget, printMeans = True, scoring = 'accuracy'):
    
    # Definindo os valores do seed e do número de folds.
    
    num_folds = 10
    seed      = 100

    # Criando uma lista, para armazenar os modelos que serão utilizados.

    models = []

    # Criando Listas para armazenar os resultados e os nomes de cada um dos algoritmos testados.

    results = []
    names   = []

    # Criando um Dataframe para armazenar a média e o desvio-padrão de cada um dos algoritmos testados.

    means   = pd.DataFrame(columns = ['mean', 'std'])

    # Adicionando os modelos a lista.

    models.append(('LR'      , LogisticRegression()         ))
    models.append(('LDA'     , LinearDiscriminantAnalysis() ))
    models.append(('NB'      , GaussianNB()                 ))
    models.append(('KNN'     , KNeighborsClassifier()       ))
    models.append(('CART'    , DecisionTreeClassifier()     ))
    models.append(('AdaBoost', AdaBoostClassifier()         ))
    models.append(('GB'      , GradientBoostingClassifier() ))
    models.append(('SVM'     , SVC()                        ))
    models.append(('RF'      , RandomForestClassifier()     ))
    models.append(('XGBoost' , xgb.XGBClassifier()          ))

    # Avaliando cada um dos modelos da lista de modelos.

    for name, model in models:
        
        # Instanciando um objeto da classe Kfold para criar os folds.

        kfold = KFold(n_splits = num_folds, random_state = seed)
        
        # Treinando o modelo com Cross Validation.

        cv_results = cross_val_score(model, features, tTarget, cv = kfold, scoring = scoring)
        
        # Adicionando os resultados gerados na lista de resultados.

        results.append(cv_results)
        
        # Adicionando o nome do modelo avaliado na lista de nomes.

        names.append(name)

        # Adicionando a média e o desvio-padrão, dos resultados gerados pelo modelo analisado, ao Dataframe de médias.

        means = means.append (
            pd.DataFrame (
                data    = [[cv_results.mean(), cv_results.std()]], 
                columns = ['mean', 'std'], 
                index   = [name]
            )
        )

        # Imprime uma mensagem, contendo os resultados obtidos ao fim do treinamento, de cada um dos modelos.

        if printMeans:

            # Cria a mensagem a ser impressa.

            msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
            
            # Imprime a mensagem.

            print(msg)

    # Cria um DataFrame, com os resultados obtidos por cada um dos modelos avaliados.

    results = pd.DataFrame(np.transpose(results), columns = names)

    # Retorna o DataFrame, com os resultados e com as médias geradas.
    
    return (results, means)
In [ ]:
# Definindo uma função, para realizar a plotagem de Confusions Matrix.

def plotConfusionMatrix(data, labels, figsize = (6, 6), fontScale = 1.2, 
                        title = 'Confusion Matrix', xlabel = 'Actual', ylabel = 'Predicted'):

    # Definindo a área de plotagem e suas dimensões.

    _, ax = plt.subplots(figsize = figsize)

    # Definindo o tamanho da fonte utilizada no gráfico.

    sns.set(font_scale = fontScale)

    # Criando Heatmap para representar a Confusion Matrix.

    ax = sns.heatmap (
        data       = data,
        annot      = True,
        cmap       = 'Blues',
        linewidths = 5,
        cbar       = False,
        fmt        = 'd'
    ) 

    # Definindo as labels e o título do gráfico. 

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel) 
    ax.set_title(title)

    # Definindo as ticklabels do gráfico.

    ax.xaxis.set_ticklabels(labels)
    ax.yaxis.set_ticklabels(labels);
In [ ]:
# Definindo uma função, para realizar o cálculo do Intervalo de Confiança, da acurácia e do erro de modelos 
# preditivos de classificação.

def CI(score, nElements,  ci = .95):

    # Define o Z-score, a ser utilizado para o Intervalo de Confiança selecionado.
    
    z = {
        .90  : 1.645,      # Para Intervalos com 90% de Confiança.
        .95  : 1.96,       # Para Intervalos com 95% de Confiança.
        .98  : 2.326,      # Para Intervalos com 98% de Confiança.
        .99  : 2.576,      # Para Intervalos com 99% de Confiança.
        .995 : 2.807,      # Para Intervalos com 99.5% de Confiança.
        .999 : 3.291       # Para Intervalos com 99.9% de Confiança.
    }

    # Calculando o range de variação do Intervalo.

    interval = z.get(ci) * np.sqrt( (score * (1 - score)) / nElements)

    # Retornando o Intervalo de Confiança obtido.

    return score - interval, score + interval
In [ ]:
# Definindo uma função, para criar uma Confusion Matrix.

def confusionMatrix(yTrue, yPred, labelPositive = 'Yes', labelNegative = 'False', classError = True):

    # Convertendo arrays para o tipo de dado categórico.

    yTrue = pd.Categorical(values = yTrue, categories = labels)
    yPred = pd.Categorical(values = yPred, categories = labels)

    # Transformando arrays em Séries Temporais.

    yPred = pd.Series(data = yPred, name = 'Predicted')
    yTrue = pd.Series(data = yTrue, name = 'Actual')

    # Criando a Confusion Matrix.

    cm = pd.crosstab(index = yPred, columns = yTrue, dropna = False)

    # Calculando os erros das classes da Confusion Matrix.

    if classError:
        
        # Capturando cada um dos valores da Confusion Matrix.

        truePositve, falsePositive, falseNegative, trueNegative = np.array(cm).ravel()

        # Criando um DataFrame contendo os erros das classes.

        ce = pd.DataFrame (
            data = [
                falsePositive / (truePositve + falsePositive),
                1 - trueNegative / (trueNegative + falseNegative)
            ],
            columns = ['classError'],
            index   = labels
        )

        # Inserindo no DataFrame, as colunas da Confusion Matrix.

        for c in range(cm.shape[1] - 1, -1, -1):
            
            # Inserindo as colunas no DataFrame.

            ce.insert(loc = 0, column = labels[c], value = cm[labels[c]])

        # Atribuindo índices e colunas ao DataFrame.
        
        ce.index   = pd.Series(ce.index, name = 'Predicted')
        ce.columns = pd.Series(ce.columns, name = 'Actual')

        # Retornando a Confusion Matrix com o erro das classes.
        
        return ce

    # Retornando Confusion Matrix.

    return cm
In [ ]:
# Definindo uma função, para calcular as métricas baseadas na Confusion Matrix.

def getClassificationMetrics(yTrue, predProb, labelPositive = 'Yes', labelNegative = 'No'):


    # Binarizando os scores obtidos nas previsões.
    
    yPred = [labelPositive if v >= 0.5 else labelNegative for v in predProb]
    
    # Convertendo arrays para o tipo categórico.

    labels = [labelPositive, labelNegative]

    yTrue = pd.Categorical(values = yTrue, categories = labels)
    yPred = pd.Categorical(values = yPred, categories = labels)

    # Convertendo arrays para o tipo numérico. 

    yNTrue = [1 if v == labelPositive else 0 for v in yTrue]
    yNPred = [1 if v == labelPositive else 0 for v in yPred]

    # Transformando arrays em Séries Temporais.

    yPred = pd.Series(data = yPred, name = 'Predicted')
    yTrue = pd.Series(data = yTrue, name = 'Actual')

    # Criando a Confusion Matrix.

    cm = confusionMatrix(yTrue, yPred, labelPositive = labelPositive, labelNegative = labelNegative, classError = False)

    # Capturando cada um dos valores da Confusion Matrix.

    truePositve, falsePositive, falseNegative, trueNegative = np.array(cm).ravel()

    # Calculando as métricas.

    accuracy     = accuracy_score(yTrue, yPred)
    accuracyCI   = CI(accuracy_score(yTrue, yPred), len(yTrue))
    kappa        = cohen_kappa_score(yTrue, yPred)
    sensitivity  = recall_score(yNTrue, yNPred)
    specificity  = trueNegative /(trueNegative + falsePositive)
    prevalence   = (truePositve + falseNegative) / len(yTrue)
    ppv          = (sensitivity * prevalence) /((sensitivity * prevalence) + ((1 - specificity) * (1 - prevalence)))
    npv          = (specificity * (1 - prevalence)) / (((1 - sensitivity) * prevalence) + ((specificity) * (1 - prevalence)))
    precision    = precision_score(yNTrue, yNPred)
    avgPrecision = average_precision_score(yNTrue, yNPred)
    dRate        = truePositve / len(yTrue)
    dPrevalence  = (truePositve + falsePositive) / len(yTrue)
    f1           = f1_score(yNTrue, yNPred)
    rocAuc       = roc_auc_score(yNTrue, predProb)
    error        = 1 - accuracy_score(yTrue, yPred)
    errorCI      = CI(error, len(yTrue))
    bAccuracy    = balanced_accuracy_score(yTrue, yPred)

    # Criando um DataFrame, com o resultado das métricas calculadas.

    metrics = pd.DataFrame([{
        'Accuracy'            : accuracy,     # Determina a precisão geral prevista do modelo.
        '95% CI for Accuracy' : accuracyCI,   # Determina um intervalo de confiança de 95% para a acurácia.
        'Kappa'               : kappa,        # Determina o coeficiente de Kappa.
        'Recall (Sensitivity)': sensitivity,  # Determina a proporção de registros positivos que foram classificados
                                              # pelo algoritmo  como positivos.
        'Specificity'         : specificity,  # Determina a proporção de registros negativos que foram classificados 
                                              # pelo algoritmo como negativos.
        'Pos Pred Value'      : ppv,          # Determina a porcentagem de positivos previstos que são realmente positivos.
        'Neg Pred Value'      : npv,          # Determina a porcentagem de negativos previstos que são realmente negativos.
        'Precision'           : precision,    # Determina a proporção de classificações positivas, que realmente 
                                              # são positivas.
        'Avarage Precision'   : avgPrecision, # Determina a precisão como a média ponderada de precisões alcançadas em 
                                              # cada limite.
        'Prevalence'          : prevalence,   # Determina a frequência com que a classe positiva realmente ocorre em 
                                              # nossa amostra.
        'Detection Rate'      : dRate,        # Determina a proporção de classificações positivas feitas corretamente em 
                                              # relação a todas as previsões feitas.
        'Detection Prevalence': dPrevalence,  # Determina o número de previsões positivas como uma proporção de 
                                              # todas as previsões.
        'F1'                  : f1,           # Determina a média Harmônica entre a precision e o recall do modelo.
        'ROC AUC'             : rocAuc,       # Determina a medida de separabilidade ROC. Ela indica o quanto o modelo é 
                                              # capaz de distinguir  as classes.   
        'Error'               : error,        # Determina o erro do modelo em relação a sua acurácia.
        '95% CI for Error'    : errorCI,      # Determina um intervalo de confiança de 95% para o erro.
        'Balanced Accuracy'   : bAccuracy,    # Determina a acurácia do modelo balanceada pelos tamanhos das classes.
        'Positive Class'      : labelPositive # Define qual classe é a classe positiva.
    }], index = ['Metrics']).transpose()

    # Retornando o DataFrame, com as métricas obtidas.

    return metrics
In [ ]:
# Definindo uma função para salvar um modelo preditivo já treinado.

def saveModel(name, model, fold = '/content/outputs/', ext = '.sav'):

    # Definindo o diretório e o nome do arquivo que será utilizado para salvar o modelo.

    dir = fold + name + ext

    # Salvando o modelo especificado.

    pickle.dump(model, open(dir, 'wb'))

    # Imprimindo mensagem de sucesso.

    print("Modelo salvo!")
In [ ]:
# Definindo uma função para carregar um modelo preditivo já treinado.

def loadModel(name, fold = '/content/outputs/', ext = '.sav'):

    # Definindo o diretório e o nome do arquivo que será utilizado para carregar o modelo.

    dir = fold + name + ext

    # Imprimindo mensagem de sucesso.

    print("Modelo carregado!")

    # Carregando o modelo especificado.

    return pickle.load(open(dir, 'rb'))

6.2 Criando modelos preditivos e avaliando suas Performances

Iremos criar modelos preditivos, com diferentes algoritmos, e com as Features em diferentes escalas. Também criaremos classificadores utilizando a técnica PCA. Por fim, selecionaremos os modelos que obtiveram as maiores acurácias.

In [ ]:
# Treinando classificadores, a partir dos componentes criados pela técnica PCA.

resultsPCA = classifiersTraining (
    features = trainFeaturesPCA, 
    tTarget  = trainTarget
)
LR: 0.855072 (0.026752)
LDA: 0.854171 (0.020045)
NB: 0.848778 (0.011336)
KNN: 0.884776 (0.016584)
CART: 0.903391 (0.015413)
AdaBoost: 0.879985 (0.013249)
GB: 0.942692 (0.012350)
SVM: 0.905778 (0.019567)
RF: 0.942091 (0.014846)
XGBoost: 0.943889 (0.014235)
In [ ]:
# Plotando os scores, da acurácia dos classificadores treinados, em boxplots.

plotBoxplot(data = resultsPCA[0])

O algoritmo XGBoost, foi o que obteve a melhor acurácia, para o conjunto de componentes do PCA.

In [ ]:
# Treinando classificadores, a partir da escala, e da técnica de Feature Selection utilizada.

resultsMM = classifiersTraining (
    features = trainFeaturesMM, 
    tTarget  = trainTarget
)
LR: 0.860169 (0.024142)
LDA: 0.853872 (0.020025)
NB: 0.846977 (0.012321)
KNN: 0.886872 (0.023782)
CART: 0.911492 (0.010917)
AdaBoost: 0.876981 (0.014699)
GB: 0.952895 (0.012456)
SVM: 0.897979 (0.017565)
RF: 0.948693 (0.013992)
XGBoost: 0.952893 (0.011247)
In [ ]:
# Plotando os scores, da acurácia dos classificadores treinados, em boxplots.

plotBoxplot(data = resultsMM[0])

O algoritmo XGBoost, foi o que obteve a melhor acurácia, para o conjunto dados transformados pelo algoritmo MinMaxScaler.

In [ ]:
# Treinando classificadores, a partir da escala, e da técnica de Feature Selection utilizada.

resultsSS = classifiersTraining (
    features = trainFeaturesSS, 
    tTarget  = trainTarget
)
LR: 0.861371 (0.020609)
LDA: 0.853872 (0.020025)
NB: 0.846977 (0.012321)
KNN: 0.891085 (0.020676)
CART: 0.916896 (0.011744)
AdaBoost: 0.876981 (0.014699)
GB: 0.952895 (0.012811)
SVM: 0.916586 (0.017675)
RF: 0.949292 (0.014626)
XGBoost: 0.952893 (0.011247)
In [ ]:
# Plotando os scores, da acurácia dos classificadores treinados, em boxplots.

plotBoxplot(data = resultsSS[0])

O algoritmo XGBoost, foi o que obteve a melhor acurácia, para o conjunto dados transformados pelo algoritmo StandardScaler.

In [ ]:
# Treinando classificadores, a partir da escala, e da técnica de Feature Selection utilizada.

resultsND = classifiersTraining (
    features = trainFeaturesNormDistribuition, 
    tTarget  = trainTarget
)
LR: 0.865569 (0.024494)
LDA: 0.856870 (0.022657)
NB: 0.846070 (0.019371)
KNN: 0.879982 (0.023912)
CART: 0.916890 (0.016784)
AdaBoost: 0.876981 (0.014699)
GB: 0.953195 (0.012683)
SVM: 0.907284 (0.019504)
RF: 0.947792 (0.015507)
XGBoost: 0.953194 (0.011178)
In [ ]:
# Plotando os scores, da acurácia dos classificadores treinados, em boxplots.

plotBoxplot(data = resultsND[0])

O algoritmo XGBoost, foi o que obteve a melhor acurácia, para o conjunto dados transformados pelo algoritmo Box-Cox.

In [ ]:
# Treinando classificadores, a partir da escala, e da técnica de Feature Selection utilizada.

resultsNorm = classifiersTraining (
    features = trainFeaturesNormalized, 
    tTarget  = trainTarget
)
LR: 0.855072 (0.026752)
LDA: 0.853872 (0.020025)
NB: 0.846977 (0.012321)
KNN: 0.882676 (0.023005)
CART: 0.912392 (0.012501)
AdaBoost: 0.876981 (0.014699)
GB: 0.952895 (0.012811)
SVM: 0.902479 (0.021994)
RF: 0.946291 (0.014628)
XGBoost: 0.952893 (0.011247)
In [ ]:
# Plotando os scores, da acurácia dos classificadores treinados, em boxplots.

plotBoxplot(data = resultsNorm[0])

O algoritmo XGBoost, foi o que obteve a melhor acurácia, para o conjunto dados transformados pelo algoritmo Normalize.

6.3 Realizando previsões para o conjunto de dados de teste

6.3.1 Otimizando Classificadores

Agora que já testamos diferentes algoritmos com diversas escalas, iremos escolher os melhores modelos criados e otimizar seus parâmetros.

6.3.1.1 Algoritmo Logistic Regression

Iremos buscar pelos melhores parâmetros, para criar um modelo com o algoritmo de Regressão Logística.

In [ ]:
# Definindo qual conjunto de dados de treino, já escalado, deve ser utilizado e a sua variável target.

trainX = trainFeaturesMM
trainY = trainTarget

# Definindo qual conjunto de dados de teste, já escalado, deve ser utilizado e a sua variável target.

testX = testFeaturesMM
testY = testTarget

# Definindo os valores que devem ser testados, em cada um dos parâmetros do modelo especificado.

paramGrid = dict (
    penalty      = ['l1', 'l2'],
    C            = [0.001, 0.01, 0.05, 0.1, 0.15, 1, 10, 100],
    solver       = ['lbfgs', 'liblinear'],
    random_state = [0],
    tol          = [0.0001, 0.0005],
    max_iter     = [13, 15, 30, 50, 100]
)

# Criando uma instância da classe do modelo de Regressão Logística.

model = LogisticRegression()

# Criando o grid, para fazer a busca dos melhores parâmetros para o modelo.

grid = GridSearchCV(estimator = model, param_grid = paramGrid, cv = 10, verbose = True, n_jobs = -1)

# Buscando pelos melhores parâmetros para o modelo.

grid.fit(trainX, trainY)

# Exibindo a configuração, do melhor modelo treinado.

print("\n" + "Melhores Parâmetros para o Modelo:" + "\n\n", grid.best_estimator_)
Fitting 10 folds for each of 320 candidates, totalling 3200 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=-1)]: Done 522 tasks      | elapsed:    4.4s
[Parallel(n_jobs=-1)]: Done 2982 tasks      | elapsed:   34.1s
Melhores Parâmetros para o Modelo:

 LogisticRegression(C=1, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=50,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=0, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
[Parallel(n_jobs=-1)]: Done 3200 out of 3200 | elapsed:   40.0s finished
In [ ]:
# Criando o modelo, com a melhor configuração encontrada.

classifierLR = LogisticRegression (
    penalty      = 'l2',
    C            = 1, 
    solver       = 'lbfgs',
    random_state = 0,
    tol          = 0.0001,
    max_iter     = 50,
)

# Treinando o modelo com os dados de treino.

classifierLR.fit(X = trainX, y = trainY)
Out[ ]:
LogisticRegression(C=1, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=50,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=0, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de treino.

scoreTrainLR = accuracy_score(trainY, classifierLR.predict(trainX))

# Visualizando o resultado.

print('Acurácia para os dados de treino: ' + str(scoreTrainLR))
Acurácia para os dados de treino: 0.8628862886288629
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de teste.

scoreTestLR = accuracy_score(testY, classifierLR.predict(testX))

# Visualizando o resultado.

print('Acurácia  para os dados de teste: ' + str(scoreTestLR))
Acurácia  para os dados de teste: 0.874625074985003
6.3.1.2 Algoritmo Random Forest

Iremos buscar pelos melhores parâmetros, para criar um modelo com o algoritmo Random Forest.

In [ ]:
# Definindo qual conjunto de dados de treino, já escalado, deve ser utilizado e a sua variável target.

trainX = trainFeaturesNormalized
trainY = trainTarget

# Definindo qual conjunto de dados de teste, já escalado, deve ser utilizado e a sua variável target.

testX = testFeaturesNormalized
testY = testTarget

# Definindo os valores que devem ser testados, em cada um dos parâmetros do modelo especificado.

paramGrid = dict (
    n_estimators      = [100, 105, 110, 115,120, 130], 
    max_depth         = [None, 2, 4, 6, 8],
    max_features      = ['auto', 'sqrt', 'log2'], 
    min_samples_split = [2, 5, 7, 9],
    min_samples_leaf  = [1, 2, 5]
)

# Criando uma instância da classe do modelo Random Forest.

model = RandomForestClassifier()

# Criando o grid, para fazer a busca dos melhores parâmetros para o modelo.

grid = GridSearchCV(estimator = model, param_grid = paramGrid, cv = 10, verbose = True, n_jobs = -1)

# Buscando pelos melhores parâmetros para o modelo.

grid.fit(trainX, trainY)

# Exibindo a configuração, do melhor modelo treinado.

print("\n" + "Melhores Parâmetros para o Modelo:" + "\n\n", grid.best_estimator_)
Fitting 10 folds for each of 1080 candidates, totalling 10800 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   22.9s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  1.7min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  3.8min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed:  6.6min
[Parallel(n_jobs=-1)]: Done 1246 tasks      | elapsed: 10.4min
[Parallel(n_jobs=-1)]: Done 1796 tasks      | elapsed: 15.0min
[Parallel(n_jobs=-1)]: Done 2446 tasks      | elapsed: 19.0min
[Parallel(n_jobs=-1)]: Done 3196 tasks      | elapsed: 21.8min
[Parallel(n_jobs=-1)]: Done 4046 tasks      | elapsed: 24.9min
[Parallel(n_jobs=-1)]: Done 4996 tasks      | elapsed: 29.1min
[Parallel(n_jobs=-1)]: Done 6046 tasks      | elapsed: 34.1min
[Parallel(n_jobs=-1)]: Done 7196 tasks      | elapsed: 40.1min
[Parallel(n_jobs=-1)]: Done 8446 tasks      | elapsed: 47.2min
[Parallel(n_jobs=-1)]: Done 9796 tasks      | elapsed: 56.0min
[Parallel(n_jobs=-1)]: Done 10800 out of 10800 | elapsed: 62.7min finished
Melhores Parâmetros para o Modelo:

 RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
                       criterion='gini', max_depth=None, max_features='auto',
                       max_leaf_nodes=None, max_samples=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=5,
                       min_weight_fraction_leaf=0.0, n_estimators=105,
                       n_jobs=None, oob_score=False, random_state=None,
                       verbose=0, warm_start=False)
In [ ]:
# Criando o modelo, com a melhor configuração encontrada.

classifierRF = RandomForestClassifier (
    n_estimators      = 100, 
    max_depth         = None,
    max_features      = 'log2', 
    min_samples_split = 9,
    min_samples_leaf  = 1
)

# Treinando o modelo com os dados de treino.

classifierRF.fit(X = trainX, y = trainY)
Out[ ]:
RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
                       criterion='gini', max_depth=None, max_features='log2',
                       max_leaf_nodes=None, max_samples=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=9,
                       min_weight_fraction_leaf=0.0, n_estimators=100,
                       n_jobs=None, oob_score=False, random_state=None,
                       verbose=0, warm_start=False)
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de treino.

scoreTrainRF = accuracy_score(trainY, classifierRF.predict(trainX))

# Visualizando o resultado.

print('Acurácia para os dados de treino: ' + str(scoreTrainRF))
Acurácia para os dados de treino: 0.9792979297929792
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de teste.

scoreTestRF = accuracy_score(testY, classifierRF.predict(testX))

# Visualizando o resultado.

print('Acurácia  para os dados de teste: ' + str(scoreTestRF))
Acurácia  para os dados de teste: 0.9526094781043791
6.3.1.3 Algoritmo Xgboost

Iremos buscar pelos melhores parâmetros, para criar um modelo com algoritmo Xgboost.

In [ ]:
# Definindo qual conjunto de dados de treino, já escalado, deve ser utilizado e a sua variável target.

trainX = trainFeaturesNormalized
trainY = trainTarget

# Definindo qual conjunto de dados de teste, já escalado, deve ser utilizado e a sua variável target.

testX = testFeaturesNormalized
testY = testTarget

# Definindo os valores que devem ser testados, em cada um dos parâmetros do modelo especificado.

paramGrid = dict (
    missing          = [np.nan],
    booster          = ['gbtree'],#, 'gblinear', 'dart'],
    max_depth        = [4, 5], 
    n_estimators     = [300, 350], 
    learning_rate    = [0.025, 0.03], 
    nthread          = [4], 
    subsample        = [0.95, 1], 
    colsample_bytree = [0.95, 1], 
    seed             = [100]
)

# Criando uma instância da classe do modelo Random Forest.

model = xgb.XGBClassifier()

# Criando o grid, para fazer a busca dos melhores parâmetros para o modelo.

grid = GridSearchCV(estimator = model, param_grid = paramGrid, cv = 10, verbose = True, n_jobs = -1)

# Buscando pelos melhores parâmetros para o modelo.

grid.fit(trainX, trainY)

# Exibindo a configuração, do melhor modelo treinado.

print("\n" + "Melhores Parâmetros para o Modelo:" + "\n\n", grid.best_estimator_)
Fitting 10 folds for each of 32 candidates, totalling 320 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   43.8s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  3.4min
[Parallel(n_jobs=-1)]: Done 320 out of 320 | elapsed:  5.7min finished
Melhores Parâmetros para o Modelo:

 XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=0.95, gamma=0,
              learning_rate=0.03, max_delta_step=0, max_depth=5,
              min_child_weight=1, missing=None, n_estimators=300, n_jobs=1,
              nthread=4, objective='binary:logistic', random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=100,
              silent=None, subsample=1, verbosity=1)
In [ ]:
# Criando o modelo, com a melhor configuração encontrada.

classifierXGB = xgb.XGBClassifier(   
    missing          = np.nan,
    booster          = 'gbtree',
    max_depth        = 5, 
    n_estimators     = 300, 
    learning_rate    = 0.03, 
    nthread          = 4, 
    subsample        = 1, 
    colsample_bytree = 0.95, 
    seed             = 100    
)

# Treinando o modelo com os dados de treino.

classifierXGB.fit(X = trainX, y = trainY)
Out[ ]:
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=0.95, gamma=0,
              learning_rate=0.03, max_delta_step=0, max_depth=5,
              min_child_weight=1, missing=None, n_estimators=300, n_jobs=1,
              nthread=4, objective='binary:logistic', random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=100,
              silent=None, subsample=1, verbosity=1)
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de treino.

scoreTrainXGB = accuracy_score(trainY, classifierXGB.predict(trainX))

# Visualizando o resultado.

print('Acurácia para os dados de treino: ' + str(scoreTrainXGB))
Acurácia para os dados de treino: 0.9792979297929792
In [ ]:
# Calculando a acurácia do modelo para o conjunto de dados de teste.

scoreTestXGB = accuracy_score(testY, classifierXGB.predict(testX))

# Visualizando o resultado.

print('Acurácia  para os dados de teste: ' + str(scoreTestXGB))
Acurácia  para os dados de teste: 0.9616076784643072

6.3.2 Avaliando as métricas do melhor classificador para os dados de teste

O melhor classificador treinado, utiliza o algoritmo XGBoost com as features normalizadas. Salvaremos as configurações desse modelo em um arquivo .sav.

In [ ]:
# Salvando o modelo preditivo especificado.

saveModel(name = 'classifierXGB', model = classifierXGB)
Modelo salvo!

Caso deseje pular as etapas anteriores de treinamento, execute o comando a seguir, para carregar o modelo já treinado.

In [ ]:
# Carregando o modelo preditivo especificado.

classifierXGB = loadModel(name = 'classifierXGB')
Modelo carregado!

Para analisar melhor a performance do modelo, precisamos determinar os valores das probabilidades geradas nas previsões.

In [ ]:
# Definindo qual conjunto de dados de treino, já escalado, deve ser utilizado e a sua variável target.

trainX = trainFeaturesNormalized
testX  = testFeaturesNormalized

# Realizando as predições das probabilidades, dos dados de treino e teste, para o modelo selecionado.

predTrainProb = classifierXGB.predict_proba(trainX)[:,1]
predTestProb  = classifierXGB.predict_proba(testX)[:,1]

Iremos binarizar as previsões, e os valores a serem previstos, dos conjuntos de dados de treino e teste.

In [ ]:
# Definindo as classes positiva e negativa da variável target.

labelPositive = 'Yes'
labelNegative = 'No'

# Criando uma lista com as categorias das classes.

labels = [labelPositive, labelNegative]

# Convertendo dados da variável target, dos dados de treino, para utilizar as labels especificadas.

trainTargetLabels = [labelPositive if t == 1 else labelNegative for t in trainTarget]
trainPredLabels   = [labelPositive if t >= 0.5 else labelNegative for t in predTrainProb]

# Convertendo dados da variável target, dos dados de teste, para utilizar as labels especificadas.

testTargetLabels = [labelPositive if t == 1 else labelNegative for t in testTarget]
testPredLabels   = [labelPositive if t >= 0.5 else labelNegative for t in predTestProb]
In [ ]:
# Criando uma Confusion Matrix para avaliar as previsões feitas para os dados de treino.

cm = confusionMatrix(yTrue = trainTargetLabels, yPred = trainPredLabels)

# Exibindo a Confusion Matrix.

cm
Out[ ]:
Actual Yes No classError
Predicted
Yes 414 0 0.000000
No 69 2850 0.023638

Podemos observar, que o modelo só apresenta falsos negativos para o conjunto de dados de treino. Isto é, o modelo tende a classificar indivíduos que realizaram o churn, como não o tendo feito.

In [ ]:
# Criando uma Confusion Matrix para avaliar as previsões feitas para os dados de teste.

cm = confusionMatrix(yTrue = testTargetLabels, yPred = testPredLabels)

# Exibindo a Confusion Matrix.

cm
Out[ ]:
Actual Yes No classError
Predicted
Yes 166 6 0.034884
No 58 1437 0.038796

Nos dados de teste, constatamos a ocorrência de falsos positivos, mas a proporção de falsos negativos continua sendo predominante.

In [ ]:
# Plotando a Confusion Matrix dos dados de teste em um gráfico.

plotConfusionMatrix (
    data   = cm.drop(labels= 'classError', axis = 1), 
    labels = labels
)

Vamos calcular algumas estatísticas, baseadas nos resultados gerados pelo modelo, para os dados de teste.

In [ ]:
# Calculando os scores de diferentes métricas, com base nas previsões geradas pelo modelo, para os dados de teste.

getClassificationMetrics(yTrue = testTargetLabels, predProb = predTestProb)
Out[ ]:
Metrics
Accuracy 0.961608
95% CI for Accuracy (0.9523838881869389, 0.9708314687416755)
Kappa 0.817026
Recall (Sensitivity) 0.741071
Specificity 0.995842
Pos Pred Value 0.965116
Neg Pred Value 0.961204
Precision 0.965116
Avarage Precision 0.750013
Prevalence 0.134373
Detection Rate 0.0995801
Detection Prevalence 0.103179
F1 0.838384
ROC AUC 0.929586
Error 0.0383923
95% CI for Error (0.029168531258324533, 0.04761611181306113)
Balanced Accuracy 0.868457
Positive Class Yes

Finalizamos esta análise, concluindo que o algoritmo XGBoost, gerou o modelo com a melhor acurácia. Os scores alcançados para os conjuntos de dados foram:

  • Dados de treino: 0.979298.
  • Dados de teste: 0.961608.

Entre em contato comigo!

Caso tenha alguma dúvida, sugestão ou apenas queira trocar uma ideia sobre este projeto, não hesite em entrar em contato comigo!