"""
# 1) Introdução

Esse código foi feito com o intuito de demonstrar algumas técnicas de ciência de dados em um dos datasets mais famosos da comunidade.

O trabalho foi dividido da seguinte forma:

1. Introdução;
2. Análise exploratória;
3. Pré-processamento;
4. Criação do modelo;
5. Previsão dos resultados.

"""
"""
## Objetivo:

Predizer quais pessoas vão sobreviver ao naufrágio do Titanic.
"""
"""
## Dados fornecidos:

Foram fornecidos dados de treino e teste. Os dados de treino são uma **amostra da população do Titanic** e possuem uma coluna indicando se os indivíduos sobreviveram ou não e esta deve ser tomada como verdade para predizer o futuro dos outros passageiros não incluídos na amostra.

Os dados fornecidos possuem 9 variáveis independentes e 1 variável target:

* **survival**: variável target indicando quem sobreviveu ou não ao naufrágio;
* **pclass**: indica a qual classe o passageiro pertencia (primeira, segunda ou terceira classes);
* **Sex**: indica o sexo do passageiro;
* **Age**: indica a idade do passageiro;
* **SibSp**: indica a quantidade de irmão ou esposa/marido abordo;
* **Parch**: indica a quantitdade de pais e filhos abordo;
* **ticket**: número do ticket (passagem) para o navio;
* **cabin**: número da cabine na qual o passageiro foi alocado para a viagem;
* **Embarked**: local de embarque do passageiro.
"""
"""
## Importando as bibliotecas necessárias
"""
# importanto as bibliotecas báscias
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import seaborn as sns

# obtendo cores do padrão Tableau
import matplotlib.colors as mcolors

# imoportando as bibliotecas de ML
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.pipeline import make_pipeline
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import KFold
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score

# importanto os modelos de ML
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import AdaBoostClassifier
from xgboost import XGBClassifier
"""
## Importando os dados e definições básicas
"""
# importando os dados
treino = pd.read_csv('../input/titanic/train.csv')
teste = pd.read_csv('../input/titanic/test.csv')

# definindo seed para reprodução dos mesmos resultados
seed = 10

# definindo os temas dos gráficos
custom_params = {"axes.spines.right": False, "axes.spines.top": False}
sns.set_theme(style="ticks", rc=custom_params)
"""
# 2) Análise Exploratória

Nessa etapa irei explorar os dados e obter mais informações sobre o naufrágio, tentando identificar padrões estatísticos/visuais nas variáveis, além de determinar quais features são importantes para a modelagem subsequente.
"""
"""
## Análises Numéricas
"""
# avaliando os dados de treino
treino.head()
# avaliando os dados de teste
teste.head()
# verificando os tipos de dados armazenados
treino.info()
print('\n','-'*50,'\n')
teste.info()
# verificando valores nulos
print('Dados de treino:\n')
print(treino.isnull().sum().sort_values(ascending = False))
print('\n','-'*30,'\n')
print('Dados de teste:\n')
print(teste.isnull().sum().sort_values(ascending = False))
"""
Por meio desse trecho de código podemos percerber que os **dados de treino** possuem **891 indivíduos** enquanto os **dados de teste** possuem **418 indivíduos**.

Além disso, é possível perceber que existem **dados faltantes** tanto em **variáveis categóricas** como nas **variáveis numéricas** (vai requerer que apliquemos técnicas diferentes para preenchimento desses valores). **A variável target não possui valores faltantes**.
"""
# verificando a proporção entre classes
treino.groupby('Survived').size()
"""
É interessante saber a proporção entre as classes, já que classes muito desbalanceadas podem levar a aprendizados também desbalanceados. Ou seja, o algoritmo vai aprender mais sobre uma classe do que da outra, prejudicando a acurácia do modelo.

Nesse caso, temos um balanço razoável (geralmente até a proporção de 1 para 5) entre as classes e não será necessário aplicar oversampling ou undersampling.
"""
# verificando as variáveis numéricas
treino.describe()
"""
Percebemos através da descrição dos dados que:

* **Survived:** cerca de 38% da amostra sobreviveu ao naufrágio (valor próximo ao que realmente ocorreu de 32%);
* **Pclass**: mais de 50% das pessoas viajaram de terceira classe;
* **Age**: a amostra mostra que a maioria dos passageiros eram jovens (por volta dos 30 anos de idade);
* **Sibsp**: mais de 50% dos passageiros viajaram sem os irmãos ou maridos/esposas;
* **Parch**: mais de 75% das pessoas viajaram sem parentes (pais e mãe) e filhos;
* **Fare**: o valor das passagens possui um range alto, indicando desigualdade nos preços entre as classes de cabine. Sendo que 50% das passagens tinham preços por volta ou menores que 15 dólares.
"""
# verificando as variáveis categóricas
treino.describe(include =['O'])
"""
Por meio dessa descrição podemos avaliar que:

* **Name**: todas as pessoas tem seus nomes preenchidos;
* **Sex**: a maioria dos passageiros era do sexo masculino;
* **Ticket** e **Cabin**: possuem uma quantidade alta de valores faltantes e/ou repetidos;
* **Embarked**: a maioria das pessoas embarcou no porto "S" (Southampton).
"""
"""
## Análises Gráficas
"""
# verificando a correlação das variáveis numéricas
fig, ax = plt.subplots(figsize = (7,7))
ax = sns.heatmap(treino.corr(), annot=True, linewidths=0.5, vmax = 1, vmin = -1, center = 0)
"""
Por meio da análise de correlação é possível perceber que:

1. A variável **Pclass** tem uma **correlação negativa** com a variável target **survived**, indicando que quanto menor o número da classe, ou seja, quanto mais luxuosa a classe, maior são as chances de sobrevivência.
2. A variável **Fare** tem uma correlação positiva com a variável target **survived**, indicando que quanto maior foi o valor da passagem, maiores foram as chances de sobrevivência. Muito correlacionado com o item superior (a matriz de correlação também mostra essa avaliação).

Ambas as avaliações fazem sentido no contexto dessa tragédia, já que é esperado que as pessoas mais ricas (mais privilegiadas) tenham sobrevivido à custo das mais pobres.

Seguem abaixo comparativos relacionados a cada variável independente em relação a variável target:
"""
"""
### Sobreviventes por Classe de Cabine
"""
# quantidade de sobreviventes por classe de cabine:
fig2, ax2 = plt.subplots(constrained_layout=True)
treino2 = treino[['Pclass', 'Survived']].groupby('Pclass', as_index=False).sum()
treino2['Quantidade'] = treino[['Pclass', 'Survived']].groupby('Pclass').count().values
treino2['%Survived'] = treino2['Survived']/treino2['Quantidade']*100
print(treino2)
sns.barplot(data=treino2, x='Pclass', y='%Survived', alpha=0.8, ax=ax2)
ax2.yaxis.set_major_formatter(mtick.PercentFormatter())
ax2.bar_label(ax2.containers[0], fmt='%d%%')
ax2.set_title("Proporção de sobreviventes por Classe\n", size=15)
"""
Percebe-se que proporcionalmente os passageiros da **primeira classe** são os com maior chance de sobrevivência.
"""
"""
### Sobreviventes por Sexo:
"""
# quantidade de sobreviventes por sexo:
fig3, ax3 = plt.subplots(constrained_layout=True)
treino3 = treino[['Sex', 'Survived']].groupby('Sex', as_index=False).sum()
treino3['Quantidade'] = treino[['Sex', 'Survived']].groupby('Sex').count().values
treino3['%Survived'] = treino3['Survived']/treino3['Quantidade']*100
print(treino3)
sns.barplot(data=treino3, x='Sex', y='%Survived', alpha=0.8, ax=ax3)
ax3.yaxis.set_major_formatter(mtick.PercentFormatter())
ax3.bar_label(ax3.containers[0], fmt='%d%%')
ax3.set_title("Proporção de sobreviventes por Sexo\n", size=15)
"""
Percebe-se que proporcionalmente os passageiros do **sexo feminino** são os com **maior chance de sobrevivência.**
"""
"""
### Sobreviventes por Idade
"""
# quantidade de sobreviventes por idade:
fig5, ax5 = plt.subplots(figsize=(17,5), constrained_layout=True)
treino4 = treino.copy()
treino4['AgeBand'] = pd.cut(treino['Age'],10)
treino5 = treino4[['AgeBand', 'Survived']].groupby('AgeBand', as_index=False).sum()
treino5['Quantidade'] = treino4[['AgeBand', 'Survived']].groupby('AgeBand').count().values
treino5['%Survived'] = treino5['Survived']/treino5['Quantidade']*100
print(treino5)
sns.barplot(data=treino5, x='AgeBand', y='%Survived', alpha=0.8, ax=ax5)
ax5.yaxis.set_major_formatter(mtick.PercentFormatter())
ax5.bar_label(ax5.containers[0], fmt='%d%%')
ax5.set_title("Proporção de sobreviventes por faixa de Idade\n", size=20)
"""
Percebe-se que proporcionalmente os passageiros **mais jovens** e **mais velhos** são os com **maior chance de sobrevivência.**
"""
"""
### Sobreviventes por quantidade de irmãos e marido/esposa
"""
# quantidade de sobreviventes por SibSp:
fig6, ax6 = plt.subplots(constrained_layout=True)
treino6 = treino[['SibSp', 'Survived']].groupby('SibSp', as_index=False).sum()
treino6['Quantidade'] = treino[['SibSp', 'Survived']].groupby('SibSp').count().values
treino6['%Survived'] = treino6['Survived']/treino6['Quantidade']*100
print(treino6)
sns.barplot(data=treino6, x='SibSp', y='%Survived', alpha=0.8, ax=ax6)
ax6.yaxis.set_major_formatter(mtick.PercentFormatter())
ax6.bar_label(ax6.containers[0], fmt='%d%%')
ax6.set_title("Proporção de sobreviventes por SibSp\n", size=15)
"""
Percebe-se que proporcionalmente os passageiros **sem nenhum ou com poucos irmãos ou maridos/esposas** são os com **maior chance de sobrevivência.**
"""
"""
### Sobreviventes por quantidade de parentes ou filhos
"""
# quantidade de sobreviventes por Parch:
fig7, ax7 = plt.subplots(constrained_layout=True)
treino7 = treino[['Parch', 'Survived']].groupby('Parch', as_index=False).sum()
treino7['Quantidade'] = treino[['Parch', 'Survived']].groupby('Parch').count().values
treino7['%Survived'] = treino7['Survived']/treino7['Quantidade']*100
print(treino7)
sns.barplot(data=treino7, x='Parch', y='%Survived', alpha=0.8, ax=ax7)
ax7.yaxis.set_major_formatter(mtick.PercentFormatter())
ax7.bar_label(ax7.containers[0], fmt='%d%%')
ax7.set_title("Proporção de sobreviventes por Parch\n", size=15)
"""
Percebe-se que proporcionalmente os passageiros **com poucos parentes/filhos** são os com **maior chance de sobrevivência.** Um pouco diferente da variável SibSp, possilvemente os pais foram salvos com os filhos e os filhos com os pais (no caso de poucos filhos).
"""
"""
### Sobreviventes por local de embarque
"""
# quantidade de sobreviventes por embarque:
fig8, ax8 = plt.subplots(constrained_layout=True)
treino8 = treino[['Embarked', 'Survived']].groupby('Embarked', as_index=False).sum()
treino8['Quantidade'] = treino[['Embarked', 'Survived']].groupby('Embarked').count().values
treino8['%Survived'] = treino8['Survived']/treino8['Quantidade']*100
print(treino8)
sns.barplot(data=treino8, x='Embarked', y='%Survived', alpha=0.8, ax=ax8)
ax8.yaxis.set_major_formatter(mtick.PercentFormatter())
ax8.bar_label(ax8.containers[0], fmt='%d%%')
ax8.set_title("Proporção de sobreviventes por Embarque\n", size=15)
"""
Percebe-se que proporcionalmente os passageiros que **embarcaram em Cherbourg** são os com **maior chance de sobrevivência.**
"""
"""
## Avaliando a distribuição dos dados
"""
# avaliando a distribuição das variáveis numéricas de forma geral
variaveis_num = ['Age', 'SibSp', 'Parch', 'Fare']
fig9, ax9 = plt.subplots(nrows = 2, ncols = 2, figsize = (10,7), constrained_layout=True)
for variavel,axes, color in zip(variaveis_num, ax9.flat, mcolors.TABLEAU_COLORS.keys()):
    sns.kdeplot(x=treino[variavel], ax=axes, fill=True, color=color)
"""
Os dados possuem distribuição exponencial em sua maioria e portanto devem ser normalizados e padronizados para se adequarem melhor ao modelo de Machine Learning.

***É possível ainda fazer muito mais análises correlacionais entre as variáveis. Elas poderão surgir de acordo com a necessidade da modelagem e acurácia necessária.***
"""
"""
# 3) Pré-processamento

Nessa etapa serão executadas as atividades de eliminação e escolha de variáveis, transformação de variáveis e criação das pipelines para tornar o código mais pythonico, manutenível e também evitar que dados de teste vazem para os dados de treino.
"""
"""
## Eliminação de variáveis
"""
"""
De acordo com as análises efetuadas as seguintes variáveis serão inicialmente eliminadas:

* **Name**: trata-se do nome dos passageiros e inicialmente não traz nenhum informação pertinente para a criação do modelo. *Talvez em futuras otimização possa ser utilizada para criar classes de passageiros de acordo com sua classificação (Mr. Sr. etc.)*;
* **Ticket**: valor alfanumérico que indica o número da passagem. Não traz informações relevantes em relação à sobrevivência dos passageiros;
* **Cabin**: valor alfanumérico que indica a cabine do passageiro. É uma variável com muitos valores nulos (687 de 891 nos dados de treino) e valores duplicados. Não traz informação relevante para o modelo.
* **PassengerId**: identificação numérica do passageiro. Não traz informações úteis ao modelo.
"""
# elimando as colunas que não ajudarão na predição
treino.drop(['Name', 'Ticket', 'Cabin', 'PassengerId'], inplace = True, axis = 1)
#salvando um backup de teste antes
teste_inicial = teste.copy()
teste.drop(['Name', 'Ticket', 'Cabin', 'PassengerId'], inplace = True, axis = 1)
treino.head()
"""
## Transformação do Data Frame

Para melhorar a visualização, irei mover a variável target para a última coluna. Além disso separar a matriz de variáveis indepentes em **X** e o vetor target para **y**.
"""
# passando a variável target para a última coluna (melhorando a visualização)
treino = treino[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch',
       'Fare', 'Embarked', 'Survived']]
teste = teste[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch',
       'Fare', 'Embarked']]
treino.head()
# separação das variáveis independentes e variável target
X = treino.iloc[:,:-1]
y = treino.iloc[:,-1]
print(X.head(),'\n\n')
print(y.head())
"""
## Criação das Pipelines

Para as variáveis numéricas serão utilizados métodos de transformação para preencher os valores faltantes e ao mesmo tempo normalizá-los e padronizá-los.

Já para as variáveis categóricas serão criadas transformação para preencher valores faltantes e colocá-las em formato numérico codificado para melhorar o aprendizado dos algoritmos.
"""
# criação dos pipelines
num_transform = make_pipeline(SimpleImputer(strategy = 'median'), MinMaxScaler(), StandardScaler())
cat_transform = make_pipeline(SimpleImputer(strategy = 'most_frequent'), OneHotEncoder())

# criação do processador unificado para as diferentes colunas
ct = ColumnTransformer(transformers = [('num_transform', num_transform, ['Age', 'SibSp', 'Parch', 'Fare']),
                                      ('cat_transform', cat_transform, ['Pclass', 'Sex', 'Embarked'])])
"""
# 4) Criação do Modelo
"""
"""
## Teste de Modelos para Classificação (usando crossvalidation)

O teste dos modelos é essencial para definir qual algoritmo irá possuir a maior acurácia (método de avaliação desse desafio). O uso do CrossValidation agrega mais confiança na decisão, já que com essa metodologia reduzimos a possibilidade de overfitting do modelo para os dados apresentados no treinamento.

Vamos utilizar os seguinte modelos:

* Logistic Regression
* Linear Discrimination Analysis
* Naive Bayes
* K-Nearest Neighbours
* Suppor Vector Machine - Classifier
* Decision Tree Classifier
* Random Forest
* AdaBoost
* XGBoost Classifier
"""
# criação do pipeline unificado e final
#modelo_base = make_pipeline(ct, XGBClassifier(use_label_encoder=False, eval_metric='logloss'))

# criação de kfolds (usando o shuffle para alterar a ordem dos dados)
kfold = KFold(n_splits=10, random_state=seed, shuffle=True)

# definição dos modelos que serão iterados para escolha
modelos = {'LRE': LogisticRegression(),
           'LDA': LinearDiscriminantAnalysis(),
           'GNB': GaussianNB(),
           'KNN': KNeighborsClassifier(),
           'SVC': SVC(),
           'DTC': DecisionTreeClassifier(),
           'RFC': RandomForestClassifier(),
           'ADB': AdaBoostClassifier(),
           'XGB': XGBClassifier(use_label_encoder=False, eval_metric='logloss')}

# criação do loop de verificação
scores = []
for nome, modelo in modelos.items(): 
    modelo_teste = make_pipeline(ct,modelo)
    score = cross_val_score(modelo_teste, X, y, cv=kfold)
    scores.append(score)
    print('Modelo: %s | Média: %.3f | Desvio Padrão: %.2f | Mediana: %.3f' %(nome, score.mean(), score.std(), np.median(score)))
# criação do daframe e gráfico boxplot
df = pd.DataFrame(dict(zip(modelos.keys(), scores)))
fig10, ax10 = plt.subplots(figsize=(10,5),constrained_layout=True )
sns.boxplot(data=df, ax=ax10)
sns.swarmplot(data=df, color=".25", ax=ax10)
"""
As informações coletadas e o boxplot evidenciam que os **modelos mais acertivos e estáveis** entre os testados são o **RandomForestClassifier** e o **XGBClassifier** (o queredinho do Kaggle). **O RFC é mais estável** que o XGB já que possui **menor desvio padrão e range**. Portanto, **o modelo escolhido é o RandomForestClassifier**.
"""
"""
## Definição dos parâmetros do modelo final (usando crossvalidation)
"""
# criação do teste de variáveis para o RandomForest (usando o GridSearchCV de forma simples)
modelo_escolhido = make_pipeline(ct, RandomForestClassifier(random_state=seed))

parametros = {'randomforestclassifier__criterion': ['gini', 'entropy'],
              'randomforestclassifier__max_depth': range(1,11)}

modelo = GridSearchCV(estimator = modelo_escolhido, param_grid = parametros, cv = kfold)

# ajustando o modelo aos dados de treino
modelo.fit(X,y)
modelo.best_estimator_
# verificando a acurácia do modelo em relação aos dados de treino
print('A acurácia do modelo é: %.2f%%' % (modelo.score(X,y) *100))
"""
# 5) Previsão dos resultados
"""
# colando os dados de teste em uma variável para melhorar a compreensão
X_teste = teste.copy()

X_teste.head()

# prevendo os resultados
y_teste = modelo.predict(X_teste)
"""
## Salvando arquivo para submissão
"""
## Colocando no formato final de envio e salvando em um arquivo

df_final = pd.DataFrame({'PassengerId': teste_inicial['PassengerId'], 'Survived': y_teste})
print(df_final.head()) #conferência no formato final
df_final.to_csv('/kaggle/working/submission.csv', index = False)
"""
# Melhorias para revisões futuras

1. Criar uma função para criação dos gráficas e evitar a repetição do código (melhorar legibilidade do código);
2. Criar uma variável relacionada aos nomes que possa ajudar na avaliação;
3. Parch e SibSp são variáveis de correlação baixa, talvez seja melhor juntá-las e criar uma nova variável mais interessante;
4. Explorar mais o modelo de RandomForest (hyperparameters tuning) e entender mais sobre a árvore gerada.
"""
"""
# Referências

1. Um código bem explicado e com várias técnicas aplicadas: https://www.kaggle.com/startupsci/titanic-data-science-solutions
2. Um guia bem bacana sobre pipelines: https://medium.com/data-hackers/como-usar-pipelines-no-scikit-learn-1398a4cc6ae9
3. Um Cheat Sheet iterativo do Scikit Learning muito bom: https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html
4. Muitas das técnicas aprendi no curso da DSA: https://www.datascienceacademy.com.br/course/analise-de-dados-com-python
"""