[Parte I - Buscando o Melhor Score]
A variável "Response" é a que queremos prever. Ela é dividida em 8 categorias (1 a 8), isto é, y = 1,2,3,4,5,6,7,8.
Tipicamente, inicia-se a avaliação da base de dados a partir de uma análise descritiva do conjunto de variáveis que a compõe. Desta forma, temos:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import style
import seaborn as sns
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
df=train_df.copy()
train_df.info() # para saber se as características são object, int ou float

A base possui um total de 128 variáveis, sendo "Response" a que queremos prever. O próximo passo é avaliar a existência de missing data.
Tratamento de Missing Values
# GRÁFICO PARA VISUALIZAR A % DE MISSING VALUES (features) NOS DADOS DE TREINO
missing= df.isnull().sum()[df.isnull().sum() !=0]
missing=pd.DataFrame(missing.reset_index())
missing.rename(columns={'index':'features',0:'missing_count'},inplace=True)
missing['missing_count_percentage']=((missing['missing_count'])/59381)*100
plt.figure(figsize=(7,3))
sns.barplot(y=missing['features'],x=missing['missing_count_percentage'])

Variáveis Contínuas: EmploymentInfo146
InsuranceHistory5
FamilyHist2-3-4-5
Variáveis discretas: MedicalHistory1,
MedicalHistory10,
MedicalHistory15,
MedicalHistory24,
MedicalHistory32
A estratégia adotada será remover linhas com missing values e ver como o
modelo performa. Podemos escolher a forma de substituir os missing values com:
(a) média;
(b) mediana;
(c) moda.
Antes de decidir sobre isso, vamos ver a distribuição dos dados pelo respectivo
boxplot.
Tratamento dos Outliers
plt.plot(figsize=(15,10))
sns.boxplot(df['Employment_Info_1'])

EmploymentInfo1 parece ter muitos outliers - talvez o mais adequado seja
utilizar a mediana para substituir os NA tanto na amostra de treino quanto
de teste.
train_df['Employment_Info_1'].isna().sum()
test_df['Employment_Info_1'].isna().sum()
train_df['Employment_Info_1'].fillna(train_df['Employment_Info_1'].median(),inplace=True)
test_df['Employment_Info_1'].fillna(test_df['Employment_Info_1'].median(),inplace=True)
## PARA CONFERIR SE NÃO HÁ NENHUM N/A ##
train_df['Employment_Info_1'].isna().sum()
test_df['Employment_Info_1'].isna().sum()
train_df['Employment_Info_1'].describe()

sns.boxplot(train_df['Employment_Info_4'])

EmploymentInfo4 é centrada em zero, mas tem muitos outliers.
train_df['Employment_Info_4'].fillna(train_df['Employment_Info_4'].median(),inplace=True)
test_df['Employment_Info_4'].fillna(test_df['Employment_Info_4'].median(),inplace=True)
sns.boxplot(traindf['EmploymentInfo_6'])

Como não há outliers em 'EmploymentInfo6' , a média pode ser uma proxy para os N/A:
train_df['Employment_Info_6'].fillna(train_df['Employment_Info_6'].mean(),inplace=True)
test_df['Employment_Info_6'].fillna(test_df['Employment_Info_6'].mean(),inplace=True)
sns.boxplot(y=train_df['Medical_History_1'])

train_df['Medical_History_1'].fillna(train_df['Medical_History_1'].median(),inplace=True)
test_df['Medical_History_1'].fillna(test_df['Medical_History_1'].median(),inplace=True)
Fazendo o drop das variáveis com muitos missing values
train_df.drop(['Medical_History_10','Medical_History_15','Medical_History_24','Medical_History_32','Family_Hist_3','Family_Hist_5','Family_Hist_2','Family_Hist_4'],axis=1,inplace=True)
test_df.drop(['Medical_History_10','Medical_History_15','Medical_History_24','Medical_History_32','Family_Hist_3','Family_Hist_5','Family_Hist_2','Family_Hist_4'],axis=1,inplace=True)
train_df.isnull().sum()[train_df.isnull().sum()!=0]
sns.boxplot(y=train_df['Insurance_History_5'])

Substituindo pela mediana
train_df['Insurance_History_5'].fillna(train_df['Insurance_History_5'].median(),inplace=True)
test_df['Insurance_History_5'].fillna(test_df['Insurance_History_5'].median(),inplace=True)
train_df.isnull().sum()
Todos os missing NA values foram devidamente tratados. Agora, podemos iniciar o próximo passo, que é converter as características do tipo string em valores (dados) numéricos. É o caso de 'ProductInfo2':
train_df.info(object)
train_df['Product_Info_2'].unique()

Utiliza-se o LabelEncoder para a conversão:
from sklearn.preprocessing import LabelEncoder
le=LabelEncoder()
train_df['Product_Info_2']=le.fit_transform(train_df['Product_Info_2'])
test_df['Product_Info_2']=le.transform(test_df['Product_Info_2'])
train_df['Product_Info_2'].unique()
Que transforma a variável em:

O próximo passo foi fazer a separação das variáveis independentes e a dependente ('Response'). Também optou-se por fazer o drop de "Id"
X_train=train_df.iloc[:,0:-1]
y_train=train_df['Response']
X_train.drop('Id',axis=1,inplace=True)
X_train.head()
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X_train,y_train)
y_train.unique()
from sklearn.metrics import accuracy_score,confusion_matrix
from sklearn.model_selection import cross_val_score,GridSearchCV
from sklearn.multiclass import OneVsRestClassifier
Feito isso, podemos começar a estimar o score de alguns modelos. A ver:
Árvore de Decisão
from sklearn.tree import DecisionTreeClassifier
param_grid={'max_depth':range(1,20,2)}
DT=DecisionTreeClassifier()
clf_DT=GridSearchCV(DT,param_grid,cv=10,scoring='accuracy',n_jobs=-1).fit(X_train,y_train)
y_pred=clf_DT.predict(X_test)
print(accuracy_score(y_test,y_pred))
Que nos retorna um score de 0.5189950154923885.
Random Forest
from sklearn.ensemble import RandomForestClassifier
param_grid={'max_depth':range(1,20,2)}
RF=RandomForestClassifier()
clf_rf=GridSearchCV(RF,param_grid,cv=10,scoring='accuracy',n_jobs=-1).fit(X_train,y_train)
y_pred=clf_rf.predict(X_test)
accuracy_score(y_test,y_pred)
cujo score é 0.5041762090798868
Ridge Regression
Para avaliar a performance da Ridge Regression, podemos partir pra uma modelagem distinta da apresentada anteriormente.
from collections import Counter
import numpy as np
from scipy import stats
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
Leitura dos Dados
Dividir os dados do input em 4 classes distintas de variáveis, dado que as
formas de tratamento é diferente pra cada uma:
i) categórica
ii) Contínua
iii)discreta
iv) dummy
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
TRAIN_DATA=train_df.copy()
TEST_DATA=test_df.copy()
CATEGORICAL_COLUMNS = ["Product_Info_1", "Product_Info_2", "Product_Info_3", "Product_Info_5", "Product_Info_6",\
"Product_Info_7", "Employment_Info_2", "Employment_Info_3", "Employment_Info_5", "InsuredInfo_1",\
"InsuredInfo_2", "InsuredInfo_3", "InsuredInfo_4", "InsuredInfo_5", "InsuredInfo_6", "InsuredInfo_7",\
"Insurance_History_1", "Insurance_History_2", "Insurance_History_3", "Insurance_History_4", "Insurance_History_7",\
"Insurance_History_8", "Insurance_History_9", "Family_Hist_1", "Medical_History_2", "Medical_History_3",\
"Medical_History_4", "Medical_History_5", "Medical_History_6", "Medical_History_7", "Medical_History_8",\
"Medical_History_9", "Medical_History_11", "Medical_History_12", "Medical_History_13", "Medical_History_14",\
"Medical_History_16", "Medical_History_17", "Medical_History_18", "Medical_History_19", "Medical_History_20",\
"Medical_History_21", "Medical_History_22", "Medical_History_23", "Medical_History_25", "Medical_History_26",\
"Medical_History_27", "Medical_History_28", "Medical_History_29", "Medical_History_30", "Medical_History_31",\
"Medical_History_33", "Medical_History_34", "Medical_History_35", "Medical_History_36", "Medical_History_37",\
"Medical_History_38", "Medical_History_39", "Medical_History_40", "Medical_History_41"]
CONTINUOUS_COLUMNS = ["Product_Info_4", "Ins_Age", "Ht", "Wt", "BMI",
"Employment_Info_1", "Employment_Info_4", "Employment_Info_6",
"Insurance_History_5", "Family_Hist_2", "Family_Hist_3", "Family_Hist_4", "Family_Hist_5"]
DISCRETE_COLUMNS = ["Medical_History_1", "Medical_History_10", "Medical_History_15", "Medical_History_24", "Medical_History_32"]
DUMMY_COLUMNS = ["Medical_Keyword_{}".format(i) for i in range(1, 48)]
categorical_data = pd.concat([TRAIN_DATA[CATEGORICAL_COLUMNS], TEST_DATA[CATEGORICAL_COLUMNS]])
continuous_data = pd.concat([TRAIN_DATA[CONTINUOUS_COLUMNS], TEST_DATA[CONTINUOUS_COLUMNS]])
discrete_data = pd.concat([TRAIN_DATA[DISCRETE_COLUMNS], TEST_DATA[DISCRETE_COLUMNS]])
dummy_data = pd.concat([TRAIN_DATA[DUMMY_COLUMNS], TEST_DATA[DUMMY_COLUMNS]])
Tratamento dos Dados
Variáveis Categóricas
Do total de 128 colunas, 60 representam variáveis categóricas:
ProductInfo1, ProductInfo2, ProductInfo3, ProductInfo5,
ProductInfo6, ProductInfo7,
EmploymentInfo2, EmploymentInfo3, EmploymentInfo5,
InsuredInfo1, InsuredInfo2, InsuredInfo3, InsuredInfo4, InsuredInfo5,
InsuredInfo6, InsuredInfo_7,
InsuranceHistory1, InsuranceHistory2, InsuranceHistory3,
InsuranceHistory4, InsuranceHistory7, InsuranceHistory8,
InsuranceHistory9,
FamilyHist1, MedicalHistory2, MedicalHistory3, MedicalHistory4,
MedicalHistory5, MedicalHistory6, MedicalHistory7, MedicalHistory8,
MedicalHistory9, MedicalHistory11, MedicalHistory12, MedicalHistory13,
MedicalHistory14, MedicalHistory16, MedicalHistory17, MedicalHistory18,
MedicalHistory19, MedicalHistory20, MedicalHistory21, MedicalHistory22,
MedicalHistory23, MedicalHistory25, MedicalHistory26, MedicalHistory27,
MedicalHistory28, MedicalHistory29, MedicalHistory30, MedicalHistory31,
MedicalHistory33, MedicalHistory34, MedicalHistory35, MedicalHistory36,
MedicalHistory37, MedicalHistory38, MedicalHistory39, MedicalHistory40,
MedicalHistory41
def plot_categoricals(data):
ncols = len(data.columns)
fig = plt.figure(figsize=(5 * 5, 5 * (ncols // 5 + 1)))
for i, col in enumerate(data.columns):
cnt = Counter(data[col])
keys = list(cnt.keys())
vals = list(cnt.values())
plt.subplot(ncols // 5 + 1, 5, i + 1)
plt.bar(range(len(keys)), vals, align="center")
plt.xticks(range(len(keys)), keys)
plt.xlabel(col, fontsize=10)
plt.ylabel("frequência", fontsize=10)
fig.tight_layout()
plt.show()
plot_categoricals(categorical_data)
O comando acima plota todas as variáveis categóricas. Aqui, para fins de ilustração, apresentaremos apenas duas:

Para manipular as variáveis categóricas com as scikit-learn built in functions
foi necessário transformar elas em dummy usando o 'pandas.get_dummies'. Ai,
podemos notar também que não há missing values nos dados categóricos.
# pra usar o get_dummies é preciso converter para string
categorical_data = categorical_data.applymap(str)
categorical_data = pd.get_dummies(categorical_data, drop_first=True)
Variáveis Contínuas
Correspondem às seguintes 13 colunas:
ProductInfo4, InsAge, Ht, Wt, BMI, EmploymentInfo1, EmploymentInfo4,
EmploymentInfo6, InsuranceHistory5, FamilyHist2, FamilyHist3,
FamilyHist4, FamilyHist_5
# Plotando as variáveis contínuas
def plot_histgrams(data):
ncols = len(data.columns)
fig = plt.figure(figsize=(4 * 4, 5 * (ncols // 5 + 1)))
for i, col in enumerate(data.columns):
X = data[col].dropna()
plt.subplot(ncols // 5 + 1, 5, i + 1)
plt.hist(X, bins=20, alpha=0.5, \
edgecolor="black", linewidth=1)
plt.xlabel(col, fontsize=12)
plt.ylabel("frequência", fontsize=12)
fig.tight_layout()
plt.show()
plot_histgrams(continuous_data)
O comando acima plota todas as contínuas. Aqui apresentaremos apenas duas, em função de sua assimetria:

Como algumas variáveis são distribuídas de forma bastante assimétrica, será
aplicada a transformação de Box-Cox. Assim, cada coluna é normalizada, com
cada missing value assumindo valor igual a zero...depois podemos testar outras
formas de substituir os missing values.
Transformação Box-Cox nas variáveis contínuas
def preproc_quantitatives(X):
Y = X.copy()
# transformaçao Box-Cox nos non-missing values
not_missing = Y[~Y.isnull()].copy()
not_missing = not_missing - np.min(not_missing) + 1e-10 # para evitar erros com valores não positivos
res = stats.boxcox(not_missing)
Y[~Y.isnull()] = res[0]
Variáveis discretas
Foram tratadas da mesma forma que as variáveis continuas (por simplicidade)
Correspondem às seguintes colunas (5):
MedicalHistory1, MedicalHistory10, MedicalHistory15, MedicalHistory24,
MedicalHistory32
# transformação das variáveis discretas
for col in discrete_data.columns:
discrete_data[col] = preproc_quantitatives(discrete_data[col])
# plotando as variáveis discretas
plot_histgrams(discrete_data)
Ridge Regression
Como exemplo, vou começar com a para prever a nossa variável
de interesse. Antes da previsão, será feita uma bateria de experimentos para
decidir qual das características devem ser utilizadas ou não, bem como para
"setar" o valor de alpha
# normalizando os non-missing values
mu = np.mean(Y[~Y.isnull()])
sigma = Y[~Y.isnull()].std()
Y = (Y - mu) / sigma
# Substituindo os missing values (aqui, depois podemos testar a média pra ver como fica)
Y[Y.isnull()] = 0.0
return Y
# Pre processando os dados continuos
for col in continuous_data.columns:
continuous_data[col] = preproc_quantitatives(continuous_data[col])
plot_histgrams(continuous_data)
Desta forma, do conjunto dos dados, foram utilizadas 40000 observações (linhas)
para o treino e 10000 obs para o teste (train, test) = (0.8, 0.2), retiradas
da variável TRAIN_DATA
# Habilitando teste de vários alphas para a Ridge Regression
# rodar todo o bloco abaixo para gerar o grafico corretamente
configs = {'var. quantitativas' : pd.concat([continuous_data, discrete_data], axis = 1),
'var. qualitativas' : pd.concat([categorical_data, dummy_data], axis = 1),
'todas as características' : pd.concat([continuous_data, discrete_data, categorical_data, dummy_data], axis = 1)}
errors_dict = {}
y = TRAIN_DATA['Response']
for title, X in configs.items():
X_train = X[:40000]
X_test = X[40000:50000]
y_train = y[:40000]
y_test = y[40000:50000]
alphas = [0.1, 1.0, 10.0, 100.0, 1000.0]
errors = []
for alpha in alphas:
model = Ridge(alpha=alpha)
model.fit(X_train, y_train)
z = model.predict(X_test)
error = np.sqrt(np.sum((y_test - z) * (y_test - z)) / (1.0 * len(y_test)))
errors.append(error)
errors_dict[title] = errors
errors_dict = pd.DataFrame(data=errors_dict)
plt.plot(alphas, errors_dict)
plt.xlabel("alpha", fontsize=12)
plt.ylabel("(MSE)^1/2", fontsize=12)
plt.title('Experimentos - Ridge Regression', fontsize=12)
plt.legend(errors_dict.columns)
plt.xscale('log')
plt.show()

A partir dos experimentos com a Ridge Regression, que mostra que o MSE para
várias combinações possíveis, foi possível notar que o menor MSE é aquele em
que se utiliza todas as características, considerando alpha = 10.0
Previsão
o código a seguir prevê a variável de interesse para a base de dados reservadas
para o teste e gera o arquivo de submissão ao kaggle no formato (csv).
# Previsão para o "test set" e submissão ao kaggle
X = pd.concat([continuous_data, discrete_data, categorical_data, dummy_data], axis = 1)
X_train = X[:len(y)]
X_test = X[len(y):]
model = Ridge(alpha=10.0)
model.fit(X_train, y)
z = model.predict(X_test)
z
z = np.round(z)
z[z < 1] = 1
z[z > 8] = 8
z = z.astype(np.int64)
df = pd.DataFrame(data={'Id' : TEST_DATA['Id'], 'Response' : z})
df.to_csv('submit_ridge_1.csv', index=False)
# plot histgram of scores in leaderboard
data = pd.read_csv("/Users/Douglas/Desktop/MCE - UnB/Seguro//prudential-life-insurance-assessment-publicleaderboard.csv")
plt.hist(data["Score"], bins=20, alpha=0.5, color="green", \
edgecolor="black", linewidth=2.0)
plt.xlabel('Score', fontsize=18)
plt.ylabel('Frequência', fontsize=18)
plt.title('Score Geral em 04/07/2019', fontsize=16)
plt.xlim([-0.5, 0.8])
plt.axvline(0.55443, color='b', linestyle='dashed', linewidth=2)
plt.show()

Note que a Ridge Regression apresentou o melhor score = 0.55443.