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
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
# 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
# configuração para não exibir os warnings
import warnings
warnings.filterwarnings("ignore")
# 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=';')
vinhoreddados.head()
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 |
# 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=';')
vinhowhitedados.head()
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 |
https://colab.research.google.com/drive/1MCCMFdsGaXIQjdAYpFrqTyw0bvtXhuYb#scrollTo=8w7HuVO9-XRo
PERGUNTAS SOBRE A AMOSTRA:
Exame as dimensões do conjunto de dados, suas informações e alguns exemplos de
# exibindo as dimensões do dataset
vinhoreddados.shape
(1599, 12)
# exibindo as dimensões do dataset de vinho Branco
vinhowhitedados.shape
(4898, 12)
# 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
# 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
# Mostra as 10 primeiras linhas do dataset. VINHO TINTO
vinhoreddados.head(10)
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 |
# Mostra as 10 primeiras linhas do dataset. VINHO BRANCO
vinhowhitedados.head(10)
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 |
# Mostra as 10 últimas linhas do dataset
vinhoreddados.tail(10)
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 |
# Mostra as 10 últimas linhas do dataset
vinhoreddados.tail(10)
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 |
# exibindo as últimas linhas
vinhowhitedados.tail(10)
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 |
# Verificando os tipos de cada coluna
vinhoreddados.dtypes
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
# Verificando os tipos de cada coluna
vinhowhitedados.dtypes
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
# Faz um resumo estatístico do dataset (média, desvio padrão, mínimo, máximo e os quartis)
vinhoreddados.describe()
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 |
# Faz um resumo estatístico do dataset (média, desvio padrão, mínimo, máximo e os quartis)
vinhowhitedados.describe()
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 |
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.
# 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
# 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
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
import matplotlib
import matplotlib.pyplot as plt
# Histograma
vinhoreddados.hist(figsize = (15,10))
plt.show()
# 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.
import seaborn as sns
# Density Plot
vinhoreddados.plot(kind = 'density', subplots = True, layout = (4,4), sharex = False, figsize = (15,10))
plt.show()
# Density Plot
vinhowhitedados.plot(kind = 'density', subplots = True, layout = (4,4), sharex = False, figsize = (15,10))
plt.show()
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.
# Boxplot
vinhoreddados.plot(kind = 'box', subplots = True, layout = (4,4), sharex = False, sharey = False, figsize = (15,10))
plt.show()
# Boxplot
vinhowhitedados.plot(kind = 'box', subplots = True, layout = (4,4), sharex = False, sharey = False, figsize = (15,10))
plt.show()
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:
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.
# Matriz de Correlação com Matplotlib Seaborn. VINHO TINTO
sns.heatmap(vinhoreddados.corr(), annot=True, cmap='RdBu');
# 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.
# Scatter Plot com Seaborn - Variação 1
sns.pairplot(vinhoreddados)
<seaborn.axisgrid.PairGrid at 0x7f13ead5a820>
# Scatter Plot com Seaborn - Variação 1
sns.pairplot(vinhowhitedados)
<seaborn.axisgrid.PairGrid at 0x7f13e56b2520>
OUTROS
# selecionando uma coluna específica
vinhoreddados['fixed acidity']
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
# selecionando um subconjunto de linhas consecutivas
vinhoreddados[7:11]
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 |
# selecionando um subconjunto de colunas para todas as linhas
vinhoreddados.loc[:, ['fixed acidity', 'volatile acidity', 'citric acid']]
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
# selecionando um subconjunto linhas e colunas
vinhoreddados.loc[7:11, ['fixed acidity', 'volatile acidity', 'citric acid']]
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 |
# selecionando linhas segundo um critério
vinhoreddados[vinhoreddados['fixed acidity'] > 3.5]
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
# 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()
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 |
# eliminando linhas que tenham ALGUM valor missing
df = df.dropna(how='any')
df.tail()
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 |
# eliminando linhas que tenham TODOS os valores missing
df = df.dropna(how='all')
df.tail()
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 |
# 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()
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 |
# exibindo a média de cada atributo, agrupado por espécie
vinhoreddados.groupby('quality').mean()
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 |
# 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
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 |
# ... e exibir sua soma...
df.groupby(['A', 'B']).sum()
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 |
# ... ou sua média/mediana
df.groupby(['A', 'B']).mean()
# df.groupby(['A', 'B']).median()
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 |
# adicionando uma nova coluna ao dataframe (não tenho ideia se essa métrica faz sentido!)
vinhoreddados['razao'] = (vinhoreddados['quality'] * vinhoreddados['quality'])
# exibindo o conteúdo da nova coluna
vinhoreddados['razao'].head()
0 25 1 25 2 25 3 36 4 25 Name: razao, dtype: int64
vinhoreddados
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
# 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'
# 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)
# exibindo o conteúdo do dataframe com as novas colunas
vinhoreddados.head()
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 |
# exibindo a distribuicao da nova coluna
pd.value_counts(vinhoreddados['cat_razao'])
Média 1372 Grande 217 Pequena 10 Name: cat_razao, dtype: int64
# 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)
# exibindo o conteúdo do dataframe com a exclusão das colunas
vinhoreddados.head()
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 |
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.
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.
# verificando nulls no dataset
vinhoreddados.isnull().sum()
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
# 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)
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca4675b80>
# 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)
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca1d9af40>
# 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)
<matplotlib.axes._subplots.AxesSubplot at 0x7f0ca18abb20>
# 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()
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 |
Conjunto de dados original:
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.
vinhoreddados.head()
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 |
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)
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]
# 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]
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.
# 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).
# 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:
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.
# Aqui iremos armazenar os resultados tanto para o dataset original quanto para o dataset sem missings
results = []
names = []
results= [] names= []
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
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.
# 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.
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.
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.
# 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.
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.
# 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.
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.
# 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.
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.
# 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.
# 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)
LogisticRegression(max_iter=200)
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!).
# 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.]]
# 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]]
# Estimativa de quality dos dados de entrada
saidas = model.predict(rescaledEntradaX)
print(saidas)
[0. 1. 1.]