O funcionário sairá da empresa? Fazendo previsões com Machine Learning!

No primeiro projeto do “Jogando os Dados”, são utilizados os modelos de Regressão Logística, Floresta Aleatória e Redes Neurais Artificiais

Hernandes Matias Junior
Jogando os Dados
10 min readMar 13, 2021

--

Ilustração por Ilona RybakIlona Rybak.

Desde o final do ano passado venho estudando Ciência de Dados. O que antes era uma espécie de curiosidade, acabou virando uma paixão quando cheguei no Machine Learning. Embora tenha me debruçado no assunto há pouco tempo, de lá para cá sinto que já dei um salto e nisso veio a ideia de centralizar minha trajetória de aprendizagem em um lugar, no caso, o Medium. O objetivo desse espaço que batizei carinhosamente de “Jogando os Dados” é documentar meus estudos, consolidar o meu conhecimento e também ajudar aqueles que, assim como eu, deram início nessa caminhada sensacional pela Ciência de Dados.

Para esse primeiro projeto envolvendo People Analytics, utilizei uma base de dados fictícia criada pela IBM e disponibilizada no Kaggle. Você pode acessar o dataset aqui.

Ao longo do projeto, vou considerar que você já tem algum conhecimento sobre Ciência de Dados. Vale lembrar que sou um estudante. Qualquer sugestão que você tiver sobre o meu código, pode entrar em contato comigo pelo meu LinkedIn. Nosso conhecimento se expande quando o compartilhamos, e a Ciência de Dados é uma área que possui espaço para todo mundo.

Todo o desenvolvimento será realizado utilizando Python, dentro do Jupyter Notebook.

Vamos aos códigos!

1. Entendendo o problema de negócio

É sabido que a rotatividade de pessoal é uma das principais preocupações das empresas hoje. O processo de recrutamento representa uma despesa considerável, e para se substituir um funcionário leva-se tempo, não apenas para se encontrar um novo profissional, mas também para treiná-lo, e para que ele consiga chegar no mesmo nível de performance do colaborador anterior. Mas e se as empresas conseguirem identificar, antes de efetivar a contratação, quais candidatos estão mais propensos a trocá-las por uma nova organização? Nesse projeto o objetivo é exatamente esse.

Utilizando de dados históricos de funcionários que deixaram e permaneceram na empresa, vamos identificar, usando modelos de Machine Learning, quais candidatos estão mais propensos a se desligarem, para que o gestor de área possa atuar diretamente, seja melhorando seu salário, benefícios ou apresentando uma proposta de crescimento vertical na carreira.

2. Importando as bibliotecas e o dataset

As bibliotecas utilizadas nesse projeto são o pandas e o numpy, para o manuseio dos dados; seaborn e matplotlib para a visualização; o scikitlearn e o tensorflow para o treinamento, teste e avaliação do modelo de Machine Learning; e, por fim, o pickle, para a exportação do modelo.

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import precision_score, recall_score, f1_score, classification_report, accuracy_score, confusion_matrix
import tensorflow as tf
import pickle
sns.set()%matplotlib inline

Como eu coloquei meu dataset em um diretório diferente do Jupyter Notebook, passei o caminho completo para que ele fosse encontrado. Para esse dataframe, dei o nome de “employee_df”:

employee_df = pd.read_csv(r”C:\Users\herna\OneDrive\Ciência de Dados para Negócios\RH\Employee.csv”)

Utilizando o “employee_df.shape”, verifiquei que meu dataframe possui 1470 linhas e 35 colunas. Com o “employee_df.head()” pude ter uma visão dos meus dados.

Se trata de dados autoexplicativos dos funcionários, como idade, gênero, departamento, formação acadêmica, cargo, salário, e nível de satisfação. Recomendo que, caso queira acessar os dados de forma detalhada, acesse meu Github.

Uma das colunas desse dataframe tem o nome “Attrition”, com os dados “Yes” e “No”. Essa coluna é o nosso atributo, e se refere se o funcionário saiu ou permaneceu na empresa. Nosso trabalho é prever, para novos funcionários, justamente o atributo “Attrition”.

3. Análise exploratória dos dados

Modelos de Machine Learning utilizam fórmulas matemáticas, e por isso é necessário que seja feito uma transformação nos dados, passando-os de dados categóricos para dados numéricos. Normalmente, essa transformação fica na etapa de pré-processamento, mas aqui adianto para termos uma visão de todos os dados na visualização de histograma.

Portanto, converti os dados categóricos de Yes e No, das colunas “Attrition”, “OverTime” e “Over18”, para valores binários de 1 e 0. O código fica assim:

employee_df[‘OverTime’] = employee_df[‘OverTime’].apply(lambda x: 1 if x ==’Yes’ else 0)

employee_df[‘Over18’] = employee_df[‘Over18’].apply(lambda x: 1 if x ==’Y’ else 0)

employee_df[‘Attrition’] = employee_df[‘Attrition’].apply(lambda x: 1 if x ==’Yes’ else 0)

employee_df[‘OverTime’] = employee_df[‘OverTime’].apply(lambda x: 1 if x ==’Yes’ else 0)

employee_df[‘Over18’] = employee_df[‘Over18’].apply(lambda x: 1 if x ==’Y’ else 0)

Antes de irmos para a visualização dos dados, checamos de existem dados faltantes(null) em nosso dataframe. Para isso, utilizei a função “Heatmap” da biblioteca seaborn.

sns.heatmap(employee_df.isnull(), cbar=False)

Não foram encontrados dados faltantes, portanto não foi necessário nenhum tipo de transformação.

Agora partindo para a visualização de dados, utilizei o “Histograma”.

employee_df.hist(bins=30, figsize=(20,20), color = ‘r’);

Algumas conclusões que cheguei com o histograma:

  • A maioria dos funcionários possui idade entre 30 e 40 anos;
  • Os funcionários moram majoritariamente perto da empresa;
  • O capital intelectual da empresa tem como principais formações “Bachelor” e “Master”;
  • Mais da metade dos colaboradores se encontra em cargos iniciais, e isso reflete na remuneração;
  • “Very High” e “High” são os que possuem maior quantidade quando se trata da satisfação dos funcionários, mas “Low” e “Medium” também são relevantes.

Separei meu dataframe para verificar a quantidade de funcionários que saíram e a quantidade dos funcionários que permaneceram na empresa:

left_df = employee_df[employee_df[‘Attrition’]==1]
stayed_df = employee_df[employee_df[‘Attrition’]==0]

print(‘Total: ‘, len(employee_df))
print(‘Número de funcionários que saíram da empresa= ‘, len(left_df))
print(‘Porcentagem de funcionários que saíram da empresa= ‘, (len(left_df) / len(employee_df))*100)
print(‘Número de funcionários que ficaram na empresa= ‘, len(stayed_df))
print(‘Porcentagem de funcionários que ficaram na empresa= ‘, (len(stayed_df) / len(employee_df))*100)

O resultado foi que o dataframe é desbalanceado, com os funcionários que saíram representando 16%, e os que permaneceram, 84. Isso prejudicará o treinamento do nosso modelo de Machine Learning.

Realizei uma série de visualizações para identificar o perfil de quem saiu e quem permaneceu na empresa:

plt.figure(figsize=(25,12))
sns.countplot(x=’Age’, hue=’Attrition’, palette=’inferno’, data=employee_df)

plt.figure(figsize=(20,20))
plt.subplot(411)
sns.countplot(x=’JobRole’, hue=’Attrition’,palette=’inferno’, data=employee_df)
plt.subplot(412)
sns.countplot(x=’MaritalStatus’, hue=’Attrition’, palette=’inferno’,data=employee_df)
plt.subplot(413)
sns.countplot(x=’JobInvolvement’, hue=’Attrition’, palette=’inferno’,data=employee_df)
plt.subplot(414)
sns.countplot(x=’JobLevel’, hue=’Attrition’, palette=’inferno’,data=employee_df)

Algumas conclusões que cheguei com as visualizações:

  • Pessoas mais jovens entre 18 e 26 anos são as mais propensas a saírem da empresa;
  • O cargo de “Sales Representative” é o que mais possui representatividade de evasão, enquanto o “Manager” é o que mais possui representatividade de permanência;
  • Como pessoas mais jovens são as que mais saem, o estado civil “Single” é o que registra maior saída, assim como os níveis iniciais de cargo e função.

Utilizando o “Kernel Density Estimate”, não encontrei relação entre o “Attrition” e o “DistanceFromHome”, o que foi uma surpresa, pois acreditava que pessoas que moram muito longe do trabalho, tendem a procurar por um mais próximo.

plt.figure(figsize=(20,9))
sns.kdeplot(left_df[‘DistanceFromHome’], label=’Funcionários que saíram da empresa’, shade=True, color=’r’)
sns.kdeplot(stayed_df[‘DistanceFromHome’], label=’Funcionários que ficaram na empresa’, shade=True, color=’b’)

O mesmo aconteceu com o “TotalWorkingYears”:

plt.figure(figsize=(20,9))
sns.kdeplot(left_df[‘TotalWorkingYears’], label=’Funcionários que saíram da empresa’, shade=True, color=’r’)
sns.kdeplot(stayed_df[‘TotalWorkingYears’], label=’Funcionários que ficaram na empresa’, shade=True, color=’b’)

Muitas vezes o motivo pela mudança de emprego é pelo funcionário estar buscando uma remuneração maior. Vamos verificar se essa relação apareci nos dados:

plt.figure(figsize=(15, 10))
sns.boxplot(x = ‘MonthlyIncome’, y = ‘JobRole’, palette=’hsv’, data=employee_df);

De fato, o “Sales Representative”, cargo que apresenta maior representatividade de evasão, é justamente o cargo que possui a menor remuneração. O aposto acontece com o “Manager”. Portanto, quanto maior a remuneração, maior também a permanência.

4. Pré-processamento de dados

Como dito anteriormente, é necessário converter dados categóricos em dados numéricos, antes de submeter ao treinamento do modelo. Separei meus dados categóricos em um dataframe e fiz a conversão utilizando o OneHotEncoder, importado da biblioteca “Scikit Learn”:

X_cat = employee_df[[‘BusinessTravel’, ‘Department’, ‘EducationField’, ‘Gender’, ‘JobRole’, ‘MaritalStatus’]]
onehotencoder = OneHotEncoder()
X_cat = onehotencoder.fit_transform(X_cat).toarray()

Separei também os meus dados que já eram numéricos:

X_numerical = employee_df[[‘Age’, ‘DailyRate’, ‘DistanceFromHome’, ‘Education’, ‘EnvironmentSatisfaction’, ‘HourlyRate’, ‘JobInvolvement’, ‘JobLevel’, ‘JobSatisfaction’, ‘MonthlyIncome’, ‘MonthlyRate’, ‘NumCompaniesWorked’, ‘OverTime’, ‘PercentSalaryHike’, ‘PerformanceRating’, ‘RelationshipSatisfaction’, ‘StockOptionLevel’, ‘TotalWorkingYears’ ,’TrainingTimesLastYear’ , ‘WorkLifeBalance’, ‘YearsAtCompany’ ,’YearsInCurrentRole’, ‘YearsSinceLastPromotion’, ‘YearsWithCurrManager’]]

E concatenei, unindo os dois dataframes em apenas um:

X_all = pd.concat([X_cat, X_numerical], axis = 1)

Com os atributos numéricos, utilizei o MinMaxScaler para que o modelo de machine learning os considere com o mesmo grau de importância:

scaler = MinMaxScaler()

X = scaler.fit_transform(X_all)

5. Aplicação dos modelos de Machine Learning

Recapitulando, o objetivo do modelo é prever o atributo “Attrition”, se o funcionário sairá ou não da empresa.

Com minha função train_test_split importada da biblioteca “Scikit Learn”, dividi os meus dados reservando 30% deles para o meu teste:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

O meu modelo será desenvolvido utilizando os dados de “treino”. Após isso, submeto meus dados de “teste” no meu modelo treinado, dados de “teste” que eu já sei a resposta, portanto vou verificar a acurácia do meu modelo.

Existem várias formas de se mensurar a precisão do modelo, e aqui representarei apenas a Confusion Matrix. Para visualizar a avaliação mais detalhada, visite o notebook do modelo no meu Github.

Regressão Logística

Diferente da Regressão Linear, utilizada para prever dados numéricos, por exemplo, de vendas, a Regressão Logística nos retorna um resultado binário, como aprovado/reprovado, bom/ruim, e sim/não, exatamente o resultado que buscamos nesse modelo.

Abaixo o treinamento do modelo e sua avaliação:

logistic = LogisticRegression()
logistic.fit(X_train, y_train)

y_pred = logistic.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

sns.heatmap(cm,annot=True,cbar = False, fmt=”d”)

A Confusion Matrix ao lado, pode ser interpretada da seguinte forma:

O eixo X é a resposta correta, e o eixo Y é o que o meu modelo previu.

Dos 369 funcionários que permaneceram na empresa, o modelo previu esse acontecimento em 360 deles, uma acurácia de 98%.

Dos 72 funcionários que saíram da empresa, o modelo previu esse acontecimento em 38 deles, uma acurácia de apenas 53%. Isso acontece porque meu dataframe está desbalanceado, como dito anteriormente.

Floresta Aleatória

Seguindo metodologia parecia com a utilizada no modelo anterior e estando com a função já importada da biblioteca “Scikit Learn”, temos:

forest = RandomForestClassifier(n_estimators=100)
forest.fit(X_train, y_train)

y_pred = forest.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

sns.heatmap(cm, annot=True, cbar = False, fmt=”d”)

A Confusion Matrix ao lado, pode ser interpretada da seguinte forma:

O eixo X é a resposta correta, e o eixo Y é o que o meu modelo previu.

Dos 369 funcionários que permaneceram na empresa, o modelo previu esse acontecimento em 367 deles, uma acurácia de quase 100%.

Dos 72 funcionários que saíram da empresa, o modelo previu esse acontecimento em 38 deles, uma acurácia de apenas 20%. O desempenho então foi pior que o modelo de Regressão Logística.

Redes Neurais Artificiais

Diferente dos outros modelos em que utilizei a biblioteca “Scikit Learn”, nas Redes Neurais utilizei o “Tensorflow”. Confesso que esse é um modelo novo para mim, e sugiro que qualquer pessoa que queira se aprofundar sobre Machine Learning, dê uma atenção a Redes Neurais. Aqui, utilizei 25 neurônios, que é meu número de entradas do treino dividido por dois. A saída é apenas 1 neurônio, que é o atributo que eu busco.

Criando as camadas das Redes Neurais:

rede_neural = tf.keras.models.Sequential()
rede_neural.add(tf.keras.layers.Dense(units=25, activation=’relu’, input_shape=(50,)))
rede_neural.add(tf.keras.layers.Dense(units=25, activation=’relu’))
rede_neural.add(tf.keras.layers.Dense(units=25, activation=’relu’))
rede_neural.add(tf.keras.layers.Dense(units=1, activation=’sigmoid’))

Compilando:

rede_neural.compile(optimizer=’Adam’, loss=’binary_crossentropy’, metrics=[‘accuracy’])

rede_neural.fit(X_train, y_train, epochs=200)

Avaliando o modelo:

y_pred = rede_neural.predict(X_test)

O resultado dessa minha predição vem com notação científica. Precisei criar uma regra para, se for maior ou igual a 0,5, retornar 1, do contrário, 0. Segue o raciocínio do atributo “Attrition”.

y_pred = (y_pred >= 0.5)

cm = confusion_matrix(y_test, y_pred)
cm

sns.heatmap(cm, annot=True,cbar=False,fmt=”d”)

A Confusion Matrix ao lado, pode ser interpretada da seguinte forma:

O eixo X é a resposta correta, e o eixo Y é o que o meu modelo previu.

Dos 369 funcionários que permaneceram na empresa, o modelo previu esse acontecimento em 342 deles, uma acurácia de 93%.

Dos 72 funcionários que saíram da empresa, o modelo previu esse acontecimento em 32 deles, uma acurácia de apenas 44%. O desempenho então foi pior que o modelo de Regressão Logística.

6. Salvando o modelo

Diante dos resultados encontrados, o modelo que apresentou mais precisão foi o de Regressão Logística. Agora, o que precisa ser feito é salvar esse modelo para que ele possa ser colocado em produção pelo pessoal do front-end, quando falamos de uma empresa.

Para salvar o modelo, utilizamos o “Pickle”. Damos um nome para o arquivo, no caso “variaveis_modelo.pk1”, e, como estamos com o objetivo de gravar o modelo em disco, utilizamos a função “wb”. Em seguida apontamos o que queremos salvar. Nesse caso, é o “logistic”, e as funções que utilizamos no pré-processamento, “scaler” e “onehotencoder”.

with open(‘variaveis_modelo.pkl’, ‘wb’) as f:
pickle.dump([scaler, onehotencoder, logistic], f)

Próximos passos

O modelo praticamente não errou quando se tratava de apontar quais funcionários permanecerão na empresa. No entanto, a grande questão era definir quais funcionários possuem potencial de sair.

O dataset estava desbalanceado. Nesse caso, faz-se necessário mais inputs de funcionários que saíram da empresa, para colocarmos os dados em treinamento.

Também é interessante que o RH trabalhe com mais pesquisas e forneça atributos para que possamos trabalhar com mais variáveis.

--

--

Hernandes Matias Junior
Jogando os Dados

Engenheiro, Administrador pela PUC Minas e pós-graduando em Data Science e Analytics pela USP.