Engenharia de Software para Ciência de Dados - PUC-Rio Introdução à Análise Exploratória de Dados com a Biblioteca Pandas

Professores: Marcos Kalinowski e Tatiana Escovedo e Villamizar

Aluno: Walter Dominguez

Site de Apoio: Web

Book chamado para : seleção de caracteristicas

1. DEFINIÇÃO DA SOLUÇÃO:¶

Estória do Usuário :

[US01] Como Gerente da vinícula Quero explorar as propriedades fisico-química de um vinho para saber como elas influenciam na classificação da qualidade de um vinho (obtida sensorialmente por especialistas, através de pontuação da qualidade de 0 a 10).

Perguntas dos gestores.

Como as caracteristicas dos vinhos contidos no conjuntos de dados influenciam na classificaçãp do vinho ?

Perguntas dos analistas.

Como construir um modelo de classificação que preveja sua qualidade com base nas características físicas e químicas do vinho (a qualidade do vinho foi avaliada com base em seu sabor).

Descrição dos dados.

Os Data set foram obtidos Conjunto de dados sobre Vinho da UCI Machine Learning Repository [Web] (em caso de dúvida consultar Home Page do Paulo Cortez , assim como a descrição de como foram coletados os dados.).

Esse conjunto de dados organizado contém 1.599 vinhos tintos com 11 variáveis sobre as propriedades químicas do vinho. Pelo menos três especialistas em vinhos avaliaram a qualidade de cada vinho, fornecendo uma classificação entre 0 (muito ruim) e 10 (muito excelente).

Requisitos funcionais e não-funcionais.

Os dois conjuntos de dados: Conjunto de valores de cada caracteristica e avaliação de vinho tinto (red) winequality-red.csv Web

Conjunto de valores de cada caracteristica e avaliação de vinho branco (verde) winequality-white.csv Web

Na referência acima, foram criados dois conjuntos de dados, utilizando amostras de vinho tinto e branco.

Em cada arquivo .csv contém: Entradas com elementos químicos dos vinhos (por exemplo, valor de PH por vinho). Saída é baseada em dados sensoriais (mediana de pelo menos 3 avaliações feitas por peritos em vinho - ruim, médio e excelente). Cada perito avaliou a qualidade de cada vinho entre 0 (muito mau) e 10 (muito excelente).

Variáveis de entrada que desejam ser preditas ou descritas, assim como as que possivelmente são relacionadas. Variáveis de entrada (com base em testes físico-químicos): acidez fixa acidez volátil ácido cítrico açúcar residual cloretos dióxido de enxofre livre dióxido de enxofre total densidade pH sulfatos álcool

Variável de saída (com base em dados sensoriais):

qualidade (pontuação entre 0 e 10) Valores de Atributos em Falta: Nenhum

Definição do problema de Ciência de dados (CD). Como as caracteristicas dos vinhos contidos no conjuntos de dados inflenciam na classificaçãp do vinho ? Tipo de problema: Classificação Tipo de aprendizado: supervisionado Utiliza dois grupos: X, com os atributos a serem utilizados na predição do valor Variáveis de entrada (com base em testes físico-químicos): Y, com o atributo para o qual se deve fazer a predição do valor (atributo-alvo) qualidade (pontuação entre 0 e 10) O atributo alvo é categórico

Fonte:

Conjunto de dados sobre Vinho da UCI Machine Learning Repository Web

Informação Relevante: Os dois conjuntos de dados estão relacionados com as variantes tinto e branco do vinho português "Vinho Verde".

Para mais pormenores, consultar: http://www.vinhoverde.pt/en/ ou a referência [Cortez et al., 2009]. Devido a questões de privacidade e logística, apenas variáveis físico-químicas (entradas) e sensoriais (saídas) estão disponíveis (por exemplo, não há dados sobre tipos de uva, marca de vinho, preço de venda do vinho, etc.).

Número de Instâncias: vinho tinto - 1599; vinho branco - 4898. Número de Atributos: 11 + atributo de saída Nota: vários dos atributos podem estar correlacionados, por isso faz sentido aplicar algum tipo de selecção de características.

Informação sobre os atributos: Para mais informações, ler [Cortez et al., 2009].

Documentos relevantes:

P. Cortez, A. Cerdeira, F. Almeida, T. Matos e J. Reis. Modelagem de preferências de vinho por mineração de dados de propriedades físico-químicas. Em Decision Support Systems, Elsevier, 47(4):547-553, 2009. Disponível em: Web Citação: P. Cortez, A. Cerdeira, F. Almeida, T. Matos e J. Reis. Modelagem de preferências de vinho por mineração de dados de propriedades físico-químicas. Em Decision Support Systems, Elsevier, 47(4):547-553, 2009.

Paulo Cortez, Universidade do Minho, Guimarães, Portugal, Web A. Cerdeira, F. Almeida, T. Matos e J. Reis, Comissão de Viticultura da Região dos Vinhos Verdes (CVRVV) , Porto, Portugal @2009

2. CARGA DOS DADOS: /¶

In [ ]:
# imports necessários 
import  numpy  as  np      # computação matemática (arrays). 
import  pandas  as  pd     # manipulação e análise de dados, o "excel" do  Python # modelo usado como guia https://colab.research.go ogle.com/drive/19FMe4m37xDNZPHtz-40ZA-MMvZpMpCyt?u sp=sharing#scrollTo= t_CREFDWPt2G # linguagem de referência python https://colab.researc h.google.com/drive/1RVt_lKLXvRm_sglQqi5ts_u2GUYm87 H0?usp=sharing#scrollTo=pJrSlM8M0d**
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as ms # para tratamento de missings
from matplotlib import cm
from pandas import set_option
from pandas.plotting import scatter_matrix
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier
In [ ]:
# configuração para não exibir os warnings
import warnings
warnings.filterwarnings("ignore")
In [ ]:
# importando dados de um arquvo  crv para dataframe vinhoreddados : VINHO TINTO
 # url a importar
url_dados_wred = 'https://wdz.eng.br/Curso/Atividade2/arquivos/winequality-red.csv'
#rotulos dos atributos do dadtaset
atributosred = ['acidezfixa', 'acidezvolátil', 'ácidocítrico', 'acucarresidual', 'cloretos', 'dióxidoenxofrelivre', 'dióxidoenxofretotal', 'densidade', 'pH', 'sulfatos', 'alcool']
#carga do dataset
vinhoreddados = pd.read_csv(url_dados_wred, delimiter=';')
In [ ]:
vinhoreddados.head()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
In [ ]:
# importando dados de um arquvo  crv para dataframe vinhowhitedados.  VINHO BRANCO
 # url a importar
url_dados_wwhite = 'https://wdz.eng.br/Curso/Atividade2/arquivos/winequality-white.csv'
#rotulos dos atributos do dadtaset
atributoswhite = ['acidezfixa', 'acidezvolátil', 'ácidocítrico', 'acucarresidual', 'cloretos', 'dióxidoenxofrelivre', 'dióxidoenxofretotal', 'densidade', 'pH', 'sulfatos', 'alcool']
#carga do dataset
vinhowhitedados = pd.read_csv(url_dados_wwhite, delimiter=';')
In [ ]:
vinhowhitedados.head()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.0 0.27 0.36 20.7 0.045 45.0 170.0 1.0010 3.00 0.45 8.8 6
1 6.3 0.30 0.34 1.6 0.049 14.0 132.0 0.9940 3.30 0.49 9.5 6
2 8.1 0.28 0.40 6.9 0.050 30.0 97.0 0.9951 3.26 0.44 10.1 6
3 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6
4 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6

3. ANÁLISE DE DADOS: /¶

  1. Para selecionar caracteristicas ver em link text

https://colab.research.google.com/drive/1MCCMFdsGaXIQjdAYpFrqTyw0bvtXhuYb#scrollTo=8w7HuVO9-XRo

PERGUNTAS SOBRE A AMOSTRA:

  • Saber quantas amostras foram enviadas?
  • Quais são as 5 primeiras amostras mostrando as caracteristicas?
  • Veio faltando algum valor de caracteristica?
  • Veio faltando alguma classificação?

3.1 Estatísticas Descritivas¶

Exame as dimensões do conjunto de dados, suas informações e alguns exemplos de

  • Quantidade de registros de vinho Tinto e Branco
  • Quantidade de elementos da matriz (qtd registro, numero de caracteristicas)
  • Caracteristicas de um vinho
  • Quantidade de valores não nulos de vinho Tinto e Branco
  • Lista dos 10 primeiros registros de vinho Tinto e Branco
  • Lista dos 10 últimos registros de vinho Tinto e Branco
  • Tipo de valor de cada caracteristica e variavel alvo (qualidade do vinho)
  • (média, desvio padrão, mínimo, máximo e os quartis por caracteristica e qualidade do vinho
  • distribuição das classes de qualidade do vinho para TINTO e BRANCO
In [ ]:
 
In [ ]:
# exibindo as dimensões do dataset
vinhoreddados.shape
Out[ ]:
(1599, 12)
In [ ]:
# exibindo as dimensões do dataset de vinho Branco
vinhowhitedados.shape
Out[ ]:
(4898, 12)
In [ ]:
# Mostra as informações do dataset
print(vinhoreddados.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1599 entries, 0 to 1598
Data columns (total 12 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   fixed acidity         1599 non-null   float64
 1   volatile acidity      1599 non-null   float64
 2   citric acid           1599 non-null   float64
 3   residual sugar        1599 non-null   float64
 4   chlorides             1599 non-null   float64
 5   free sulfur dioxide   1599 non-null   float64
 6   total sulfur dioxide  1599 non-null   float64
 7   density               1599 non-null   float64
 8   pH                    1599 non-null   float64
 9   sulphates             1599 non-null   float64
 10  alcohol               1599 non-null   float64
 11  quality               1599 non-null   int64  
dtypes: float64(11), int64(1)
memory usage: 150.0 KB
None
In [ ]:
# Mostra as informações do dataset
print(vinhowhitedados.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4898 entries, 0 to 4897
Data columns (total 12 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   fixed acidity         4898 non-null   float64
 1   volatile acidity      4898 non-null   float64
 2   citric acid           4898 non-null   float64
 3   residual sugar        4898 non-null   float64
 4   chlorides             4898 non-null   float64
 5   free sulfur dioxide   4898 non-null   float64
 6   total sulfur dioxide  4898 non-null   float64
 7   density               4898 non-null   float64
 8   pH                    4898 non-null   float64
 9   sulphates             4898 non-null   float64
 10  alcohol               4898 non-null   float64
 11  quality               4898 non-null   int64  
dtypes: float64(11), int64(1)
memory usage: 459.3 KB
None
In [ ]:
# Mostra as 10 primeiras linhas do dataset. VINHO TINTO
vinhoreddados.head(10)
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
5 7.4 0.66 0.00 1.8 0.075 13.0 40.0 0.9978 3.51 0.56 9.4 5
6 7.9 0.60 0.06 1.6 0.069 15.0 59.0 0.9964 3.30 0.46 9.4 5
7 7.3 0.65 0.00 1.2 0.065 15.0 21.0 0.9946 3.39 0.47 10.0 7
8 7.8 0.58 0.02 2.0 0.073 9.0 18.0 0.9968 3.36 0.57 9.5 7
9 7.5 0.50 0.36 6.1 0.071 17.0 102.0 0.9978 3.35 0.80 10.5 5
In [ ]:
# Mostra as 10 primeiras linhas do dataset. VINHO BRANCO
vinhowhitedados.head(10)
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.0 0.27 0.36 20.7 0.045 45.0 170.0 1.0010 3.00 0.45 8.8 6
1 6.3 0.30 0.34 1.6 0.049 14.0 132.0 0.9940 3.30 0.49 9.5 6
2 8.1 0.28 0.40 6.9 0.050 30.0 97.0 0.9951 3.26 0.44 10.1 6
3 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6
4 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6
5 8.1 0.28 0.40 6.9 0.050 30.0 97.0 0.9951 3.26 0.44 10.1 6
6 6.2 0.32 0.16 7.0 0.045 30.0 136.0 0.9949 3.18 0.47 9.6 6
7 7.0 0.27 0.36 20.7 0.045 45.0 170.0 1.0010 3.00 0.45 8.8 6
8 6.3 0.30 0.34 1.6 0.049 14.0 132.0 0.9940 3.30 0.49 9.5 6
9 8.1 0.22 0.43 1.5 0.044 28.0 129.0 0.9938 3.22 0.45 11.0 6
In [ ]:
# Mostra as 10 últimas linhas do dataset
vinhoreddados.tail(10)
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1589 6.6 0.725 0.20 7.8 0.073 29.0 79.0 0.99770 3.29 0.54 9.2 5
1590 6.3 0.550 0.15 1.8 0.077 26.0 35.0 0.99314 3.32 0.82 11.6 6
1591 5.4 0.740 0.09 1.7 0.089 16.0 26.0 0.99402 3.67 0.56 11.6 6
1592 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1593 6.8 0.620 0.08 1.9 0.068 28.0 38.0 0.99651 3.42 0.82 9.5 6
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
In [ ]:
# Mostra as 10 últimas linhas do dataset
vinhoreddados.tail(10)
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1589 6.6 0.725 0.20 7.8 0.073 29.0 79.0 0.99770 3.29 0.54 9.2 5
1590 6.3 0.550 0.15 1.8 0.077 26.0 35.0 0.99314 3.32 0.82 11.6 6
1591 5.4 0.740 0.09 1.7 0.089 16.0 26.0 0.99402 3.67 0.56 11.6 6
1592 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1593 6.8 0.620 0.08 1.9 0.068 28.0 38.0 0.99651 3.42 0.82 9.5 6
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
In [ ]:
# exibindo as últimas linhas
vinhowhitedados.tail(10)
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
4888 6.8 0.220 0.36 1.20 0.052 38.0 127.0 0.99330 3.04 0.54 9.2 5
4889 4.9 0.235 0.27 11.75 0.030 34.0 118.0 0.99540 3.07 0.50 9.4 6
4890 6.1 0.340 0.29 2.20 0.036 25.0 100.0 0.98938 3.06 0.44 11.8 6
4891 5.7 0.210 0.32 0.90 0.038 38.0 121.0 0.99074 3.24 0.46 10.6 6
4892 6.5 0.230 0.38 1.30 0.032 29.0 112.0 0.99298 3.29 0.54 9.7 5
4893 6.2 0.210 0.29 1.60 0.039 24.0 92.0 0.99114 3.27 0.50 11.2 6
4894 6.6 0.320 0.36 8.00 0.047 57.0 168.0 0.99490 3.15 0.46 9.6 5
4895 6.5 0.240 0.19 1.20 0.041 30.0 111.0 0.99254 2.99 0.46 9.4 6
4896 5.5 0.290 0.30 1.10 0.022 20.0 110.0 0.98869 3.34 0.38 12.8 7
4897 6.0 0.210 0.38 0.80 0.020 22.0 98.0 0.98941 3.26 0.32 11.8 6
In [ ]:
# Verificando os tipos de cada coluna
vinhoreddados.dtypes
Out[ ]:
fixed acidity           float64
volatile acidity        float64
citric acid             float64
residual sugar          float64
chlorides               float64
free sulfur dioxide     float64
total sulfur dioxide    float64
density                 float64
pH                      float64
sulphates               float64
alcohol                 float64
quality                   int64
dtype: object
In [ ]:
# Verificando os tipos de cada coluna
vinhowhitedados.dtypes
Out[ ]:
fixed acidity           float64
volatile acidity        float64
citric acid             float64
residual sugar          float64
chlorides               float64
free sulfur dioxide     float64
total sulfur dioxide    float64
density                 float64
pH                      float64
sulphates               float64
alcohol                 float64
quality                   int64
dtype: object
In [ ]:
# Faz um resumo estatístico do dataset (média, desvio padrão, mínimo, máximo e os quartis)
vinhoreddados.describe()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
count 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000
mean 8.319637 0.527821 0.270976 2.538806 0.087467 15.874922 46.467792 0.996747 3.311113 0.658149 10.422983 5.636023
std 1.741096 0.179060 0.194801 1.409928 0.047065 10.460157 32.895324 0.001887 0.154386 0.169507 1.065668 0.807569
min 4.600000 0.120000 0.000000 0.900000 0.012000 1.000000 6.000000 0.990070 2.740000 0.330000 8.400000 3.000000
25% 7.100000 0.390000 0.090000 1.900000 0.070000 7.000000 22.000000 0.995600 3.210000 0.550000 9.500000 5.000000
50% 7.900000 0.520000 0.260000 2.200000 0.079000 14.000000 38.000000 0.996750 3.310000 0.620000 10.200000 6.000000
75% 9.200000 0.640000 0.420000 2.600000 0.090000 21.000000 62.000000 0.997835 3.400000 0.730000 11.100000 6.000000
max 15.900000 1.580000 1.000000 15.500000 0.611000 72.000000 289.000000 1.003690 4.010000 2.000000 14.900000 8.000000
In [ ]:
# Faz um resumo estatístico do dataset (média, desvio padrão, mínimo, máximo e os quartis)
vinhowhitedados.describe()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
count 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000
mean 6.854788 0.278241 0.334192 6.391415 0.045772 35.308085 138.360657 0.994027 3.188267 0.489847 10.514267 5.877909
std 0.843868 0.100795 0.121020 5.072058 0.021848 17.007137 42.498065 0.002991 0.151001 0.114126 1.230621 0.885639
min 3.800000 0.080000 0.000000 0.600000 0.009000 2.000000 9.000000 0.987110 2.720000 0.220000 8.000000 3.000000
25% 6.300000 0.210000 0.270000 1.700000 0.036000 23.000000 108.000000 0.991723 3.090000 0.410000 9.500000 5.000000
50% 6.800000 0.260000 0.320000 5.200000 0.043000 34.000000 134.000000 0.993740 3.180000 0.470000 10.400000 6.000000
75% 7.300000 0.320000 0.390000 9.900000 0.050000 46.000000 167.000000 0.996100 3.280000 0.550000 11.400000 6.000000
max 14.200000 1.100000 1.660000 65.800000 0.346000 289.000000 440.000000 1.038980 3.820000 1.080000 14.200000 9.000000
In [ ]:
Vamos agora verificar se o dataset tem as classes balanceadas para que possamos tratar o desbalanceamento posteriormente, se necessário. 
Veremos que as classes 0 (qualidade fraca) e 9 (qualidade excelente) estão desequilibradas. 
Vamos guardar esta informação, pois possivelmente precisaremos realizar algum tipo de tratamento nas próximas etapas.
In [ ]:
# distribuição das classes. VINHO TINTO
print(vinhoreddados.groupby('quality').size())
quality
3     10
4     53
5    681
6    638
7    199
8     18
dtype: int64
In [ ]:
# distribuição das classes  VINHO BRANCO
print(vinhowhitedados.groupby('quality').size())
quality
3      20
4     163
5    1457
6    2198
7     880
8     175
9       5
dtype: int64

3.1 Visualizações Unimodais¶

Histograma para cada caracteristica do vinho. Veremos que os atributos açucar residual, acido citric, dioxido sulfur total, dioxido sulfor livre, seguem uma distribuição exponencial, e que as colunas densidade segue uma distribuição aproximadamente normal, ph seguem uma distribuição normal

In [ ]:
import matplotlib
import matplotlib.pyplot as plt
In [ ]:
# Histograma
vinhoreddados.hist(figsize = (15,10))
plt.show()
In [ ]:
# Histograma
vinhowhitedados.hist(figsize = (15,10))
plt.show()

O Gráfico de Densidade, ou Density Plot, é bem parecido com o histograma, mas com uma visualização um pouco diferente. Com ele, pode ser mais fácil identificar a distribuição do atributos do dataset. Assim como fizemos com o histograma, vamos criar um density plot para cada atributo do dataset.

Veremos que muitos dos atributos têm uma distribuição distorcida. Uma transformação como a Box-Cox, que pode aproximar a distribuição de uma Normal, pode ser útil neste caso.

In [ ]:
import seaborn as sns
In [ ]:
# Density Plot
vinhoreddados.plot(kind = 'density', subplots = True, layout = (4,4), sharex = False, figsize = (15,10))
plt.show()
In [ ]:
# Density Plot
vinhowhitedados.plot(kind = 'density', subplots = True, layout = (4,4), sharex = False, figsize = (15,10))
plt.show()
In [ ]:
 

Vamos agora trabalhar com boxplots. No boxblot, a linha no centro (vermelha) representa o valor da mediana (segundo quartil ou p50). A linha abaixo é o 1o quartil (p25) e a linha acima o terceiro quartil (p75). O boxplot ajuda a ter uma ideia da dispersão dos dataset e os possíveis outliers.

OBS: Se um ponto do dataset é muito distante da média (acima de 3 desvios padrão da média), pode ser considerado outlier.

Nos gráficos bloxplot, veremos que a dispersão dos atributos do dataset é bem diferente.

In [ ]:
# Boxplot
vinhoreddados.plot(kind = 'box', subplots = True, layout = (4,4), sharex = False, sharey = False, figsize = (15,10))
plt.show()
In [ ]:
# Boxplot
vinhowhitedados.plot(kind = 'box', subplots = True, layout = (4,4), sharex = False, sharey = False, figsize = (15,10))
plt.show()
In [ ]:
 

3.2 Visualizações Multimodais¶

Ao visualizar as correlações entre os atributos através da matriz de correlação, perceberemos que parece haver alguma estrutura na ordem dos atributos. O azul ao redor da diagonal sugere que os atributos que estão próximos um do outro são geralmente mais correlacionados entre si. Os vermelhos também sugerem alguma correlação negativa moderada, a medida que os atributos

Vamos agora verificar a covariância entre as variáveis numéricas do dataset. A covariância representa como duas variáveis numéricas estão relacionadas. Existem várias formas de calcular a correlação entre duas variáveis, como por exemplo, o coeficiente de correlação de Pearson, que pode ser:

  • Próximo de -1 : há uma correlação negativa entre as variáveis,
  • Próximo de +1: há uma correlação positiva entre as variáveis.
  • 0: não há correlação entre as variáveis.

OBS: Esta informação é relevante porque alguns algoritmos como regressão linear e regressão logística podem apresentar problemas de performance se houver atributos altamente correlacionados. Vale a pena consultar a documentação do algoritmo para verificar se algum tipo de tratamento de dataset é necessário.

Falamos anteriormente da importância da correlação entre os atributos, e agora iremos visualizar esta informação em formato gráfico. A matriz de correlação exibe graficamente a correlação entre os atributos numéricos do dataset.estão mais distantes um do outro na ordenação.

O código a seguir exibe a matriz de correlação.

In [ ]:
# Matriz de Correlação com Matplotlib Seaborn.  VINHO TINTO
sns.heatmap(vinhoreddados.corr(), annot=True, cmap='RdBu');
In [ ]:
# Matriz de Correlação com Matplotlib Seaborn. VINHO BRANCO
sns.heatmap(vinhowhitedados.corr(), annot=True, cmap='RdBu');

Por sua vez, o gráfico de dispersão (scatter plot) mostra o relacionamento entre duas variáveis. Vamos exibir um para cada par de atributos dos dataset, usando o Seaborn.

In [ ]:
# Scatter Plot com Seaborn - Variação 1

sns.pairplot(vinhoreddados)
Out[ ]:
<seaborn.axisgrid.PairGrid at 0x7f13ead5a820>
In [ ]:
# Scatter Plot com Seaborn - Variação 1

sns.pairplot(vinhowhitedados)
Out[ ]:
<seaborn.axisgrid.PairGrid at 0x7f13e56b2520>

OUTROS

In [ ]:
# selecionando uma coluna específica
vinhoreddados['fixed acidity']
Out[ ]:
0        7.4
1        7.8
2        7.8
3       11.2
4        7.4
        ... 
1594     6.2
1595     5.9
1596     6.3
1597     5.9
1598     6.0
Name: fixed acidity, Length: 1599, dtype: float64
In [ ]:
# selecionando um subconjunto de linhas consecutivas
vinhoreddados[7:11]
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
7 7.3 0.65 0.00 1.2 0.065 15.0 21.0 0.9946 3.39 0.47 10.0 7
8 7.8 0.58 0.02 2.0 0.073 9.0 18.0 0.9968 3.36 0.57 9.5 7
9 7.5 0.50 0.36 6.1 0.071 17.0 102.0 0.9978 3.35 0.80 10.5 5
10 6.7 0.58 0.08 1.8 0.097 15.0 65.0 0.9959 3.28 0.54 9.2 5
In [ ]:
# selecionando um subconjunto de colunas para todas as linhas
vinhoreddados.loc[:, ['fixed acidity', 'volatile acidity', 'citric acid']]
Out[ ]:
fixed acidity volatile acidity citric acid
0 7.4 0.700 0.00
1 7.8 0.880 0.00
2 7.8 0.760 0.04
3 11.2 0.280 0.56
4 7.4 0.700 0.00
... ... ... ...
1594 6.2 0.600 0.08
1595 5.9 0.550 0.10
1596 6.3 0.510 0.13
1597 5.9 0.645 0.12
1598 6.0 0.310 0.47

1599 rows × 3 columns

In [ ]:
# selecionando um subconjunto linhas e colunas
vinhoreddados.loc[7:11, ['fixed acidity', 'volatile acidity', 'citric acid']]
Out[ ]:
fixed acidity volatile acidity citric acid
7 7.3 0.65 0.00
8 7.8 0.58 0.02
9 7.5 0.50 0.36
10 6.7 0.58 0.08
11 7.5 0.50 0.36
In [ ]:
 
In [ ]:
# selecionando linhas segundo um critério
vinhoreddados[vinhoreddados['fixed acidity'] > 3.5]
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.700 0.00 1.9 0.076 11.0 34.0 0.99780 3.51 0.56 9.4 5
1 7.8 0.880 0.00 2.6 0.098 25.0 67.0 0.99680 3.20 0.68 9.8 5
2 7.8 0.760 0.04 2.3 0.092 15.0 54.0 0.99700 3.26 0.65 9.8 5
3 11.2 0.280 0.56 1.9 0.075 17.0 60.0 0.99800 3.16 0.58 9.8 6
4 7.4 0.700 0.00 1.9 0.076 11.0 34.0 0.99780 3.51 0.56 9.4 5
... ... ... ... ... ... ... ... ... ... ... ... ...
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6

1599 rows × 12 columns

In [ ]:
 
In [ ]:
# criando um novo dataframe com vinhoreddados + uma nova linha com um valor missing para o próximo exemplo
df = vinhoreddados.append({'pH': 4.0, 
                  'sulphates': 5.0, 
                  'alcohol': 0.4, 
                  'quality': '1'},
                  ignore_index=True)
df.tail()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
1599 NaN NaN NaN NaN NaN NaN NaN NaN 4.00 5.00 0.4 1
In [ ]:
 
In [ ]:
# eliminando linhas que tenham ALGUM valor missing

df = df.dropna(how='any')
df.tail()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
In [ ]:
# eliminando linhas que tenham TODOS os valores missing
df = df.dropna(how='all')
df.tail()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
In [ ]:
 
In [ ]:
# definindo valores para o preenchimento de missings
values = {'density': vinhoreddados['quality'].median(), 
          'pH': 4.0, 'sulphates': 5.0, 
          'alcohol': 0.4, 'quality': '8'}
vinhoreddados = vinhoreddados.fillna(value=values)
vinhoreddados.tail()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6
In [ ]:
 
In [ ]:
# exibindo a média de cada atributo, agrupado por espécie
vinhoreddados.groupby('quality').mean()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol
quality
3 8.360000 0.884500 0.171000 2.635000 0.122500 11.000000 24.900000 0.997464 3.398000 0.570000 9.955000
4 7.779245 0.693962 0.174151 2.694340 0.090679 12.264151 36.245283 0.996542 3.381509 0.596415 10.265094
5 8.167254 0.577041 0.243686 2.528855 0.092736 16.983847 56.513950 0.997104 3.304949 0.620969 9.899706
6 8.347179 0.497484 0.273824 2.477194 0.084956 15.711599 40.869906 0.996615 3.318072 0.675329 10.629519
7 8.872362 0.403920 0.375176 2.720603 0.076588 14.045226 35.020101 0.996104 3.290754 0.741256 11.465913
8 8.566667 0.423333 0.391111 2.577778 0.068444 13.277778 33.444444 0.995212 3.267222 0.767778 12.094444
In [ ]:
 
In [ ]:
# se tivéssemos duas colunas categóricas, poderiamos fazer subgrupos...

df = pd.DataFrame({'A': ['16.0', '13.0', '16.0', '13.0', '16.0', '13.0', '16.0', '13.0','16.0', '13.0', '16.0', '13.0'],
                   'B': ['1', '1', '2', '3', '2', '2', '1', '3', '1', '2', '1', '3'],
                   'C': np.random.randn(12), # gera 12 números aleatórios
                   'D': np.random.randn(12)})
df
Out[ ]:
A B C D
0 16.0 1 -0.791203 -1.102770
1 13.0 1 -0.508607 -0.376677
2 16.0 2 1.712126 -0.565894
3 13.0 3 0.067513 -0.101723
4 16.0 2 -0.705688 -0.966695
5 13.0 2 -0.511492 -1.065836
6 16.0 1 0.619149 -1.342145
7 13.0 3 0.612262 0.455423
8 16.0 1 -0.621624 -0.701874
9 13.0 2 1.025349 -0.027342
10 16.0 1 0.168052 0.473770
11 13.0 3 -0.450048 0.226918
In [ ]:
 
In [ ]:
# ... e exibir sua soma...
df.groupby(['A', 'B']).sum()
Out[ ]:
C D
A B
13.0 1 -0.508607 -0.376677
2 0.513857 -1.093178
3 0.229728 0.580618
16.0 1 -0.625625 -2.673020
2 1.006438 -1.532588
In [ ]:
# ... ou sua média/mediana
df.groupby(['A', 'B']).mean()
# df.groupby(['A', 'B']).median()
Out[ ]:
C D
A B
13.0 1 -0.508607 -0.376677
2 0.256929 -0.546589
3 0.076576 0.193539
16.0 1 -0.156406 -0.668255
2 0.503219 -0.766294
In [ ]:
 
In [ ]:
# adicionando uma nova coluna ao dataframe (não tenho ideia se essa métrica faz sentido!)
vinhoreddados['razao'] = (vinhoreddados['quality'] * vinhoreddados['quality'])
In [ ]:
# exibindo o conteúdo da nova coluna
vinhoreddados['razao'].head()
Out[ ]:
0    25
1    25
2    25
3    36
4    25
Name: razao, dtype: int64
In [ ]:
vinhoreddados
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality razao
0 7.4 0.700 0.00 1.9 0.076 11.0 34.0 0.99780 3.51 0.56 9.4 5 25
1 7.8 0.880 0.00 2.6 0.098 25.0 67.0 0.99680 3.20 0.68 9.8 5 25
2 7.8 0.760 0.04 2.3 0.092 15.0 54.0 0.99700 3.26 0.65 9.8 5 25
3 11.2 0.280 0.56 1.9 0.075 17.0 60.0 0.99800 3.16 0.58 9.8 6 36
4 7.4 0.700 0.00 1.9 0.076 11.0 34.0 0.99780 3.51 0.56 9.4 5 25
... ... ... ... ... ... ... ... ... ... ... ... ... ...
1594 6.2 0.600 0.08 2.0 0.090 32.0 44.0 0.99490 3.45 0.58 10.5 5 25
1595 5.9 0.550 0.10 2.2 0.062 39.0 51.0 0.99512 3.52 0.76 11.2 6 36
1596 6.3 0.510 0.13 2.3 0.076 29.0 40.0 0.99574 3.42 0.75 11.0 6 36
1597 5.9 0.645 0.12 2.0 0.075 32.0 44.0 0.99547 3.57 0.71 10.2 5 25
1598 6.0 0.310 0.47 3.6 0.067 18.0 42.0 0.99549 3.39 0.66 11.0 6 36

1599 rows × 13 columns

In [ ]:
# criando uma função para processamento de dados.
def categoriza_razao(razao):
    if razao >= 40:
        return 'Grande'
    elif razao >= 15:
        return 'Média'
    else:
        return 'Pequena'
In [ ]:
# adicionando uma nova coluna ao dataframe a partir do processamento realizado com a função que criamos
vinhoreddados['cat_razao'] = vinhoreddados['razao'].apply(categoriza_razao)
In [ ]:
# exibindo o conteúdo do dataframe com as novas colunas
vinhoreddados.head()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality razao cat_razao
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5 25 Média
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5 25 Média
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5 25 Média
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6 36 Média
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5 25 Média
In [ ]:
# exibindo a distribuicao da nova coluna
pd.value_counts(vinhoreddados['cat_razao'])
Out[ ]:
Média      1372
Grande      217
Pequena      10
Name: cat_razao, dtype: int64
In [ ]:
# excluindo colunas do dataframe
## OBS1: a opção axis=1 define que iremos excluir uma coluna (0 para linha - default)
## OBS2: inplace=True define que a alteração irá modificar o dataframe em memória, sem necessidade de reatribuí-lo a uma nova variável

vinhoreddados.drop(['razao', 'cat_razao'], axis=1, inplace=True)

# para excluir apenas uma coluna, faríamos: vinhoreddados.drop(['razao'], axis=1, inplace=True)
In [ ]:
# exibindo o conteúdo do dataframe com a exclusão das colunas
vinhoreddados.head()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5

4. Pré-Processamento de dados¶

Nesta etapa, poderíamos realizar diversas operações de preparação de dados, como por exemplo, tratamento de valores missings (faltantes), limpeza de dados, transformações como one-hot-encoding, seleção de características (feature selection), entre outras não mostradas neste notebook. Lembre-se de não criar uma versão padronizada/normalizada dos dados neste momento (apesar de serem operações de pré-processamento) para evitar o Data Leakage.

4.1. Tratamento de Missings e Limpeza¶

Sabemos que o datset Vinhos não tem missings aparentes, mas valores "0" que parecem ser missings. Vamos então fazer este tratamento e criar uma nova visão do nosso dataset.

In [ ]:
# verificando nulls no dataset
vinhoreddados.isnull().sum()
Out[ ]:
fixed acidity           0
volatile acidity        0
citric acid             0
residual sugar          0
chlorides               0
free sulfur dioxide     0
total sulfur dioxide    0
density                 0
pH                      0
sulphates               0
alcohol                 0
quality                 0
dtype: int64
In [ ]:
# salvando um NOVO dataset para tratamento de missings (cuidado para não sobrescrever o dataset original!)

# recuperando os nomes das colunas
col = list(vinhoreddados.columns)

# o novo dataset irá conter todas as colunas com exceção da última (quality)
atributos = vinhoreddados[col[0:-1]]

# substituindo os zeros por NaN
atributos.replace(0, np.nan, inplace=True)

# exibindo visualização matricial da nulidade do dataset
ms.matrix(atributos)
Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca4675b80>
In [ ]:
# removendo as colunas 'residual sugar' e 'chlorides'
atributos.drop(['residual sugar', 'chlorides'], axis=1, inplace= True)

# exibindo visualização matricial da nulidade do dataset
ms.matrix(atributos)
Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca1d9af40>
In [ ]:
# substituindo os NaN de 'preg' por 0
atributos['citric acid'].fillna(0, inplace=True)

# substituindo os NaN de 'free sulfur dioxide', 'total sulfur dioxide'e 'density' pela mediana da coluna
atributos['free sulfur dioxide'].fillna(atributos['free sulfur dioxide'].median(), inplace=True)
atributos['total sulfur dioxide'].fillna(atributos['total sulfur dioxide'].median(), inplace=True)
atributos['density'].fillna(atributos['density'].median(), inplace=True)

# exibindo visualização matricial da nulidade do dataset
ms.matrix(atributos)
Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca18abb20>
In [ ]:
# Guardando o novo dataset para testes futuros
datasetSemMissings = atributos

# incluindo a coluna 'quality' no novo dataset
datasetSemMissings['quality'] = vinhoreddados['quality']

# exibindo as primeiras linhas
datasetSemMissings.head()
Out[ ]:
fixed acidity volatile acidity citric acid free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 11.0 34.0 0.9978 3.51 0.56 9.4 5

4.2. Separação em conjunto de treino e conjunto de teste¶

Conjunto de dados original:

  • vinhoreddados (Valores das caracteristicas do vinho TINTO).
  • vinhowhitedados (Valores das caracteristicas do vinho BRANCO)

Amostra dos dados que não será usada para a construção do modelo, mas somente no fim do projeto para confirmar a precisão do modelo final).

80% do conjunto de dados para modelagem: 20% do conjunto de dados para teste:

Estratégia train-test-split.

  1. Sinalizar quais são as colunas de atributos (X - 0 a 7) e qual é a coluna das classes (Y - 8).
  2. Especificaremos do tamanho do conjunto de teste desejado e uma semente (para garantir a reprodutibilidade dos resultados).
  3. Separação dos conjuntos de treino e teste através do comando train_test_split, que retornará 4 estruturas de dados:
  • os atributos e classes para o conjunto de teste
  • os atributos e classes para o conjunto de treino.
In [ ]:
vinhoreddados.head()
Out[ ]:
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
In [ ]:
import numpy as np
from sklearn.model_selection import train_test_split
X, y = np.arange(10).reshape((5, 2)), range(5)
print ("X=", X)
print ("y=", y)
X= [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]
y= range(0, 5)
In [ ]:
test_size = 0.29 #29 %
seed = 7
print ("test_size=", test_size)
print ("seed=",seed)
# Separação em conjuntos de treino e teste (dataset original)
array = vinhoreddados.values
X = array[:,0:8]
y = array[:,8]
print ("array=", array,"X=", X, "Y=", y)
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=test_size, shuffle=True, random_state=seed) # sem estratificação
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, shuffle=True, random_state=seed, stratify=y) # com estratificação
test_size= 0.29
seed= 7
array= [[ 7.4    0.7    0.    ...  0.56   9.4    5.   ]
 [ 7.8    0.88   0.    ...  0.68   9.8    5.   ]
 [ 7.8    0.76   0.04  ...  0.65   9.8    5.   ]
 ...
 [ 6.3    0.51   0.13  ...  0.75  11.     6.   ]
 [ 5.9    0.645  0.12  ...  0.71  10.2    5.   ]
 [ 6.     0.31   0.47  ...  0.66  11.     6.   ]] X= [[7.4000e+00 7.0000e-01 0.0000e+00 ... 1.1000e+01 3.4000e+01 9.9780e-01]
 [7.8000e+00 8.8000e-01 0.0000e+00 ... 2.5000e+01 6.7000e+01 9.9680e-01]
 [7.8000e+00 7.6000e-01 4.0000e-02 ... 1.5000e+01 5.4000e+01 9.9700e-01]
 ...
 [6.3000e+00 5.1000e-01 1.3000e-01 ... 2.9000e+01 4.0000e+01 9.9574e-01]
 [5.9000e+00 6.4500e-01 1.2000e-01 ... 3.2000e+01 4.4000e+01 9.9547e-01]
 [6.0000e+00 3.1000e-01 4.7000e-01 ... 1.8000e+01 4.2000e+01 9.9549e-01]] Y= [3.51 3.2  3.26 ... 3.42 3.57 3.39]
In [ ]:
# Separação em conjuntos de treino e teste (dataset sem missings - 2 colunas a menos!)

array = datasetSemMissings.values
print ("array", array)
X_sm = array[:,0:6]
y_sm = array[:,6]
X_train_sm, X_test_sm, y_train_sm, y_test_sm = train_test_split(X_sm, y_sm,test_size=test_size, shuffle=True, random_state=seed) # sem estratificação
#X_train_sm, X_test_sm, y_train_sm, y_test_sm = train_test_split(X_sm, y_sm,
#    test_size=test_size, shuffle=True, random_state=seed, stratify=y_sm) # com estratificação
print ("X_train_sm=", X_train_sm)
print ("X_test_sm=", X_test_sm)
print ("y_train_sm=", y_train_sm)
print ("y_test_sm=", y_test_sm)
array [[ 7.4    0.7    0.    ...  0.56   9.4    5.   ]
 [ 7.8    0.88   0.    ...  0.68   9.8    5.   ]
 [ 7.8    0.76   0.04  ...  0.65   9.8    5.   ]
 ...
 [ 6.3    0.51   0.13  ...  0.75  11.     6.   ]
 [ 5.9    0.645  0.12  ...  0.71  10.2    5.   ]
 [ 6.     0.31   0.47  ...  0.66  11.     6.   ]]
X_train_sm= [[7.8000e+00 6.0000e-01 2.6000e-01 3.1000e+01 1.3100e+02 9.9622e-01]
 [6.3000e+00 3.6000e-01 1.9000e-01 1.5000e+01 3.9000e+01 9.9560e-01]
 [5.6000e+00 5.0000e-01 9.0000e-02 1.7000e+01 9.9000e+01 9.9370e-01]
 ...
 [8.1000e+00 8.2500e-01 2.4000e-01 5.0000e+00 1.3000e+01 9.9720e-01]
 [1.0900e+01 3.2000e-01 5.2000e-01 1.7000e+01 4.4000e+01 9.9734e-01]
 [6.9000e+00 5.0000e-01 4.0000e-02 1.9000e+01 4.9000e+01 9.9580e-01]]
X_test_sm= [[6.8000e+00 4.7000e-01 8.0000e-02 1.8000e+01 3.8000e+01 9.9553e-01]
 [1.0800e+01 4.0000e-01 4.1000e-01 7.0000e+00 1.7000e+01 9.9840e-01]
 [7.1000e+00 2.7000e-01 6.0000e-01 1.7000e+01 2.5000e+01 9.9814e-01]
 ...
 [9.8000e+00 6.6000e-01 3.9000e-01 2.1000e+01 5.9000e+01 9.9890e-01]
 [6.9000e+00 5.2000e-01 2.5000e-01 1.0000e+01 3.7000e+01 9.9685e-01]
 [6.8000e+00 8.1000e-01 5.0000e-02 6.0000e+00 1.4000e+01 9.9562e-01]]
y_train_sm= [3.21 3.56 3.63 ... 3.37 3.28 3.35]
y_test_sm= [3.3  3.08 3.38 3.43 3.32 3.21 3.5  3.15 3.35 3.12 3.4  3.28 3.   3.3
 3.28 3.32 3.29 3.37 3.35 4.01 3.44 3.32 3.34 3.29 3.26 3.27 3.45 3.27
 3.12 3.37 3.09 3.26 3.6  3.24 3.29 3.27 3.15 3.31 3.18 3.32 3.34 3.12
 3.33 3.39 3.52 3.51 3.32 3.51 3.32 3.27 3.27 3.48 3.21 3.32 3.37 3.36
 3.01 3.49 3.58 3.32 3.19 3.24 3.4  3.3  3.44 3.15 3.3  3.43 3.85 3.28
 3.25 3.12 3.2  3.07 3.54 3.26 3.37 3.11 3.23 3.36 3.49 3.22 3.35 3.43
 3.4  3.27 3.34 3.54 3.23 3.59 3.42 3.51 3.29 3.15 3.5  3.34 3.33 3.51
 3.22 3.14 3.53 3.33 3.15 3.26 3.3  3.31 3.51 3.31 3.45 3.39 3.71 3.52
 3.38 3.44 3.21 3.36 3.34 3.19 3.6  3.39 3.48 3.2  3.32 3.45 3.33 3.06
 3.19 3.17 3.3  3.32 3.21 3.39 3.15 3.23 3.11 3.45 3.58 3.37 3.18 3.26
 3.37 3.29 3.38 3.34 3.47 3.28 3.17 3.39 3.18 3.49 3.12 3.24 3.47 3.19
 3.23 3.39 3.27 3.33 3.25 3.45 3.2  3.25 3.06 3.32 3.36 3.51 3.42 3.34
 3.22 3.11 3.25 2.89 3.47 3.36 3.1  3.21 3.2  3.16 3.39 3.44 3.44 3.38
 3.07 3.09 3.28 3.44 3.26 3.22 3.61 2.89 3.31 3.3  3.17 3.31 3.45 3.15
 3.3  3.14 3.3  3.22 3.45 3.14 3.39 3.2  3.2  3.23 3.38 3.37 3.15 3.05
 3.16 3.68 3.26 3.37 3.3  3.26 3.46 3.13 3.38 3.44 3.37 3.17 3.39 3.41
 3.47 3.23 3.41 3.3  3.27 3.29 3.41 3.45 3.42 3.34 3.21 3.34 3.17 3.26
 3.43 3.19 3.17 3.34 3.16 3.3  3.2  3.38 3.05 3.11 3.4  3.15 3.41 3.4
 3.41 3.39 3.28 3.55 3.34 3.23 3.3  3.16 3.26 3.25 3.38 3.23 3.28 3.36
 3.29 3.41 3.28 3.37 3.32 3.18 3.39 3.1  3.38 3.29 3.25 3.36 3.09 3.2
 2.89 3.1  3.74 3.17 3.24 3.42 3.22 3.37 3.02 3.39 3.29 3.23 3.34 3.41
 3.15 3.02 3.39 3.2  3.25 2.98 3.08 3.42 3.22 3.19 3.57 3.18 3.34 3.36
 3.59 3.09 3.26 3.2  3.15 3.29 3.36 3.42 3.46 3.29 3.3  3.33 2.99 3.19
 3.57 2.74 3.26 3.49 3.24 3.58 3.42 3.18 3.52 3.03 3.33 3.26 3.15 3.15
 3.53 3.39 3.19 3.5  3.66 3.4  3.23 3.25 3.27 3.47 3.45 3.34 3.2  3.48
 3.62 3.17 3.38 3.32 3.36 2.98 3.33 3.1  3.48 3.28 3.16 3.05 3.46 3.08
 3.33 3.67 3.48 3.17 3.22 3.2  3.38 3.47 3.55 3.02 3.28 3.39 3.36 3.3
 3.33 3.23 3.29 3.06 3.4  3.24 3.51 3.42 3.15 3.47 3.4  3.3  3.14 3.22
 3.23 3.35 3.28 3.1  3.12 3.28 3.28 3.26 3.24 3.26 3.21 3.38 3.17 3.09
 3.22 3.38 3.44 3.   3.29 3.27 3.26 3.39 3.29 3.38 2.9  3.2  3.19 3.51
 3.38 3.38 3.57 3.3  3.15 3.39 3.19 3.28 3.21 3.1  3.49 3.34 3.12 3.27
 3.02 3.37 3.33 3.3  3.04 3.11 3.51 3.14 3.03 3.3  3.46 3.13 3.16 3.44
 3.23 3.26 3.44 3.21 3.4  3.3  3.5  3.19 3.56 3.18 3.32 3.18 3.25 3.37
 3.46 3.51]
In [ ]:
 

5. Modelos de Classificação¶

5.1. Criação e avaliação de modelos: linha base¶

Modelo: validação cruzada 10-fold (já detalhada anteriormente)

Avaliação: métrica de acurácia.

Parâmetros: número de folds e métrica de avaliação.

In [ ]:
# Parâmetros e partições da validação cruzada
scoring = 'accuracy'
num_particoes = 10
#kfold = KFold(n_splits=num_particoes, shuffle=True, random_state=seed) # sem estratificação
kfold = StratifiedKFold(n_splits=num_particoes, shuffle=True, random_state=seed) # com estratificação
print ("kfold=",kfold)
kfold= StratifiedKFold(n_splits=10, random_state=7, shuffle=True)

Em seguida, vamos criar uma linha base de desempenho para esse problema, verificando vários modelos diferentes com suas configurações padrão. Utilizaremos os modelos de Regressão Logística (LR), Árvores de classificação (CART), Máquinas de vetores de suporte (SVM), Naive Bayes (NB) e K-vizinhos mais próximos (KNN).

In [ ]:
# Lista que armazenará os modelos
models = []

# Criando os modelos e adicionando-os na lista de modelos
models.append(('LR', LogisticRegression(max_iter=200))) 
models.append(('KNN', KNeighborsClassifier())) 
models.append(('CART', DecisionTreeClassifier())) 
models.append(('NB', GaussianNB()))
models.append(('SVM', SVC()))

print("models=", models)
models= [('LR', LogisticRegression(max_iter=200)), ('KNN', KNeighborsClassifier()), ('CART', DecisionTreeClassifier()), ('NB', GaussianNB()), ('SVM', SVC())]

Vamos adicionar também os algoritmos de ensemble que estudamos:

In [ ]:
np.random.seed(7) # definindo uma semente global

# definindo os parâmetros do classificador base para o BaggingClassifier
base = DecisionTreeClassifier()
num_trees = 100
max_features = 3

# criando os modelos para o VotingClassifier - TODO: você poderia experimentar outras variações aqui!
bases = []
model1 = LogisticRegression(max_iter=200)
bases.append(('logistic', model1))
model2 = DecisionTreeClassifier()
bases.append(('cart', model2))
model3 = SVC()
bases.append(('svm', model3))

# Criando os modelos e adicionando-os na lista de modelos
models.append(('Bagging', BaggingClassifier(base_estimator=base, n_estimators=num_trees)))
models.append(('RF', RandomForestClassifier(n_estimators=num_trees, max_features=max_features)))
models.append(('ET', ExtraTreesClassifier(n_estimators=num_trees, max_features=max_features)))
models.append(('Ada', AdaBoostClassifier(n_estimators=num_trees)))
models.append(('GB', GradientBoostingClassifier(n_estimators=num_trees)))
models.append(('Voting', VotingClassifier(bases)))

Agora vamos comparar os resultados modelos criados, treinando-os com os dados do conjunto de treino e utilizando a técnica de validação cruzada. Para cada um dos modelos criados, executaremos a validação cruzada e, em seguida, exibiremos a acurácia média e o desvio padrão de cada um. Faremos isso tanto para o dataset original quanto para o dataset sem missings.

In [ ]:
# Aqui iremos armazenar os resultados tanto para o dataset original quanto para o dataset sem missings
results = []
names = []
results= []
names= []
In [ ]:
np.random.seed(7) # definindo uma semente global

# Avaliação dos modelos - dataset original

for name, model in models:
    cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-29-b71d1851714b> in <module>
      4 
      5 for name, model in models:
----> 6     cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
      7     results.append(cv_results)
      8     names.append(name)

NameError: name 'X_train' is not defined
In [ ]:
np.random.seed(7) # definindo uma semente global

# Avaliação dos modelos - dataset sem missings

for name, model in models:
    cv_results = cross_val_score(model, X_train_sm, y_train_sm, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)
LR: 0.757456 (0.045851)
KNN: 0.734506 (0.050838)
CART: 0.692226 (0.044920)
NB: 0.755870 (0.057395)
SVM: 0.760788 (0.041123)
Bagging: 0.763987 (0.034141)
RF: 0.762348 (0.035720)
ET: 0.759149 (0.037749)
Ada: 0.739662 (0.054412)
GB: 0.764014 (0.054441)
Voting: 0.757483 (0.040273)

Dica: organize os resultados numéricos em tabelas, para facilitar a sua comparação.

Estes resultados sugerem que, para ambos os datasets, diversos modelos têm potencial de trazerem bons resultados, porém, vale observar que estes são apenas valores médios de acurácia, sendo também prudente também observar a distribuição dos resultados dos folds da validação cruzada. Faremos isto comparando os modelos usando boxplots. Os 11 primeiros boxplots são referentes ao dataset original e os seguintes, ao dataset com tratamento de missings.

In [ ]:
# Comparação dos modelos
fig = plt.figure(figsize=(15,10)) 
fig.suptitle('Comparação dos Modelos - Dataset original e com tratamento de missings') 
ax = fig.add_subplot(111) 
plt.boxplot(results) 
ax.set_xticklabels(names) 
plt.show()

Os resultados mostram algumas diferenças comparando o dataset original e o dataset com tratamento de missings, parecendo, inicialmente, que o dataset com tratamento de missings gerou melhores resultados (especialmente no KNN: observe a distribuição dos valores). Já considerando a mediana da acurácia marcada como a linha laranja do boxplot, os modelos de regressão logística parecem ter alcançado os melhores resultados.

A seguir, repetiremos este processo usando uma visão padronizada e outra normalizada do conjunto de dados de treinamento.

5.2. Criação e avaliação de modelos: dados padronizados e normalizados¶

Como suspeitamos que as diferentes distribuições dos dados brutos possam impactar negativamente a habilidade de alguns modelos, vamos agora experimentar as visões do dataset padronizado e normalizado, comparando com a visão original do dataset, com e tratamento de missings. Na padronização (StandardScaler), os dados serão transformados de modo que cada atributo tenha média 0 e um desvio padrão 1; na normalização (MinMaxScaler), cada atributo é redimensionado para um novo intervalo entre 0 e 1.

Para evitar o vazamento de dados (data leakage) nestas transformações, vamos usar pipelines que padronizam os dados e constroem o modelo para cada fold de teste de validação cruzada. Dessa forma, podemos obter uma estimativa justa de como cada modelo com dados padronizados pode funcionar com dados não vistos.

OBS: Repare que neste notebook estamos usando um código mais "limpo" do que nos anteriores.

In [ ]:
np.random.seed(7) # definindo uma semente global

# Aqui iremos armazenar os pipelines e os resultados para todas as visões do dataset
pipelines = []
results = []
names = []


# Criando os elementos do pipeline

# Algoritmos que serão utilizados
reg_log = ('LR', LogisticRegression(max_iter=200))
knn = ('KNN', KNeighborsClassifier())
cart = ('CART', DecisionTreeClassifier())
naive_bayes = ('NB', GaussianNB())
svm = ('SVM', SVC())
bagging = ('Bag', BaggingClassifier(base_estimator=base, n_estimators=num_trees))
random_forest = ('RF', RandomForestClassifier(n_estimators=num_trees, max_features=max_features))
extra_trees = ('ET', ExtraTreesClassifier(n_estimators=num_trees, max_features=max_features))
adaboost = ('Ada', AdaBoostClassifier(n_estimators=num_trees))
gradient_boosting = ('GB', GradientBoostingClassifier(n_estimators=num_trees))
voting = ('Voting', VotingClassifier(bases))

# Transformações que serão utilizadas
standard_scaler = ('StandardScaler', StandardScaler())
min_max_scaler = ('MinMaxScaler', MinMaxScaler())


# Montando os pipelines

# Dataset original
pipelines.append(('LR-orig', Pipeline([reg_log]))) 
pipelines.append(('KNN-orig', Pipeline([knn])))
pipelines.append(('CART-orig', Pipeline([cart])))
pipelines.append(('NB-orig', Pipeline([naive_bayes])))
pipelines.append(('SVM-orig', Pipeline([svm])))
pipelines.append(('Bag-orig', Pipeline([bagging])))
pipelines.append(('RF-orig', Pipeline([random_forest])))
pipelines.append(('ET-orig', Pipeline([extra_trees])))
pipelines.append(('Ada-orig', Pipeline([adaboost])))
pipelines.append(('GB-orig', Pipeline([gradient_boosting])))
pipelines.append(('Vot-orig', Pipeline([voting])))

# Padronização do dataset original
pipelines.append(('LR-padr', Pipeline([standard_scaler, reg_log]))) 
pipelines.append(('KNN-padr', Pipeline([standard_scaler, knn])))
pipelines.append(('CART-padr', Pipeline([standard_scaler, cart])))
pipelines.append(('NB-padr', Pipeline([standard_scaler, naive_bayes])))
pipelines.append(('SVM-padr', Pipeline([standard_scaler, svm])))
pipelines.append(('Bag-padr', Pipeline([standard_scaler, bagging]))) 
pipelines.append(('RF-padr', Pipeline([standard_scaler, random_forest])))
pipelines.append(('ET-padr', Pipeline([standard_scaler, extra_trees])))
pipelines.append(('Ada-padr', Pipeline([standard_scaler, adaboost])))
pipelines.append(('GB-padr', Pipeline([standard_scaler, gradient_boosting])))
pipelines.append(('Vot-padr', Pipeline([standard_scaler, voting])))

# Normalização do dataset original
pipelines.append(('LR-norm', Pipeline([min_max_scaler, reg_log]))) 
pipelines.append(('KNN-norm', Pipeline([min_max_scaler, knn])))
pipelines.append(('CART-norm', Pipeline([min_max_scaler, cart])))
pipelines.append(('NB-norm', Pipeline([min_max_scaler, naive_bayes])))
pipelines.append(('SVM-norm', Pipeline([min_max_scaler, svm])))
pipelines.append(('Bag-norm', Pipeline([min_max_scaler, bagging]))) 
pipelines.append(('RF-norm', Pipeline([min_max_scaler, random_forest])))
pipelines.append(('ET-norm', Pipeline([min_max_scaler, extra_trees])))
pipelines.append(('Ada-norm', Pipeline([min_max_scaler, adaboost])))
pipelines.append(('GB-norm', Pipeline([min_max_scaler, gradient_boosting])))
pipelines.append(('Vot-norm', Pipeline([min_max_scaler, voting])))

# Executando os pipelines - datasets sem tratamento de missings
print("-- Datasets SEM tratamento de missings")
for name, model in pipelines:
    cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %.3f (%.3f)" % (name, cv_results.mean(), cv_results.std()) # formatando para 3 casas decimais
    print(msg)

# Executando os pipelines - datasets com tratamento de missings
print("-- Datasets COM tratamento de missings")
for name, model in pipelines:
    cv_results = cross_val_score(model, X_train_sm, y_train_sm, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %.3f (%.3f)" % (name, cv_results.mean(), cv_results.std()) # formatando para 3 casas decimais
    print(msg)
-- Datasets SEM tratamento de missings
LR-orig: 0.762 (0.057)
KNN-orig: 0.712 (0.059)
CART-orig: 0.702 (0.063)
NB-orig: 0.746 (0.051)
SVM-orig: 0.749 (0.041)
Bag-orig: 0.761 (0.047)
RF-orig: 0.772 (0.048)
ET-orig: 0.756 (0.066)
Ada-orig: 0.749 (0.053)
GB-orig: 0.751 (0.057)
Vot-orig: 0.763 (0.061)
LR-padr: 0.762 (0.057)
KNN-padr: 0.727 (0.069)
CART-padr: 0.699 (0.048)
NB-padr: 0.746 (0.051)
SVM-padr: 0.763 (0.053)
Bag-padr: 0.772 (0.045)
RF-padr: 0.751 (0.048)
ET-padr: 0.776 (0.061)
Ada-padr: 0.749 (0.053)
GB-padr: 0.748 (0.058)
Vot-padr: 0.774 (0.047)
LR-norm: 0.751 (0.045)
KNN-norm: 0.725 (0.083)
CART-norm: 0.697 (0.069)
NB-norm: 0.746 (0.051)
SVM-norm: 0.772 (0.054)
Bag-norm: 0.766 (0.050)
RF-norm: 0.776 (0.043)
ET-norm: 0.767 (0.059)
Ada-norm: 0.749 (0.053)
GB-norm: 0.749 (0.058)
Vot-norm: 0.774 (0.049)
-- Datasets COM tratamento de missings
LR-orig: 0.757 (0.046)
KNN-orig: 0.735 (0.051)
CART-orig: 0.692 (0.054)
NB-orig: 0.756 (0.057)
SVM-orig: 0.761 (0.041)
Bag-orig: 0.761 (0.036)
RF-orig: 0.775 (0.027)
ET-orig: 0.767 (0.045)
Ada-orig: 0.740 (0.054)
GB-orig: 0.764 (0.054)
Vot-orig: 0.757 (0.045)
LR-padr: 0.757 (0.046)
KNN-padr: 0.743 (0.044)
CART-padr: 0.686 (0.047)
NB-padr: 0.756 (0.057)
SVM-padr: 0.748 (0.052)
Bag-padr: 0.748 (0.038)
RF-padr: 0.769 (0.036)
ET-padr: 0.761 (0.048)
Ada-padr: 0.740 (0.054)
GB-padr: 0.764 (0.053)
Vot-padr: 0.758 (0.047)
LR-norm: 0.767 (0.046)
KNN-norm: 0.738 (0.047)
CART-norm: 0.697 (0.056)
NB-norm: 0.756 (0.057)
SVM-norm: 0.759 (0.055)
Bag-norm: 0.759 (0.047)
RF-norm: 0.764 (0.039)
ET-norm: 0.762 (0.041)
Ada-norm: 0.740 (0.054)
GB-norm: 0.764 (0.054)
Vot-norm: 0.762 (0.061)

Vamos analisar estes resultados graficamente:

OBS: você pode preferir fazer um experimento com menos variações para comparar melhor os resultados graficamente, ou mesmo incluir neste gráfico algumas linhas verticais para separar as diferentes visões do dataset.

In [ ]:
# Comparação dos modelos
fig = plt.figure(figsize=(25,6))
fig.suptitle('Comparação dos modelos - Dataset orginal, padronizado e normalizado, com e sem tratamento de missings') 
ax = fig.add_subplot(111) 
plt.boxplot(results) 
ax.set_xticklabels(names, rotation=90)
plt.show()

Neste primeiro experimento, rodamos 66 configurações: 11 diferentes algoritmos e 6 diferentes visões do nosso dataset!

Para o dataset Sem tratamento de missings, os melhores modelos em termos de acurácia foram: ET-orig (0,779), Bag-orig (0,77), LR-padr (0,77), GB-orig (0,769) e Bag-padr (0,767). Já para o dataset Com tratamento de missings, os melhores modelos foram: Vot-orig (0,774), ET-norm (0,77), SVM-norm (0,766), LR-padr (0,764) e RF-norm (0,764).

Vamos agora fazer um novo experimento, fazendo o ajuste do SVM e do KNN, variando os seus hiperparâmetros a fim de buscar configurações que possam gerar resultados melhores.

OBS: Você poderia se aprofundar em outros algoritmos também.

5.3. Ajuste dos Modelos (pipeline + gridsearch)¶

Ajuste do KNN¶

Vamos começar ajustando parâmetros como o número de vizinhos e as métricas de distância para o KNN. Para tal, tentaremos todos os valores ímpares de k entre 1 a 21 e as métricas de distância euclidiana, manhattan e minkowski. Usando o pipeline, cada valor de k e de distância será avaliado usando a validação cruzada 10-fold no conjunto de dados sem tratamento de missings e com as visões padronizada e normalizada, que mostrou melhores resultados do que os dados originais.

In [ ]:
# Tuning do KNN

# Baseado em https://scikit-learn.org/stable/tutorial/statistical_inference/putting_together.html

np.random.seed(7) # definindo uma semente global

pipelines = []

# definindo os componentes do pipeline
knn = ('KNN', KNeighborsClassifier())
standard_scaler = ('StandardScaler', StandardScaler())
min_max_scaler = ('MinMaxScaler', MinMaxScaler())

pipelines.append(('knn-orig', Pipeline(steps=[knn]))) # OBS: "steps=" é opcional
pipelines.append(('knn-padr', Pipeline(steps=[standard_scaler, knn])))
pipelines.append(('knn-norm', Pipeline(steps=[min_max_scaler, knn])))

# Parameters of pipelines can be set using ‘__’ separated parameter names:
param_grid = {
    'KNN__n_neighbors': [1,3,5,7,9,11,13,15,17,19,21],
    'KNN__metric': ["euclidean", "manhattan", "minkowski"],
}

# Dataset sem tratamento de missings
for name, model in pipelines:    
    # prepara e executa o GridSearchCV
    grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
    grid.fit(X_train, y_train)

    # imprime a melhor configuração
    print("Sem tratamento de missings: %s - Melhor: %f usando %s" % (name, grid.best_score_, grid.best_params_)) 

    # imprime todas as configurações
    #means = grid.cv_results_['mean_test_score']
    #stds = grid.cv_results_['std_test_score']
    #params = grid.cv_results_['params']
    #for mean, stdev, param in zip(means, stds, params):
        #print("%f (%f): %r" % (mean, stdev, param))

# Dataset com tratamento de missings
for name, model in pipelines:    
    # prepara e executa o GridSearchCV
    grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
    grid.fit(X_train_sm, y_train_sm)

    # imprime a melhor configuração
    print("Com tratamento de missings: %s - Melhor: %f usando %s" % (name, grid.best_score_, grid.best_params_)) 
Sem tratamento de missings: knn-orig - Melhor: 0.757615 usando {'KNN__metric': 'manhattan', 'KNN__n_neighbors': 17}
Sem tratamento de missings: knn-padr - Melhor: 0.757509 usando {'KNN__metric': 'manhattan', 'KNN__n_neighbors': 19}
Sem tratamento de missings: knn-norm - Melhor: 0.759228 usando {'KNN__metric': 'euclidean', 'KNN__n_neighbors': 13}
Com tratamento de missings: knn-orig - Melhor: 0.764014 usando {'KNN__metric': 'manhattan', 'KNN__n_neighbors': 21}
Com tratamento de missings: knn-padr - Melhor: 0.763934 usando {'KNN__metric': 'euclidean', 'KNN__n_neighbors': 21}
Com tratamento de missings: knn-norm - Melhor: 0.773770 usando {'KNN__metric': 'manhattan', 'KNN__n_neighbors': 15}

Os resultados mostram que a melhor configuração encontrada utiliza o dataset com tratamento de missings, com dados padronizados, distância de manhattan e k = 15.

Ajuste do SVM¶

Iremos ajustar dois dos principais hiperparâmetros do algoritmo SVM: o valor de C (o quanto flexibilizar a margem) e o tipo de kernel utilizado. No Scikit-Learn, o padrão para o algoritmo SVM (implementado pela classe SVC) é usar o kernel da Função Base Radial (RBF) e o valor C definido como 1.0.

Iremos testar outros valores para estes hiperparâmetros, e cada combinação de valores será avaliada usando a função GridSearchCV, como fizemos anteriormente para o KNN.

In [ ]:
# Tuning do SVM - DEMORA MUITO ESTE BLOCO DE CÓDIGO

# Baseado em https://scikit-learn.org/stable/tutorial/statistical_inference/putting_together.html

np.random.seed(7) # definindo uma semente global

pipelines = []

# definindo os componentes do pipeline
svm = ('SVM', SVC())
standard_scaler = ('StandardScaler', StandardScaler())
min_max_scaler = ('MinMaxScaler', MinMaxScaler())

pipelines.append(('svm-orig', Pipeline(steps=[svm]))) # OBS: "steps=" é opcional
pipelines.append(('svm-padr', Pipeline(steps=[standard_scaler, svm])))
pipelines.append(('svm-norm', Pipeline(steps=[min_max_scaler, svm])))

# Parameters of pipelines can be set using ‘__’ separated parameter names:
param_grid = {
    'SVM__C': [0.1, 0.3, 0.5, 0.7, 0.9, 1.0, 1.3, 1.5, 1.7, 2.0],
    'SVM__kernel': ['linear', 'poly', 'rbf', 'sigmoid'],
}

# Dataset sem tratamento de missings
for name, model in pipelines:    
    # prepara e executa o GridSearchCV
    grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
    grid.fit(X_train, y_train)

    # imprime a melhor configuração
    print("Sem tratamento de missings: %s - Melhor: %f usando %s" % (name, grid.best_score_, grid.best_params_)) 

    # imprime todas as configurações
    #means = grid.cv_results_['mean_test_score']
    #stds = grid.cv_results_['std_test_score']
    #params = grid.cv_results_['params']
    #for mean, stdev, param in zip(means, stds, params):
        #print("%f (%f): %r" % (mean, stdev, param))

# Dataset com tratamento de missings
for name, model in pipelines:    
    # prepara e executa o GridSearchCV
    grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
    grid.fit(X_train_sm, y_train_sm)

    # imprime a melhor configuração
    print("Com tratamento de missings: %s - Melhor: %f usando %s" % (name, grid.best_score_, grid.best_params_)) 
Sem tratamento de missings: svm-orig - Melhor: 0.765680 usando {'SVM__C': 1.3, 'SVM__kernel': 'linear'}
Sem tratamento de missings: svm-padr - Melhor: 0.768932 usando {'SVM__C': 0.1, 'SVM__kernel': 'linear'}
Sem tratamento de missings: svm-norm - Melhor: 0.775568 usando {'SVM__C': 1.5, 'SVM__kernel': 'rbf'}
Com tratamento de missings: svm-orig - Melhor: 0.768958 usando {'SVM__C': 1.5, 'SVM__kernel': 'rbf'}
Com tratamento de missings: svm-padr - Melhor: 0.765653 usando {'SVM__C': 0.1, 'SVM__kernel': 'sigmoid'}
Com tratamento de missings: svm-norm - Melhor: 0.770571 usando {'SVM__C': 0.1, 'SVM__kernel': 'poly'}

Podemos ver que mesmo a configuração do SVM que alcançou a maior acurácia não supera a acurácia mais alta que conseguimos até o momento, com ensembles.

Exercício: Experimente variar os hiperparâmetros de outros algoritmos para verificar se é possível encontrar uma configuração de modelo que supere os melhores resultados até o momento.

6. Finalização do Modelo¶

Analisando os resultados até aqui, verificamos que o modelo que mostrou melhor acurácia média para o problema foi o que usou Extra Trees como algoritmo (apesar de ter um desvio padrão relativamente alto). Relembrando o Experimento 1 (uma vez que o Experimento 2 não trouxe resultados melhores), nossos resultados foram:

Para o dataset Sem tratamento de missings, os melhores modelos em termos de acurácia foram: ET-orig (0,779), Bag-orig (0,77), LR-padr (0,77), GB-orig (0,769) e Bag-padr (0,767). Já para o dataset Com tratamento de missings, os melhores modelos foram: Vot-orig (0,774), ET-norm (0,77), SVM-norm (0,766), LR-padr (0,764) e RF-norm (0,764).

Examinando também o desvio padrão, poderíamos, por exemplo, optar por utilizar o modelo construído com o algoritmo de Regressão Logística, com os dados sem tratamento de missings, visão padronizada. Considerando o dataset "Sem tratamento de missings", este modelo ficou na 2a posição em termos de acurácia média, mas com um desvio padrão menor do que o que alcançou a 1a posição. Além disso, explicar como funciona este modelo para os usuários não técnicos tende a ser mais simples.

A seguir, finalizaremos este modelo, treinando-o em todo o conjunto de dados de treinamento (sem validação cruzada) e faremos predições para o conjunto de dados de teste que foi separado logo no início do exemplo, a fim de confirmarmos nossas descobertas.

Primeiro, iremos realizar a padronização dos dados de entrada. Depois, treinaremos o modelo e exibiremos a acurácia de teste, a matriz de confusão e o relatório de classificação.

In [ ]:
# Preparação do modelo
scaler = StandardScaler().fit(X_train) # ajuste do scaler com o conjunto de treino
rescaledX = scaler.transform(X_train) # aplicação da padronização no conjunto de treino
model = LogisticRegression(max_iter=200) # substitua aqui se quiser usar outro modelo
model.fit(rescaledX, y_train)

# Estimativa da acurácia no conjunto de teste
rescaledTestX = scaler.transform(X_test) # aplicação da padronização no conjunto de teste
predictions = model.predict(rescaledTestX)
print(accuracy_score(y_test, predictions))
print(confusion_matrix(y_test, predictions))
print(classification_report(y_test, predictions))
0.7727272727272727
[[88 12]
 [23 31]]
              precision    recall  f1-score   support

         0.0       0.79      0.88      0.83       100
         1.0       0.72      0.57      0.64        54

    accuracy                           0.77       154
   macro avg       0.76      0.73      0.74       154
weighted avg       0.77      0.77      0.77       154

Por meio do conjunto de teste, verificamos que alcançamos uma acurácia de 77,22% em dados não vistos. Este resultado foi ainda melhor do que a nossa avaliação anterior da regressão logística. Valores semelhantes são esperados quando este modelo estiver executando em produção e fazendo predições para novos dados.

Vamos agora preparar o modelo para utilização. Para isso, vamos treiná-lo com todo o dataset, e não apenas o conjunto de treino.

In [ ]:
# Preparação do modelo com TODO o dataset (e não apenas a base de treino)
scaler = StandardScaler().fit(X) # ajuste do scaler com TODO o dataset
rescaledX = scaler.transform(X) # aplicação da padronização com TODO o dataset
model.fit(rescaledX, y)
Out[ ]:
LogisticRegression(max_iter=200)

7. Aplicando o modelo em dados não vistos¶

Agora imagine que chegaram 3 novas instâncias, mas não sabemos a classe de saída. Podemos então aplicar nosso modelo recém-treinado para estimar as classes! Para tal, será necessário antes padronizar os dados (usando a mesma escala dos dados usados treinamento do modelo!).

In [ ]:
# Novos dados - não sabemos a quality!
data = {'acidezfixa':  [1, 9, 5],
        'acidezvolátil': [90, 100, 110],
        'ácidocítrico': [50, 60, 50],
        'acucarresidual': [30, 30, 30],
        'cloretos': [100, 100, 100],
        'dióxidoenxofrelivre': [20.0, 30.0, 40.0],
        'densidade': [20.0, 30.0, 40.0],
        'pH': [15, 40, 40],
        'sulfatos': [1.0, 2.0, 1.0],
        'alcool': [20.0, 30.0, 40.0],
        }

atributosred = ['acidezfixa', 'acidezvolátil', 'ácidocítrico', 'acucarresidual', 'cloretos', 'dióxidoenxofrelivre', 'dióxidoenxofretotal', 'densidade', 'pH', 'sulfatos', 'alcool']
entrada = pd.DataFrame(data, columns=atributos)

array_entrada = entrada.values
X_entrada = array_entrada[:,0:8].astype(float)
print(X_entrada)
[[  1.  90.  50.  30. 100.  20.   1.  15.]
 [  9. 100.  60.  30. 100.  30.   2.  40.]
 [  5. 110.  50.  30. 100.  40.   1.  40.]]
In [ ]:
# Padronização nos dados de entrada usando o scaler utilizado em X
rescaledEntradaX = scaler.transform(X_entrada)
print(rescaledEntradaX)
[[-0.84488505 -0.96691063 -0.98770975  0.59362962  0.17539902 -1.52208897
   1.59499624 -1.55207596]
 [ 1.53084665 -0.65393918 -0.47073225  0.59362962  0.17539902 -0.25289651
   4.61511492  0.57511787]
 [ 0.3429808  -0.34096773 -0.98770975  0.59362962  0.17539902  1.01629594
   1.59499624  0.57511787]]
In [ ]:
# Estimativa de quality dos dados de entrada
saidas = model.predict(rescaledEntradaX)
print(saidas)
[0. 1. 1.]

Links interessantes¶

  • https://pandas.pydata.org/pandas-docs/stable/user_guide/10min.html
  • https://pandas.pydata.org/pandas-docs/stable/user_guide/cookbook.html#cookbook
  • https://github.com/donnemartin/data-science-ipython-notebooks#pandas
  • http://datacamp-community-prod.s3.amazonaws.com/dbed353d-2757-4617-8206-8767ab379ab3
  • https://github.com/donnemartin/data-science-ipython-notebooks#pandas - Coletânea de notebooks Jupyter que abordam profundamente várias ferramentas e casos de uso do Pandas
  • https://github.com/guipsamora/pandas_exercises - Exercícios de Pandas com soluções, separados por temas
In [ ]: