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:










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 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