"""
<font size='5'><b>1. Importação dos dados</b></font> <br>
Primeiramente, importamos a biblioteca pandas para facilmente ler os conteúdos csv de teste e de treino.
"""
import pandas as pd
dados_de_teste = pd.read_csv("../input/adult-pmr3508/test_data.csv", na_values="?")
dados_de_teste.head()
dados_de_treino = pd.read_csv("../input/adult-pmr3508/train_data.csv", na_values="?")
dados_de_treino.head()
"""
Antes de analisar os dados faltantes com cuidado, pode valer a pena eliminar diretamente os indivíduos com dados faltantes de forma a fazer gráficos de análise realistas.
"""
dados_de_treino = dados_de_treino.dropna()
dados_de_teste = dados_de_teste.dropna()
"""
<font size='5'><b>2. Análise de dados</b></font> <br>
Importando as bibliotecas necessárias:
"""
from sklearn.preprocessing import LabelEncoder
import seaborn as sns
import matplotlib as plt
"""
Primeiramente, iremos análisar a correlação entre as variáveis numéricas. Como nos interessamos no atributo "income", já o transformaremos em numérico. 
"""
dados_de_treino['income'] = LabelEncoder().fit_transform(dados_de_treino['income'])
sns.heatmap(dados_de_treino.corr(), annot = True)
"""
É muito claro observando a coluna "income" que os atributos Id e fnlwgt não influenciam relevantemente a nossa variável de interesse. Retiremos portanto elas dos dados.
"""
dados_de_treino = dados_de_treino.drop(columns=["Id", "fnlwgt"])
dados_de_teste = dados_de_teste.drop(columns=["Id", "fnlwgt"])
"""
Novo heatmap:
"""
sns.heatmap(dados_de_treino.corr(), annot = True)
"""
<font size='4'><b>2.1 Gráficos e estatísticas</b></font> <br>
Primeiramente, vejamos quantos porcento dos dados de interesse são maiores que 50k (indicado por 1) e menores (0):
"""
dados_de_treino['income'].value_counts()/dados_de_treino['income'].count() * 100
"""
Partamos para a análise gráfica. O método principal que ultilizaremos para facilitar a interpretação dos dados é a continuização destes através da média. Quanto mais próximo de 1, mais indivíduos com o atributo observado possuem salário anual >50k, enquanto quanto mais próximo de 0, menos têm. Primeiro, a análise por sexo:
"""
dados_de_treino.groupby(['sex'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por sexo")
dados_de_treino['sex'].value_counts()/dados_de_treino['sex'].count() * 100
"""
Vemos que os homens tem uma tendência maior a ter salários altos do que mulheres, embora mais homens tenham sido analisados, indicando um possível viés dos dados.
"""
dados_de_treino.groupby(['race'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por raça")
dados_de_treino['race'].value_counts()/dados_de_treino['race'].count() * 100
"""
O gráfico parece indicar que brancos e asiáticos tem maiores tendências a ganhar salários altos, porém os dados estão concentrados em pessoas brancas, o que novamente pode indicar viés.
"""
dados_de_treino.groupby(['native.country'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por país de origem")
dados_de_treino['native.country'].value_counts()/dados_de_treino['income'].count() * 100
"""
Em primeira análise, o gráfico parece justo, bem distribuido. Porém quando olhamos para a concentração de dados do país de origem, encontramos uma concentração enorme nos Estados Unidos, o que pode comprometer a credibilidade dos outros dados.
"""
dados_de_treino.groupby(['education'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por nível educacional")
dados_de_treino.groupby(['education.num'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por anos de estudo")
dados_de_treino['education'].value_counts()/dados_de_treino['education'].count() * 100
"""
Analisamos nível educacional e anos de estudo juntos pois são dados muito intimamente relacionados. A análise dos dados também parece indicar o já esperado, niveis educacionais maiores e mais anos de estudo profissional tendem a proporcionar melhores chances de um alto salário.
"""
dados_de_treino.groupby(['marital.status'])['income'].aggregate('mean').plot(kind='bar', ylabel="média de income", title="Tendência a salário >50k por status de relacionamento")
dados_de_treino['marital.status'].value_counts()/dados_de_treino['marital.status'].count() * 100
"""
Podemos ver que os tipos de casamento que possivelmente tornam mais lucrativas as familias são o casamento civil e o "Married-AF-spouse". Analisando as frequências de cada atributo no entanto vemos que este segundo talvez não seja um dado confiável, visto que ele é observado em apenas 0,07% dos indivíduos.
"""
idades = list(set(dados_de_treino['age'].to_list())) #retira valores duplicados
medias_income = dados_de_treino.groupby(['age'])['income'].aggregate('mean').to_list()
plt.pyplot.title("Média de salário >50k por idade")
plt.pyplot.xlabel("age")
plt.pyplot.ylabel("Média de income")
plt.pyplot.scatter(x=idades, y=medias_income)
dados_de_treino['age'].value_counts().sort_index()[40:72]
"""
A análise das idades parece indicar que pessoas de por volta de 50 anos tendem a ter melhores salários. Há certas idades na faixa dos 75-85 anos que parecem superar as de 50 porém, observando atentamente, vemos que o número de casos destas idades é muito pequeno e provavelmente se tratam de outliers.
"""
dados_de_treino.groupby(['workclass'])['income'].aggregate('mean').plot(kind='bar', title="Tendência a salário >50k por tipo de emprego")
dados_de_treino['workclass'].value_counts()/dados_de_treino['workclass'].count() * 100
"""
Similar aos países de origem, os dados parecem até bem distribuídos porém a inspeção da frequência de cada tipo possível revela que há concentração na área privada. A tendência maior de pessoas auto-empregadas pode ser contestada portanto devido à sua baixa frequência nos dados.
"""
horas = list(set(dados_de_treino['hours.per.week'].to_list())) #retira valores duplicados
medias_income = dados_de_treino.groupby(['hours.per.week'])['income'].aggregate('mean').to_list()
print(medias_income.index(max(medias_income))+1) #valor de idade que possuí maior média
plt.pyplot.title("Média de salário >50k por horas de trabalho na semana")
plt.pyplot.scatter(x=horas, y=medias_income)
dados_de_treino['hours.per.week'].value_counts().sort_index()[40:99]
"""
Por fim, vendo o atributo de horas de trabalho por semana, vemos que a tendência geral de pico é em torno de 50 horas por semana, contando, similarmente à análise das idades, com alguns outliers, como no caso dos 61 anos de idade, que possuí média muito superior às outras, mas porque conta com apenas duas aparições no dataset, como visto na análise.
"""
"""
<font size='5'><b>3. Tratamento de dados</b></font> <br>
<font size='3'><b>3.1 Dados faltantes</b></font> <br>
Agora que analisamos que atributos podem estar enviesados, vejamos quais deles possuem lacunas e o que devemos fazer com elas: 
"""
dados_de_treino = pd.read_csv("../input/adult-pmr3508/train_data.csv", na_values="?")
dados_de_treino['income'] = LabelEncoder().fit_transform(dados_de_treino['income'])
dados_de_treino = dados_de_treino.drop(columns=["Id", "fnlwgt"])
dados_de_treino.isna().sum()
dados_de_teste = pd.read_csv("../input/adult-pmr3508/test_data.csv", na_values="?")
dados_de_teste = dados_de_teste.drop(columns=["Id", "fnlwgt"])
dados_de_teste.isna().sum()
"""
Como vimos, o atributo native.country tem grandes chances de ser enviesado e portanto deve ser retirado
"""
dados_de_treino = dados_de_treino.drop(columns=["native.country"])
dados_de_teste = dados_de_teste.drop(columns=["native.country"])
"""
Antes de decidir o que fazer com os outros, vale a pena transformar os dados não-numéricos em numéricos e ver sua correlação com o income. Caso ela seja baixa podemos já retirá-los sem precisar pensar se a substituição pela moda é uma opção melhor.
"""
dados_de_treino = dados_de_treino.apply(lambda col: LabelEncoder().fit_transform(col.astype(str)), axis=0, result_type='expand')
dados_de_teste = dados_de_teste.apply(lambda col: LabelEncoder().fit_transform(col.astype(str)), axis=0, result_type='expand')
sns.heatmap(dados_de_treino.corr()["income"].to_frame(), annot = True)
"""
Vemos portanto que tanto occupation quanto workclass pouco influenciam em nossa variável de interesse, e assim podemos nos desvencilhar delas. Por inspeção, vemos também que os atributos race e education podem ser retirados também.
"""
dados_de_treino = dados_de_treino.drop(columns=["workclass", "education", "occupation", "race"])
dados_de_teste = dados_de_teste.drop(columns=["workclass", "education", "occupation", "race"])
dados_de_teste.isna().sum()
"""
Assim ficam nossos dados de treino e teste:
"""
dados_de_treino.head()
dados_de_teste.head()
"""
<font size='5'><b>4. Criação do modelo e seleção de parâmetros</b></font> <br>
Primeiramente, separemos nossos dados em "x" (atributos disponíveis) e "y" (variável de interesse):
"""
dados_de_treino_y = dados_de_treino["income"]
dados_de_treino_x = dados_de_treino.drop(columns=["income"]).values
"""
Para a decisão do parâmetro k, necessário na implementação do algoritmo knn, utilizaremos do método de validação cruzada 10-fold iteradamente, analisando valores de k de 15 a 40, por serem de tamanho adequado para nossos dados, e encontraremos qual deles gera melhor resultado, olhando a pontuação média fornecida pela validação de cada k. Importando as bibliotecas necessárias:
"""
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
max_score = 0
max_k = 0
for k in range(15,40):
    estimador = KNeighborsClassifier(n_neighbors = k)
    scores = cross_val_score(estimador, dados_de_treino_x, dados_de_treino_y, cv=10)
    if scores.mean() > max_score:
        max_k = k
        max_score = scores.mean()
print("Max k:", max_k, "max score médio:", max_score)
"""
O valor de k que gera melhores resultados portanto é 24. Com isso, conseguimos criar o classificador utilizando a biblioteca sklearn.
"""
estimador = KNeighborsClassifier(n_neighbors=24)
estimador = estimador.fit(dados_de_treino_x, dados_de_treino_y)
previsao = estimador.predict(dados_de_teste.values)
previsao_str = []
for i in previsao:
    if i == 1:
        previsao_str.append(">50K")
    else:
        previsao_str.append("<=50K")
"""
Por fim, devemos carregar nossos resultados num arquivo csv. 
"""
df_submissao = pd.DataFrame({"Id": range(len(previsao)), "income": previsao_str})
df_submissao.to_csv("submissao.csv", index = False)