<jupyter_start><jupyter_text>Machine Learning - Parte II  Regressão Logística Regressão logística é um algoritmo de classificação largamente utilizado em diversas aplicações. Trata-se de um algoritmo simples cujo fitting objetiva minizar um erro de predição. Seu entendimento é uma fase intermediária para abordarmos outros classificadores mais poderosos, por exemplo aqueles baseados em redes neurais.<jupyter_code># import de modulos pandas e numpy
import numpy as np
import pandas as pd
from pandas import Series,DataFrame

# Math
import math

# import de modulos para graficos
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
%matplotlib inline

# imports para Machine Learning 
from sklearn.linear_model import LogisticRegression
# from sklearn.cross_validation import train_test_split # modulo antigo
from sklearn.model_selection import train_test_split

# para avaliacao
from sklearn import metrics

# dataset
import statsmodels.api as sm<jupyter_output><empty_output><jupyter_text>Função logísticaA função logistica [Logistic Function](http://en.wikipedia.org/wiki/Logistic_function) recebe como argumento uma valor de $-\infty$ a $+\infty$, e retorna um valor no intervalo $(0,1)$$$ \sigma (t)= \frac{1}{1+e^{-t}}$$<jupyter_code># Função Logistica
def logistic(t):
    return 1.0 / (1 + math.exp((-1.0)*t) )

# cria um grade de -6 a 6 ( 500 elementos, igualmente espaçados)
grade_t = np.linspace(-6,6,500)

# calcula os valores de y
p = np.array([logistic(t) for t in grade_t])

# usando exp do numpy que aceita um vetor como argumento
p = 1/(1 + np.exp(-1.0 * grade_t))

# plot
plt.plot(grade_t,p)
plt.title(' Funcao Logistica ');<jupyter_output><empty_output><jupyter_text>Consideremos que uma observação seja composta por:* $\mathbf x = (x_{1}, x_{2} ... , x_{m})$, as $m$ features disponiveis* $y$, a varíavel que queremos prever Na classificação binária, assumimos que $y = 0$ ou $y = 1$.  A ideia é considerar a função logistica gerando uma probabilidade, a partir de um polinomio:$$ t(\mathbf x) = w_0 + w_1.x_{1} + w_2.x_{2} ... + w_m.x_{m}$$onde $w_0, w_1, ..., w_m$ são coeficientes a serem aprendidos, de forma que a equação abaixo:$$ \hat{y} = Prob[y = 1 \mid \mathbf x]  = $$$$ \sigma (t(\mathbf x)) = \frac{1}{1+e^{-(w_0 + w_1.x_{1} + w_2.x_{2} ... + w_m.x_{m})}}$$forneça a 'melhor' estimativa da probabilidade de sucesso, ou seja a probabilidade de que $y$ seja $1$. Para tanto o algoritmo procura encontrar os melhores $w_0, w_1, ..., w_m$ que minimizam o erro. Uma vez aprendidos os coeficientes, na predição, cada $\mathbf x^{(i)}$ fornecerá seu $\hat{y}^{(i)}$ correspondente, onde $i$ denota o indice da amostra.Se adotarmos um limiar de $0.5$ como a probabilidade que separa as predições entre classes 0 e 1, teremos:* Se $\hat{y}^{(i)} \leq 0.5$, predição será a classe 0* Se $\hat{y}^{(i)} > 0.5$, predição será a classe 1  Exemplo prático<jupyter_code>import webbrowser

url = 'http://statsmodels.sourceforge.net/stable/datasets/generated/fair.html'

webbrowser.open_new(url)<jupyter_output><empty_output><jupyter_text>Quantidade de observações: 6366Quantidade de features: 9Definições:    rate_marriage   : How rate marriage, 1 = very poor, 2 = poor, 3 = fair,                    4 = good, 5 = very good    age             : Age    yrs_married     : No. years married. Interval approximations. See                    original paper for detailed explanation.    children        : No. children    religious       : How relgious, 1 = not, 2 = mildly, 3 = fairly,                    4 = strongly    educ            : Level of education, 9 = grade school, 12 = high                    school, 14 = some college, 16 = college graduate,                    17 = some graduate school, 20 = advanced degree    occupation      : 1 = student, 2 = farming, agriculture; semi-skilled,                    or unskilled worker; 3 = white-colloar; 4 = teacher                    counselor social worker, nurse; artist, writers;                    technician, skilled worker, 5 = managerial,                    administrative, business, 6 = professional with                    advanced degree    occupation_husb : Husband's occupation. Same as occupation.    affairs         : measure of time spent in extramarital affairs<jupyter_code># Carregando dataframe 

df = sm.datasets.fair.load_pandas().data
df.shape
df.info()
# Define uma coluna, indicador binário que indica se a pessoa teve um caso extra-conjugal
df['teve_affair'] = [1 if a else 0 for a in df.affairs]
df.sample(10)
df.teve_affair.value_counts(normalize = True)
# Media por cada label
df.groupby('teve_affair').mean().round(2)
# desvio padrão por cada label
df.groupby('teve_affair').std()
sns.set_context('poster')
# sns.jointplot(data = df[df.affairs < 2], x = 'age', y = 'affairs')
df.affairs.describe(percentiles=[0, 0.9, 0.99])
# Countplot com idade
sns.countplot(data=df, x='age', hue='teve_affair', palette='coolwarm')
# sns.distplot(df[df.teve_affair == 0].age)
# sns.distplot(df[df.teve_affair == 1].age)
# Countplot com anos de casamento
sns.countplot('yrs_married',data=df,hue='teve_affair',palette='coolwarm')
# Countplot com quantidade de filhos
sns.countplot('children',data=df,hue='teve_affair',palette='coolwarm')
# Countplot com nivel de educação
sns.countplot('educ',data=df,hue='teve_affair',palette='coolwarm')
# considerando que a pessoa teve um caso extra-conjugal, 
# visualizacão da distribuição por boxplot
sns.boxplot(data=df[df.affairs > 0], x='age', y = 'affairs')
plt.ylim(0, 15)
# considerando que a pessoa teve um caso extra-conjugal, 
# visualizacão da distribuição por violinplot
sns.violinplot(data=df[(df.affairs > 0) & (df.affairs < 9)], x='age', y = 'affairs')
plt.ylim(-2, 12)
sns.pairplot(data=df[['affairs', 'age', 'yrs_married', 'children', 'religious']], 
        plot_kws = {'alpha':0.08, 's':180, 'edgecolor': None},)
df.sample(5)<jupyter_output><empty_output><jupyter_text>pre-processamentoNotamos as variaveis categoricas Occupation e Husband's Occupation. De maneira similar a regressão linear, precisamos tratar essas colunas. Nesse caso, fazemos um OHE (one hot encoding). Importante: Labelencoding não funciona!!Pandas tem um método para criar esses [dummy variables](http://en.wikipedia.org/wiki/Dummy_variable_%28statistics%29) criando colunas dedicadas para cada valor encontrado.<jupyter_code># Cria novo DataFrame para as colunas categoricas
occ_dummies = pd.get_dummies(df['occupation'])
hus_occ_dummies = pd.get_dummies(df['occupation_husb'])

occ_dummies.head()
# Atribui nomes as colunas
occ_dummies.columns = ['occ1','occ2','occ3','occ4','occ5','occ6']
hus_occ_dummies.columns = ['hocc1','hocc2','hocc3','hocc4','hocc5','hocc6']
# Atribui X primeiramente sem as colunas categoricas 
X = df.drop(['occupation','occupation_husb','teve_affair'],axis=1)

# Concatena os dataframes dummies
dummies = pd.concat([occ_dummies,hus_occ_dummies],axis=1)
# Concatena o X com o dataframe dos dummies
X = pd.concat([X,dummies],axis=1)

# amostragem do X
X.sample(5)<jupyter_output><empty_output><jupyter_text>multicolinearidadeNotar que nos dataframe com as variaveis dummies, uma das colunas é combinação linear das outras.Por exemplo, o valor da primeira coluna será igual a $1 - \sum_{i=2}^{k} x_i$, onde $x_i$ é o valor da coluna $i$ na mesma observação, e $k$ é a cardinalidade (quantidade de valores possíveis) da coluna categorica original.Para remediar esse problema, bem simples, basta deletar uma das colunas.<jupyter_code># deletando uma coluna para cada coluna categorica
X = X.drop('occ1',axis=1)
X = X.drop('hocc1',axis=1)

# deletando coluna não utilizada
X = X.drop('affairs',axis=1)

# amostragem
X.sample(5)<jupyter_output><empty_output><jupyter_text>alternativaPor razões didáticas o roteiro acima foi apresentado, mas todo ele pode ser resumido em apenas uma linha:<jupyter_code># X = pd.get_dummies(df, columns=['occupation', 'occupation_husb'], 
#                    drop_first=True).drop(['affairs', 'teve_affair'], axis = 1)<jupyter_output><empty_output><jupyter_text>separando o vetor y Agora que já temos a matriz com as features definidas, vamos retirar a coluna resposta em um vetor a parte ($y$)<jupyter_code># Atribui y a coluna teve_affair
y = df.teve_affair

# amostragem do y
y.head()
# transformando em numpy
y = y.values

# checando resultado
y
y.mean()
y
X.shape
y.shape<jupyter_output><empty_output><jupyter_text>Rodando Regressão Logistica com sklearn<jupyter_code># Instanciando objeto
log_model = LogisticRegression()

# Treinando o modelo
log_model.fit(X, y)

# Checando acurácia
log_model.score(X, y)
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
y_pred = log_model.predict(X)
y_pred
y
(y == y_pred).mean()
accuracy_score(y, y_pred)
precision_score(y, y_pred)
recall_score(y, y_pred)
f1_score(y, y_pred)
y_pred_proba_1 = log_model.predict_proba(X)[:, 1]
roc_auc_score(y, y_pred_proba_1)
log_model
# Checando a percentagem de pessoas com casos extra-conjugais
y.mean()
coeff_df = DataFrame(log_model.coef_)

coeff_df.columns = X.columns

coeff_df = coeff_df.T

coeff_df.columns = ['coeficiente']
coeff_df.plot(kind='bar', figsize=(12,6))<jupyter_output><empty_output><jupyter_text>O que podemos observar pelos coeficientes acima?<jupyter_code>from sklearn import metrics
metrics.confusion_matrix(y, log_model.predict(X))<jupyter_output><empty_output><jupyter_text>Treinamento e validação<jupyter_code># Segmentando a base
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 100)

# Instanciando um objeto
log_model2 = LogisticRegression()

# Treinando o modelo
log_model2.fit(X_train, y_train)
# Predizendo a classe das observações de teste
class_predict = log_model2.predict(X_test)

# Comparando as classes da predição e o gold, 
# ou seja, medindo a performance...
print (metrics.accuracy_score(y_test,class_predict))
metrics.confusion_matrix(y_test, log_model2.predict(X_test))
print(metrics.classification_report(y_test, log_model2.predict(X_test)))
y_test_pred_proba_1 = log_model2.predict_proba(X_test)[:,1]
roc_auc_score(y_test, y_test_pred_proba_1)
log_model2.coef_
log_model2.intercept_
X_train.columns<jupyter_output><empty_output><jupyter_text>Analise de resultados via matriz de confusão<jupyter_code>y_test
class_predict
(log_model2.predict_proba(X_test)[:,1] > 0.2).astype(int)
# matriz de confusão
confusion_matrix = metrics.confusion_matrix(y_test, class_predict)
confusion_matrix
# Label gold na vertical, e label predição na horizontal
sns.heatmap(confusion_matrix, annot=True, fmt ='d')
print(metrics.classification_report(y_test, class_predict))
confusion_matrix/confusion_matrix.sum()
y_pred = log_model2.predict(X_test)
np.bincount(y_pred)
log_model2.predict_proba(X_test)<jupyter_output><empty_output><jupyter_text>Area sobre a curva ROC Também conhecido por **ROC_AUC**, **AUC_ROC**, ou **AUC**<jupyter_code>y_pred = log_model2.predict_proba(X_test)[:,1]
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test, y_pred)<jupyter_output><empty_output><jupyter_text>Quanto devemos acertar....<jupyter_code>test_mean = y_test.mean()
test_mean
# se não sabemos nada da pessoa, o melhor 'chute' seria jogar uma moeda com 32.2% de probabilidade de cara (label = 1)
# 32.2% é a media do vetor y (arredondado)

# nesse caso teriamos:
print('prob. verdadeiro positivo:', test_mean * test_mean)
print('prob. verdadeiro negativo:', (1- test_mean) * (1- test_mean))
print('prob. falso positivo:', (1- test_mean) * test_mean)
print('prob. falso negativo:', test_mean * (1- test_mean))
from random import random


confusion_chute_list = list()
for _ in range(10000):
    chute = [int(random() < test_mean) for i in range(len(y_test))]
    confusion_chute = metrics.confusion_matrix(y_test, chute)
    confusion_chute_list.append(confusion_chute/confusion_chute.sum())

np.array((confusion_chute_list)).sum(axis = 0)/10000<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11546512601523905, 'fraction_numerical': 0.01649501800217701, 'mean_word_length': 5.250130821559393, 'pattern_counts': {'":': 0, '<': 60, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '146895', 'license': 'Apache-2.0', 'n_tokens_mistral': 4477, 'n_tokens_neox': 4283, 'n_words': 1134, 'path': 'notebooks/fgv_classes/professor_hitoshi/aula 3 - sckitlearn - parte II.ipynb', 'repo_name': 'ldaniel/Advanced-Predictive-Analytics', 'size': '11943'}