Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Um exercício de Machine Learning usando a base "SF Salaries" do Kaggle

+3 votos
42 visitas
perguntada Jul 8 em Ciência da Computação por claudiaeirado (46 pontos)  
editado Jul 8 por claudiaeirado

A base de dados SF Salaries está disponível em https://www.kaggle.com/kaggle/sf-salaries.
O objetivo é prever o valor do salário de um funcionário do governo de São Francisco, Califórnia a partir das informações fornecidas.

Compartilhe

1 Resposta

+1 voto
respondida Jul 8 por claudiaeirado (46 pontos)  
editado Jul 10 por claudiaeirado

Uma maneira de entender como o governo da cidade funciona é olhando para quem ele emprega e como seus funcionários são compensados.

Os dados estão no Kaggle e foram extraídos do site https://transparentcalifornia.com/salaries/san-francisco/.

O objetivo é prever o valor do salário de um funcionário do governo de São Francisco, Califórnia a partir das informações fornecidas.

Os dados estão compreendidos entre 2011 e 2014. As variáveis disponíveis são:
Id: código ID
EmployeeName: nome do funcionário
Jobtitle: cargo do funcionário
BasePay: salário-base
OvertimePay: pagamentos em hora-extra
OtherPay: outros pagamentos
Benefits: benefícios
TotalPay: salário total
TotalPayBenefits: salário total com benefícios
Year: ano
Notes: observações
Agency: agência (cidade em que foram coletados os dados)
Status: status
id: código id

O banco de dados possui 148.654 registros. Foi utilizada uma base de nome e sexo para criar a variável "gender" e enriquecer a base. A base de nomes que associa ao gênero está disponível no Kaggle aqui. Foi . Após cruzamento, das duas bases, foi criada base com 139.504 registros preenchidos.
As variáveis Notes é toda missing, id e ID são variáveis com o índices, Status tem 103.810 missings e não será utilizada.

Lendo a base de dados

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import seaborn as sns
import matplotlib.pyplot as plt
import random
import sklearn as sk

# Lendo o arquivo
salaries = pd.read_csv('Salaries.csv')
gender_base = pd.read_csv('gender.csv')
salaries_df2 = salaries.assign(name=salaries['EmployeeName'].apply(lambda x: x.split()[0].upper()))
salariesdf3 = salaries.assign(name=salaries['EmployeeName'].apply(lambda x: x.split()[0].upper()))
salaries = pd.merge(salaries_df2, gender_base, how='inner', on='name').drop('name', axis=1)


salariesdf = pd.merge(salariesdf3, gender_base, how='inner', on='name').drop('name', axis=1)

# Características do banco de dados: colunas, tipo de variáveis, qtde de observações
salaries.info()
salaries.head()
# Converte as colunas de pagamentos em numéricas
for col in ['BasePay', 'OvertimePay', 'OtherPay', 'Benefits']:
    salaries[col] = pd.to_numeric(salaries[col], errors='coerce')
for col in ['BasePay', 'OvertimePay', 'OtherPay', 'Benefits']:
    salariesdf[col] = pd.to_numeric(salaries[col], errors='coerce')
# Notes seems to be missing a lot of values
# drop it, with the drop method.  axis is either 0 for rows, 1 for columns
salaries = salaries.drop('Notes', axis=1)
salaries_df = salariesdf.drop('Notes', axis=1)

Estatísticas Descritivas

descritiva=salaries.describe()
pay_columns = salaries.columns[3:salaries.columns.get_loc('Year')]
pd.plotting.scatter_matrix(salaries[pay_columns], figsize=(8,8))
plt.show()


x=salaries[['TotalPayBenefits', 'Year']]
y=salaries[['TotalPayBenefits', 'gender']]
salaries.Year = salaries.Year.astype('category')
sns.boxplot('TotalPayBenefits', 'Year', data=salaries, palette="YlGnBu")
plt.show()
sns.boxplot('Benefits','Year', data=salaries, palette="YlGnBu")
plt.show()
sns.boxplot('OvertimePay','Year', data=salaries, palette="YlGnBu")
plt.show()
sns.boxplot('OvertimePay', 'gender', data=salaries, palette="BuPu")
plt.show()
sns.boxplot('TotalPayBenefits', 'gender', data=salaries, palette="BuPu")
plt.show()
sns.boxplot(data=salaries[['BasePay', 'OvertimePay', 'OtherPay', 'Benefits','TotalPayBenefits']], palette="PiYG")
fig=plt.gcf()
fig.set_size_inches(10,10)


salaries_groupby_gender=salaries.groupby('gender')
salaries_groupby_gender.describe()
sns.barplot(x='gender', y='EmployeeName', palette="BuPu",data=salaries_groupby_gender.count().reset_index())
plt.show()


salaries_groupby_agency=salaries.groupby('Agency')
salaries_groupby_agency.describe()
sns.barplot(x='Agency', y='EmployeeName', palette="BuPu",data=salaries_groupby_agency.count().reset_index())
plt.show()

#JobTitle
salaries['JobTitle'] = salaries['JobTitle'].apply(str.lower)
jobcount = salaries['JobTitle'].value_counts()[:25]
sns.barplot(x=jobcount, y=jobcount.keys())
plt.show()    

#Pie chart
pie_genders = salaries.groupby('gender').agg('count')
gender_labels = pie_genders.EmployeeName.sort_values().index
gender_counts = pie_genders.EmployeeName.sort_values()
colors = ['#D0F9B1', '#FEBFB3', '#007bff44']
explode = (0.1,0,0) 


plt.pie(gender_counts, labels=gender_labels, autopct='%.0f%%', colors=colors, startangle=90, explode=explode)
plt.title("Employers by Gender\n",fontsize=16)
plt.tight_layout()
plt.show()


# density
p1=sns.kdeplot(salaries.loc[salaries['Year']==2011, "TotalPayBenefits"], label="2011", shade=True, color='#007bff44')
p2=sns.kdeplot(salaries.loc[salaries['Year']==2012, "TotalPayBenefits"], label="2012", shade=True, color='#008B8B')
p3=sns.kdeplot(salaries.loc[salaries['Year']==2013, "TotalPayBenefits"], label="2013",shade=True, color='#4B0082')
p4=sns.kdeplot(salaries.loc[salaries['Year']==2014, "TotalPayBenefits"], label="2014",shade=True, color='#FEBFB3')
plt.title("Total Pay with Benefits Density by Year\n",fontsize=16)
plt.show()

p5=sns.kdeplot(salaries.loc[salaries['gender']=='Female', "TotalPayBenefits"], label="Female", shade=True, color='#FEBFB3')
p6=sns.kdeplot(salaries.loc[salaries['gender']=='Male', "TotalPayBenefits"], label="Male", shade=True, color='#007bff44')
p7=sns.kdeplot(salaries.loc[salaries['gender']=='Unisex', "TotalPayBenefits"], label="Unisex",shade=True, color='#D0F9B1')
plt.title("Total Pay with Benefits Density by Gender\n",fontsize=16)
plt.show()


p8=sns.kdeplot(salaries.loc[salaries['gender']=='Female', "OvertimePay"], label="Female", shade=True, color='#FEBFB3')
p9=sns.kdeplot(salaries.loc[salaries['gender']=='Male', "OvertimePay"], label="Male", shade=True, color='#007bff44')
p10=sns.kdeplot(salaries.loc[salaries['gender']=='Unisex', "OvertimePay"], label="Unisex",shade=True, color='#D0F9B1')
plt.title("Over Time Pay Density by Gender\n",fontsize=16)
plt.show()


p11=sns.kdeplot(salaries["BasePay"], shade=True, color='#007bff44')
p12=sns.kdeplot(salaries["OvertimePay"], shade=True, color='#008B8B')
p13=sns.kdeplot(salaries["OtherPay"],shade=True, color='#4B0082')
p14=sns.kdeplot(salaries["Benefits"],shade=True, color='#FEBFB3')
p15=sns.kdeplot(salaries["TotalPay"], shade=True, color='#D0F9B1')
p16=sns.kdeplot(salaries["TotalPayBenefits"],shade=True, color='#007bff44')
plt.title("Compesation Density\n",fontsize=16)
plt.show()



salaries_groupby_year=salaries.groupby('Year')
salaries_groupby_year.describe()
pie_year = salaries.groupby('Year').agg('mean')
year_labels = pie_year.TotalPayBenefits.sort_values().index
year_counts = pie_year.TotalPayBenefits.sort_values()
year_counts.describe()
mean_Y=salaries[["Year","TotalPay", "TotalPayBenefits"]].groupby("Year").mean()
mean_G=salaries[["gender","TotalPay", "TotalPayBenefits"]].groupby("gender").mean()
salaries.describe()


#Correlation
f,ax = plt.subplots(figsize=(5, 5))
sns.heatmap(salaries.corr(), annot=True, linewidths=.5, fmt= '.1f',ax=ax)
plt.show()

Eis os gráficos:

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

Pela análise, observa-se que as variáveis que podem ter algum efeito discriminante são year, gender, JobTitle. A variável "Agency" só possui observações de San Francisco e portanto, não é discriminante.
Os dados sugerem que talvez a diferença da distribuição da "TotalPayBenefits" no ano de 2011 para os demais, seja por falta de dados na variável "Benefits" em 2011.

Existe forte correlação 1 entre "TotalPay" e "TotalPayBenefits".O valor médio anual de salário total com benefícios é de US$ 83,783.41 para o gênero feminino e US$105,106.21.

A variável escolhida como target é TotalPayBenefits. Então as variáveis que serão utilizadas para predizer o salário são:

Z=salariesdf[["JobTitleLowercase", "Year", "gender"]]

Foram criadas as variáveis dummies para rodar a regressão linear e o svm, e foram utilizadas categorização das variáveis por codificação com LabelEncoder do sklearn.

def salaryEvolutionOf(jobTitle):
    variableJobTitleLowerCase = jobTitle.lower()
    salaries_subset = salaries
    salaries_subset["JobTitleLowercase"] = salaries["JobTitle"].str.lower()
    salaries_subset = salaries_subset.loc[salaries_subset["JobTitleLowercase"] == variableJobTitleLowerCase]
    if salaries_subset.empty: 
        print("Lower case title: ", variableJobTitleLowerCase)
        print("We have identified " , len(salaries_subset), " matches")
        return False
    plotTableMean = salaries_subset[["Year", "BasePay", "TotalPay", "TotalPayBenefits"]].groupby("Year").mean()
    plotTableMean.plot(kind="bar", title = "Mean")
    plotTableMedian = salaries_subset[["Year", "BasePay", "TotalPay", "TotalPayBenefits"]].groupby("Year").median()
    plotTableMedian.plot(kind="bar", title = "Median")
    print("Found " , len(salaries_subset), " matches for ", variableJobTitleLowerCase)
    return True

salaryEvolutionOf("Transit Operator")


def salaryEvolutionOf(jobTitle):
    variableJobTitleLowerCase = jobTitle.lower()
    salariesdf_subset = salariesdf
    salariesdf_subset["JobTitleLowercase"] = salariesdf["JobTitle"].str.lower()
    salariesdf_subset = salariesdf_subset.loc[salariesdf_subset["JobTitleLowercase"] == variableJobTitleLowerCase]
    if salariesdf_subset.empty: 
        print("Lower case title: ", variableJobTitleLowerCase)
        print("We have identified " , len(salariesdf_subset), " matches")
        return False
    plotTableMean = salariesdf_subset[["Year", "BasePay", "TotalPay", "TotalPayBenefits"]].groupby("Year").mean()
    plotTableMean.plot(kind="bar", title = "Mean")
    plotTableMedian = salariesdf_subset[["Year", "BasePay", "TotalPay", "TotalPayBenefits"]].groupby("Year").median()
    plotTableMedian.plot(kind="bar", title = "Median")
    print("Found " , len(salariesdf_subset), " matches for ", variableJobTitleLowerCase)
    return True

salaryEvolutionOf("Transit Operator")

#Based on the name, the title and the year I want to know the expected total pay with benefits

#Convert string to numbers using LabelEncoder
from sklearn.preprocessing import LabelEncoder
encoderJobTitle = LabelEncoder()
salaries["JobTitleLowercase"] = encoderJobTitle.fit_transform(salaries["JobTitleLowercase"] )
salaries.head()

encodergender= LabelEncoder()
salaries["gender"] = encoderJobTitle.fit_transform(salaries["gender"] )
salaries.head(20)


encodergendertest= LabelEncoder()
salaries["gendertest"] = encoderJobTitle.fit_transform(salaries["gendertest"] )
salaries.info()

Foi realizado o crossvalidation com balanceamento da base por gênero, com 20% para a base de teste e 80% para a base de treinamento, pois a base está classificada como 46% masculino, 34% feminino e 20% unissex. Importante lembrar que essa variável criada tentar imputar o gênero a partir do nome, então não representa efetivamente o gênero das pessoas.

#Split data set
from sklearn.model_selection import train_test_split
salaries_train_set, salaries_test_set = train_test_split(salaries, test_size = 0.2, random_state = 12345, stratify=salaries['gender'])
print("Splitted sets: ",len(salaries_train_set), "train +", len(salaries_test_set), "test")
#Correlation
corr_matrix = salaries_train_set.corr()
corr_matrix

X_test = salaries_test_set[["JobTitleLowercase", "Year", "gender"]]
X_train = salaries_train_set[["JobTitleLowercase", "Year","gender"]]
X_train.head()

y_test = salaries_test_set["TotalPayBenefits"]
y_train = salaries_train_set["TotalPayBenefits"]
y_train.head()

Foram testados os modelos de Random Forest Regressor, Lasso, Regressão Linear e SVM. Os ajustes não ficaram muito bons, pois há poucas variáveis disponíveis apenas, ano e cargo para predizer o rendimento anual da pessoa. Talvez fosse necessário enriquecer a base com mais informações como idade, sexo, empresa, grau de instrução. Os outros modelos tiveram ajuste não significante. A seguir apresentamos o único modelo que teve ajuste regular:

from sklearn.ensemble import RandomForestRegressor

clf = RandomForestRegressor()
clf.fit(X_train, y_train)

pred_train = clf.predict(X_train)
pred_test = clf.predict(X_test)

accuracy_train = clf.score(X_train, y_train)
accuracy_test = clf.score(X_test, y_test)

print("Accuracy train -> ", accuracy_train)
print("Accuracy test -> ", accuracy_test)

plt.figure(figsize=(6,6))
plt.scatter(pred_test, y_test, color='#FEBFB3', s = 0.2)
plt.xlim(-50,600000)
plt.ylim(-50, 600000)
plt.plot([-50, 600000], [-50, 600000], color='#007bff44', linestyle='-', linewidth=3)
plt.suptitle('Dados - Previsão x Observados ', fontsize=18)
plt.xlabel('Previsão - Random Forest', fontsize = 16)
plt.ylabel('Observado', fontsize=16)
plt.show()

A imagem será apresentada aqui.

A acurácia do modelo foi:

Accuracy train -> 0.7089239319484477
Accuracy test -> 0.6633903723242389

Sem o balanceamento da amostra o valor obtido havia sido:

Accuracy train -> 0.88
Accuracy test -> 0.62

comentou Jul 13 por Pedro Gomes (16 pontos)  
O código está muito bom, e muito bem explicado. Alguma forma de enriquecer a base de dados poderia ajudar muito na previsão.
Talvez criar labels para o primeiro nome das pessoas, alguns nomes aparecem com muita frequência (bryan, anne, andrew...).
Um label de 0 a 10 baseado na frequência com que o nome se repete poderia identificar grupos étnicos, nomes claramente não americanos tem frequência muito baixa na base de dados.
Talvez até seja possível identificar  diferença de idade  entre as pessoas , já que alguns nomes aparecem com muito mais frequência em certos anos, por exemplo, nomes mais "modernos" podem identificar pessoas mais jovens, e existe uma tendência de maior desemprego entre certas faixas de idade, etc.
...