Código
setwd("\\Users\\paulo\\OneDrive\\Área de Trabalho\\ESTATÍSTICA\\UFPB\\8º PERÍODO\\ANÁLISE MULTIVARIADA II\\PROVA")
<- read.csv2("heart.csv", header = T, sep = ",") banco
Aplicar validação cruzada e treinar alguns modelos e verificar qual o melhor modelo de acordo com algumas métricas de erro.
Utilizando tidymodels
para treinar os modelos e junto com isso utilizando workflows
.
O pacote tidymodels é um meta-pacote que consiste de algumas bibliotecas, tais como:
rsample:
funções para particionamento e reamostragem eficiente de dados;
parsnip:
interface unificada para um amplo conjunto de modelos que podem ser testados sem que o usuário se preocupe com diferenças de sintaxe;
recipes:
pré-processamento e feature engineering;
workflows:
junta pré-processamento, modelagem (treinamento) e pós-processamento; tune: otimização de hiperparâmetros;
yardstick:
funções para avaliar a efetividade de modelos através de medidas de performance; broom: converte a informação contida em objetos comuns de R para o formato tidy;
dials:
cria e gerencia hiperparâmetros de ajuste e grids de hiperparâmetros.
Outras bibliotecas serão também utilizadas no processo, como a finetune, que permite um processo de otimização de hiperparâmetros mais eficiente.
O conjunto de dados pode ser encontrado em: conjunto de dados
Mais sobre o banco de dados
As doenças cardiovasculares (DCVs) são a causa número 1 de morte no mundo, levando cerca de 17,9 milhões de vidas a cada ano, o que representa 31% de todas as mortes em todo o mundo. Quatro em cada 5 mortes por DCV são devidas a ataques cardíacos e derrames, e um terço dessas mortes ocorre prematuramente em pessoas com menos de 70 anos de idade. A insuficiência cardíaca é um evento comum causado por DCVs e este conjunto de dados contém 11 recursos que podem ser usados para prever uma possível doença cardíaca.
Pessoas com doenças cardiovasculares ou com alto risco cardiovascular (devido à presença de um ou mais fatores de risco, como hipertensão, diabetes, hiperlipidemia ou doença já estabelecida) precisam de detecção e gerenciamento precoces, em que um modelo de aprendizado de máquina pode ser de grande ajuda.
Sobre as variáveis dependentes:
Idade:
idade do paciente em anos
Sexo:
sexo do paciente [M: Masculino, F: Feminino]
ChestPainType:
tipo de dor no peito [TA: Angina Típica, ATA: Angina Atípica, NAP: Dor Não Anginosa, ASY: Assintomática]
RestingBP:
pressão arterial em repouso [mm Hg]
Colesterol
: colesterol sérico [mm/dl]
JejumBS:
açúcar no sangue em jejum [1: se JejumBS > 120 mg/dl, 0: caso contrário]
ECG em repouso:
resultados do eletrocardiograma em repouso [Normal: Normal, ST: com anormalidade da onda ST-T (inversões da onda T e/ou elevação ou depressão do ST > 0,05 mV), HVE: mostrando hipertrofia ventricular esquerda provável ou definitiva pelos critérios de Estes]
MaxHR:
frequência cardíaca máxima alcançada [Valor numérico entre 60 e 202]
ExerciseAngina:
angina induzida por exercício [S: Sim, N: Não]
Oldpeak:
oldpeak = ST [Valor numérico medido na depressão]
ST_Slope:
a inclinação do segmento ST do exercício de pico [Up: ascendente, Flat: plano, Down: descendente]
Sobre a variável resposta:
HeartDisease:
classe de saída [1: doença cardíaca, 0: normal]
setwd("\\Users\\paulo\\OneDrive\\Área de Trabalho\\ESTATÍSTICA\\UFPB\\8º PERÍODO\\ANÁLISE MULTIVARIADA II\\PROVA")
<- read.csv2("heart.csv", header = T, sep = ",") banco
library(tidyverse) # Framework do tidyverse
library(tidymodels) # Framework de modelagem
library(skimr) # Estatística descritiva rápida
library(DataExplorer) # Exploração do conjunto de dados
library(corrplot) # Gráfico de correlação
library(GGally) # Gráficos adicionais com estrutura ggplot2
library(stringr) # Para lidar com strings
library(glmnet) # LASSO, Ridge e Rede Elástica
library(MASS) # Discriminante Linear (LDA) e Quadrático (RL)
library(recipes) # Pré-processamento dos dados
library(class) #knn
library(themis) # Balanceamento de dados
library(discrim) # lda, qda
library(kknn) # (Kernel) K-NN
library(finetune) # Otimização fina de hiperparâmetros
library(gt) # Para tabelas de maneira melhor visualmente
library(dplyr) # Para tratamento dos dados
library(plotly) # Para gráficos de melhor qualidade
library(stringr) # Para tratamentos com strings de maneira melhor
glimpse(banco)
Rows: 918
Columns: 12
$ Age <int> 40, 49, 37, 48, 54, 39, 45, 54, 37, 48, 37, 58, 39, 49,…
$ Sex <chr> "M", "F", "M", "F", "M", "M", "F", "M", "M", "F", "F", …
$ ChestPainType <chr> "ATA", "NAP", "ATA", "ASY", "NAP", "NAP", "ATA", "ATA",…
$ RestingBP <int> 140, 160, 130, 138, 150, 120, 130, 110, 140, 120, 130, …
$ Cholesterol <int> 289, 180, 283, 214, 195, 339, 237, 208, 207, 284, 211, …
$ FastingBS <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ RestingECG <chr> "Normal", "Normal", "ST", "Normal", "Normal", "Normal",…
$ MaxHR <int> 172, 156, 98, 108, 122, 170, 170, 142, 130, 120, 142, 9…
$ ExerciseAngina <chr> "N", "N", "N", "Y", "N", "N", "N", "N", "Y", "N", "N", …
$ Oldpeak <chr> "0", "1", "0", "1.5", "0", "0", "0", "0", "1.5", "0", "…
$ ST_Slope <chr> "Up", "Flat", "Up", "Flat", "Up", "Up", "Up", "Up", "Fl…
$ HeartDisease <int> 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1…
$Sex <- factor(banco$Sex, levels = c("M","F"), labels = c("Masculino", "Feminino"))
banco$ChestPainType <- factor(banco$ChestPainType, levels = c("TA", "ATA", "NAP", "ASY"), labels = c("Angina Típica", "Angina Atípica", "Dor Não Anginosa", "Assintomática"))
banco$FastingBS <- factor(banco$FastingBS, levels = c(0,1),labels = c("C.C", "JejumBS > 120 mg/dl"))
banco$RestingECG <- factor(banco$RestingECG, levels = c("Normal", "ST", "LVH"), labels = c("Normal", "anormalidade da onda", "hipertrofia ventricular"))
banco$ExerciseAngina <- factor(banco$ExerciseAngina, levels = c("N", "Y"), labels = c("Não", "Sim"))
banco$ST_Slope <- factor(banco$ST_Slope, levels = c("Up", "Flat", "Down"), labels = c("Ascendente", "Plano", "Descendente"))
banco$HeartDisease <- factor(banco$HeartDisease, levels = c(0,1),labels = c("Normal", "Doença cardiaca"))
banco$Oldpeak <- as.numeric(banco$Oldpeak) banco
glimpse(banco)
Rows: 918
Columns: 12
$ Age <int> 40, 49, 37, 48, 54, 39, 45, 54, 37, 48, 37, 58, 39, 49,…
$ Sex <fct> Masculino, Feminino, Masculino, Feminino, Masculino, Ma…
$ ChestPainType <fct> Angina Atípica, Dor Não Anginosa, Angina Atípica, Assin…
$ RestingBP <int> 140, 160, 130, 138, 150, 120, 130, 110, 140, 120, 130, …
$ Cholesterol <int> 289, 180, 283, 214, 195, 339, 237, 208, 207, 284, 211, …
$ FastingBS <fct> C.C, C.C, C.C, C.C, C.C, C.C, C.C, C.C, C.C, C.C, C.C, …
$ RestingECG <fct> Normal, Normal, anormalidade da onda, Normal, Normal, N…
$ MaxHR <int> 172, 156, 98, 108, 122, 170, 170, 142, 130, 120, 142, 9…
$ ExerciseAngina <fct> Não, Não, Não, Sim, Não, Não, Não, Não, Sim, Não, Não, …
$ Oldpeak <dbl> 0.0, 1.0, 0.0, 1.5, 0.0, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, …
$ ST_Slope <fct> Ascendente, Plano, Ascendente, Plano, Ascendente, Ascen…
$ HeartDisease <fct> Normal, Doença cardiaca, Normal, Doença cardiaca, Norma…
::vis_miss(banco) visdat
Podemos observar que não há informação ausente no banco de dados
skim
do pacote skimr
skim(banco)
Name | banco |
Number of rows | 918 |
Number of columns | 12 |
_______________________ | |
Column type frequency: | |
factor | 7 |
numeric | 5 |
________________________ | |
Group variables | None |
Variable type: factor
skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
---|---|---|---|---|---|
Sex | 0 | 1 | FALSE | 2 | Mas: 725, Fem: 193 |
ChestPainType | 0 | 1 | FALSE | 4 | Ass: 496, Dor: 203, Ang: 173, Ang: 46 |
FastingBS | 0 | 1 | FALSE | 2 | C.C: 704, Jej: 214 |
RestingECG | 0 | 1 | FALSE | 3 | Nor: 552, hip: 188, ano: 178 |
ExerciseAngina | 0 | 1 | FALSE | 2 | Não: 547, Sim: 371 |
ST_Slope | 0 | 1 | FALSE | 3 | Pla: 460, Asc: 395, Des: 63 |
HeartDisease | 0 | 1 | FALSE | 2 | Doe: 508, Nor: 410 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
Age | 0 | 1 | 53.51 | 9.43 | 28.0 | 47.00 | 54.0 | 60.0 | 77.0 | ▁▅▇▆▁ |
RestingBP | 0 | 1 | 132.40 | 18.51 | 0.0 | 120.00 | 130.0 | 140.0 | 200.0 | ▁▁▃▇▁ |
Cholesterol | 0 | 1 | 198.80 | 109.38 | 0.0 | 173.25 | 223.0 | 267.0 | 603.0 | ▃▇▇▁▁ |
MaxHR | 0 | 1 | 136.81 | 25.46 | 60.0 | 120.00 | 138.0 | 156.0 | 202.0 | ▁▃▇▆▂ |
Oldpeak | 0 | 1 | 0.89 | 1.07 | -2.6 | 0.00 | 0.6 | 1.5 | 6.2 | ▁▇▆▁▁ |
As medidas de posição e dispersão que serão utilizadas serão:
Média
Mediana
1º Quartil
3º Quartil
Mínimo
Máximo
Desvio Padrão
Coeficiente de Variação
%>%
banco group_by(HeartDisease) %>%
summarise(media = mean(Age, na.rm = T),
mediana = median(Age, na.rm = T),
quartil_1 = quantile(Age, 0.25, na.rm = T),
quartil_3 = quantile(Age, 0.75, na.rm = T),
minimo = min(Age, na.rm = T),
maximo = max(Age, na.rm = T),
desvio = sd(Age, na.rm = T),
coeficiente = round(sd(Age, na.rm = T)/mean(Age, na.rm = T)*100,2)) %>%
::kable(col.names = c("Grupo", "Média", "Mediana", "1º Quartil", "3º Quartil", "Mínimo", "Máximo", "Desvio Padrão", "Coeficiente de Variação (%)"), caption = "Estatística Descritiva da Idade de acordo com a presença ou ausência de doença cardíaca") knitr
Grupo | Média | Mediana | 1º Quartil | 3º Quartil | Mínimo | Máximo | Desvio Padrão | Coeficiente de Variação (%) |
---|---|---|---|---|---|---|---|---|
Normal | 50.55122 | 51 | 43 | 57 | 28 | 76 | 9.444915 | 18.68 |
Doença cardiaca | 55.89961 | 57 | 51 | 62 | 31 | 77 | 8.727056 | 15.61 |
plot_ly(banco, x = banco$Age, color = banco$HeartDisease, type = "box") %>%
layout(title = "Boxplot da Idade de acordo com a presença ou ausência de doença cardíaca")
No boxplot acima, pacientes que apresentaram doenças cardíacas possuem uma variabilidade menor de idade quando comparado com pacientes que não apresentaram. Além disso nota-se a presença de 4 outliers referentes a pacientes que apresentaram doenças cardiovasculares antes dos 35 anos.
Também podemos observar 75% dos pacientes que não apresentaram doenças cardiovasculares são mais novos que a idade mediana dos pacientes que apresentaram doenças cardiovasculares. Isto é, pode-se levantar a hipótese de que a idade talvez tenha uma contribuição significativa para o desenvolvimento de doenças cardíacas.
%>%
banco group_by(HeartDisease) %>%
summarise(media = mean(MaxHR, na.rm = T),
mediana = median(MaxHR, na.rm = T),
quartil_1 = quantile(MaxHR, 0.25, na.rm = T),
quartil_3 = quantile(MaxHR, 0.75, na.rm = T),
minimo = min(MaxHR, na.rm = T),
maximo = max(MaxHR, na.rm = T),
desvio = sd(MaxHR, na.rm = T),
coeficiente = round(sd(MaxHR, na.rm = T)/mean(MaxHR, na.rm = T)*100,2)) %>%
::kable(col.names = c("Grupo", "Média", "Mediana", "1º Quartil", "3º Quartil", "Mínimo", "Máximo", "Desvio Padrão", "Coeficiente de Variação (%)"), caption = "Estatística Descritiva da Frequência Máxima Cardíaca de acordo com a presença ou ausência de doença cardíaca") knitr
Grupo | Média | Mediana | 1º Quartil | 3º Quartil | Mínimo | Máximo | Desvio Padrão | Coeficiente de Variação (%) |
---|---|---|---|---|---|---|---|---|
Normal | 148.1512 | 150 | 134 | 165.00 | 69 | 202 | 23.28807 | 15.72 |
Doença cardiaca | 127.6555 | 126 | 112 | 144.25 | 60 | 195 | 23.38692 | 18.32 |
plot_ly(banco, x = banco$MaxHR, color = banco$HeartDisease, type = "box") %>%
layout(title = "Boxplot da Frequência Máxima Cardíaca de acordo com a presença ou ausência de doença cardíaca")
Como o coração de pessoas com doenças cardíacas não estão funcionando de maneira adequada, quando se é necessária uma carga maior de trabalho o coração desse indivíduo não consegue trabalhar de forma tão eficiente quanto o coração de uma pessoa saudável. Por conta disso, 75% dos indíviduos doentes possuem a frequência máxima cardíaca abaixo da mediana da frequência máxima cardíaca do grupo de pessoas saudáveis.
%>%
banco group_by(HeartDisease) %>%
summarise(media = mean(RestingBP, na.rm = T),
mediana = median(RestingBP, na.rm = T),
quartil_1 = quantile(RestingBP, 0.25, na.rm = T),
quartil_3 = quantile(RestingBP, 0.75, na.rm = T),
minimo = min(RestingBP, na.rm = T),
maximo = max(RestingBP, na.rm = T),
desvio = sd(RestingBP, na.rm = T),
coeficiente = round(sd(RestingBP, na.rm = T)/mean(RestingBP, na.rm = T)*100,2)) %>%
::kable(col.names = c("Grupo", "Média", "Mediana", "1º Quartil", "3º Quartil", "Mínimo", "Máximo", "Desvio Padrão", "Coeficiente de Variação (%)"), caption = "Estatística Descritiva da Pressão Arterial em Repouso de acordo com a presença ou ausência de doença cardíaca") knitr
Grupo | Média | Mediana | 1º Quartil | 3º Quartil | Mínimo | Máximo | Desvio Padrão | Coeficiente de Variação (%) |
---|---|---|---|---|---|---|---|---|
Normal | 130.1805 | 130 | 120 | 140 | 80 | 190 | 16.49958 | 12.67 |
Doença cardiaca | 134.1850 | 132 | 120 | 145 | 0 | 200 | 19.82868 | 14.78 |
plot_ly(banco, x = banco$RestingBP, color = banco$HeartDisease, type = "box") %>%
layout(title = "Boxplot da Pressão Arterial em Repouso de acordo com a presença ou ausência de doença cardíaca")
Apesar de ambos os grupos apresentarem dados semelhantes, o grupo que possui doenças cardiovasculares é ligeiramente maior que o grupo de indivíduos saudáveis. Afinal, o coração doente por não apresentar os batimentos tão eficientes quanto um coração saudável, as artérias tendem a compensar esses batimentos cardíacos aumentando sua pressão.
%>%
banco group_by(HeartDisease) %>%
summarise(media = mean(Cholesterol, na.rm = T),
mediana = median(Cholesterol, na.rm = T),
quartil_1 = quantile(Cholesterol, 0.25, na.rm = T),
quartil_3 = quantile(Cholesterol, 0.75, na.rm = T),
minimo = min(Cholesterol, na.rm = T),
maximo = max(Cholesterol, na.rm = T),
desvio = sd(Cholesterol, na.rm = T),
coeficiente = round(sd(Cholesterol, na.rm = T)/mean(Cholesterol, na.rm = T)*100,2)) %>%
::kable(col.names = c("Grupo", "Média", "Mediana", "1º Quartil", "3º Quartil", "Mínimo", "Máximo", "Desvio Padrão", "Coeficiente de Variação (%)"), caption = "Estatística Descritiva do Colesterol Sérico de Acordo com a presença ou ausência de doença cardíaca") knitr
Grupo | Média | Mediana | 1º Quartil | 3º Quartil | Mínimo | Máximo | Desvio Padrão | Coeficiente de Variação (%) |
---|---|---|---|---|---|---|---|---|
Normal | 227.1220 | 227 | 197.25 | 266.75 | 0 | 564 | 74.63466 | 32.86 |
Doença cardiaca | 175.9409 | 217 | 0.00 | 267.00 | 0 | 603 | 126.39140 | 71.84 |
plot_ly(banco, x = banco$Cholesterol, color = banco$HeartDisease, type = "box") %>%
layout(title = "Boxplot do Colesterol Sérico de acordo \ncom a presença ou ausência de doença cardíaca")
Devem existir inúmeros fatores que podem implicar uma maior variabilidade do colesterol no grupo de pessoas que possuem doenças cardíacas, uma delas pode estar relacionada com o fato do uso de medicações para diminuir e tentar controlar esse nível do colesterol.
Após a visualização gráfica do boxplot, observamos uma diferença significativa na variação dos dados do colesterol de acordo com a presença ou ausência de doença cardíaca.
%>%
banco group_by(HeartDisease) %>%
summarise(media = mean(Oldpeak, na.rm = T),
mediana = median(Oldpeak, na.rm = T),
quartil_1 = quantile(Oldpeak, 0.25, na.rm = T),
quartil_3 = quantile(Oldpeak, 0.75, na.rm = T),
minimo = min(Oldpeak, na.rm = T),
maximo = max(Oldpeak, na.rm = T),
desvio = sd(Oldpeak, na.rm = T),
coeficiente = round(sd(Oldpeak, na.rm = T)/mean(Oldpeak, na.rm = T)*100,2)) %>%
::kable(col.names = c("Grupo", "Média", "Mediana", "1º Quartil", "3º Quartil", "Mínimo", "Máximo", "Desvio Padrão", "Coeficiente de Variação (%)"), caption = "Estatística Descritiva do valor númerico medido na depressão de Acordo com a presença ou ausência de doença cardíaca") knitr
Grupo | Média | Mediana | 1º Quartil | 3º Quartil | Mínimo | Máximo | Desvio Padrão | Coeficiente de Variação (%) |
---|---|---|---|---|---|---|---|---|
Normal | 0.4080488 | 0.0 | 0 | 0.6 | -1.1 | 4.2 | 0.6997091 | 171.48 |
Doença cardiaca | 1.2742126 | 1.2 | 0 | 2.0 | -2.6 | 6.2 | 1.1518720 | 90.40 |
plot_ly(banco, x = banco$Oldpeak, color = banco$HeartDisease, type = "box") %>%
layout(title = "Boxplot do banco do valor númerico medido \nna depressão de acordo com a presença ou ausência de doença cardíaca")
Podemos observar que no grupo normal, temos a presença de muitos outliers, já no grupo de doença cardíaca, entre o primeiro e terceiro quartil podemos enxergar uma variabilidade maior do que comparando esses mesmos quartis.
Agora, vamos visualizar a matriz de correlação das variáveis preditoras
<- banco %>%
rho ::select(where(is.numeric)) %>%
dplyrcor()
::corrplot(rho, method = "circle", type = "lower") corrplot
%>%
banco ::group_by(HeartDisease) %>%
dplyr::summarise(quantidade = n(),
dplyr= round(n()/dim(banco)[1]*100,2)) %>%
proporção ::kable(caption = "Descritiva da Quantidade de Indívidios com ou sem doença cardíaca", col.names = c("Grupo", "Quantidade", "Proporção")) knitr
Grupo | Quantidade | Proporção |
---|---|---|
Normal | 410 | 44.66 |
Doença cardiaca | 508 | 55.34 |
%>%
banco count(HeartDisease, RestingECG) %>%
group_by(HeartDisease) %>%
mutate(percent = n / sum(n) *100,
percent = round(percent, 2)) %>%
::gt() %>%
gt::tab_header(
gttitle = "Situação dos pacientes quanto a presença de doença cardíaca",
subtitle = "Com relação ao eletrocardiograma em repouso"
%>%
) ::cols_label(
gtRestingECG = "ECG em Repouso",
n = "Frequência",
percent = "Percentual"
%>%
) ::fmt_number(
gtcolumns = vars(n),
suffixing = T,
decimals = 0
)
Situação dos pacientes quanto a presença de doença cardíaca | ||
Com relação ao eletrocardiograma em repouso | ||
ECG em Repouso | Frequência | Percentual |
---|---|---|
Normal | ||
Normal | 267 | 65.12 |
anormalidade da onda | 61 | 14.88 |
hipertrofia ventricular | 82 | 20.00 |
Doença cardiaca | ||
Normal | 285 | 56.10 |
anormalidade da onda | 117 | 23.03 |
hipertrofia ventricular | 106 | 20.87 |
%>%
banco count(HeartDisease, ST_Slope) %>%
group_by(HeartDisease) %>%
mutate(percent = n / sum(n) *100,
percent = round(percent, 2)) %>%
::gt() %>%
gt::tab_header(
gttitle = "Situação dos pacientes quanto a presença de doença cardíaca",
subtitle = "Com relação a inclinação do Segmento"
%>%
) ::cols_label(
gtST_Slope = "Inclinação do Segmento",
n = "Frequência",
percent = "Percentual"
%>%
) ::fmt_number(
gtcolumns = vars(n),
suffixing = TRUE,
decimals = 0
)
Situação dos pacientes quanto a presença de doença cardíaca | ||
Com relação a inclinação do Segmento | ||
Inclinação do Segmento | Frequência | Percentual |
---|---|---|
Normal | ||
Ascendente | 317 | 77.32 |
Plano | 79 | 19.27 |
Descendente | 14 | 3.41 |
Doença cardiaca | ||
Ascendente | 78 | 15.35 |
Plano | 381 | 75.00 |
Descendente | 49 | 9.65 |
%>%
banco count(HeartDisease, Sex) %>%
group_by(HeartDisease) %>%
mutate(percent = n / sum(n) *100,
percent = round(percent, 2)) %>%
::gt() %>%
gt::tab_header(
gttitle = "Situação dos pacientes quanto a presença de doença cardíaca",
subtitle = "Com relação ao Sexo"
%>%
) ::cols_label(
gtSex = "Sexo",
n = "Frequência",
percent = "Percentual"
%>%
) ::fmt_number(
gtcolumns = vars(n),
suffixing = TRUE,
decimals = 0
)
Situação dos pacientes quanto a presença de doença cardíaca | ||
Com relação ao Sexo | ||
Sexo | Frequência | Percentual |
---|---|---|
Normal | ||
Masculino | 267 | 65.12 |
Feminino | 143 | 34.88 |
Doença cardiaca | ||
Masculino | 458 | 90.16 |
Feminino | 50 | 9.84 |
%>%
banco count(HeartDisease, ChestPainType) %>%
group_by(HeartDisease) %>%
mutate(percent = n / sum(n) *100,
percent = round(percent, 2)) %>%
::gt() %>%
gt::tab_header(
gttitle = "Situação dos pacientes quanto a presença de doença cardíaca",
subtitle = "Com relação a dor no peito"
%>%
) ::cols_label(
gtChestPainType = "Tipo de dor no peito",
n = "Frequência",
percent = "Percentual"
%>%
) ::fmt_number(
gtcolumns = vars(n),
suffixing = TRUE,
decimals = 0
)
Situação dos pacientes quanto a presença de doença cardíaca | ||
Com relação a dor no peito | ||
Tipo de dor no peito | Frequência | Percentual |
---|---|---|
Normal | ||
Angina Típica | 26 | 6.34 |
Angina Atípica | 149 | 36.34 |
Dor Não Anginosa | 131 | 31.95 |
Assintomática | 104 | 25.37 |
Doença cardiaca | ||
Angina Típica | 20 | 3.94 |
Angina Atípica | 24 | 4.72 |
Dor Não Anginosa | 72 | 14.17 |
Assintomática | 392 | 77.17 |
%>%
banco count(HeartDisease, FastingBS) %>%
group_by(HeartDisease) %>%
mutate(percent = n / sum(n) *100,
percent = round(percent, 2)) %>%
::gt() %>%
gt::tab_header(
gttitle = "Situação dos pacientes quanto a presença de doença cardíaca",
subtitle = "Com relação ao Açúcar no Sengue em Jejum"
%>%
) ::cols_label(
gtFastingBS = "Açucar no Sangue",
n = "Frequência",
percent = "Percentual"
%>%
) ::fmt_number(
gtcolumns = vars(n),
suffixing = TRUE,
decimals = 0
)
Situação dos pacientes quanto a presença de doença cardíaca | ||
Com relação ao Açúcar no Sengue em Jejum | ||
Açucar no Sangue | Frequência | Percentual |
---|---|---|
Normal | ||
C.C | 366 | 89.27 |
JejumBS > 120 mg/dl | 44 | 10.73 |
Doença cardiaca | ||
C.C | 338 | 66.54 |
JejumBS > 120 mg/dl | 170 | 33.46 |
set.seed(2024)
<- initial_split(banco, prop = 0.75, strata = HeartDisease)
split_inicial <- training(split_inicial)
banco_treino <- testing(split_inicial) banco_teste
Para o pré-processamento é a parte de aplicarmos algumas receitas para melhorar os dados, com isso utilizamos a biblioteca recipes
, e aplicamos as receitas de acordo com as necessidades que possuímos.
<- recipe(HeartDisease ~ ., data = banco_treino) %>%
banco_receita # step_impute_knn(all_predictors(), neighbors = 5) %>% # imputa valores ausentes por K-NN
# step_impute_bag(all_predictors()) %>% # imputa valores ausentes por bagged trees
# step_impute_mean(all_numeric_predictors()) %>% # imputa valores ausentes pela média
# step_impute_median(all_numeric_predictors()) %>% # imputa valores ausentes pela mediana
# step_impute_mode(all_predictors()) %>% # imputa valores ausentes pela mediana
# step_naomit(everything(), skip = TRUE) %>% # remove linhas que contém NA ou NaN
# step_interact(terms = ~ all_numeric_predictors():all_numeric_predictors()) %>%
# step_log( # Transformação log: y = log(x)
# ) %>%
step_YeoJohnson( # Transformação Yeo-Johnson
all_numeric_predictors()
%>%
) # step_poly(all_numeric_predictors(), degree = 2) %>%
step_normalize( # normaliza variáveis numéricas para terem média 0 e variância 1
all_numeric_predictors()
%>%
) # step_range( # normaliza variáveis numéricas para pertencerem ao intervalo [0,1]
# all_numeric_predictors()
# ) %>%
step_dummy(all_nominal_predictors()) %>% # converte variáveis qualitativas em variáveis dummy
step_smote(HeartDisease, over_ratio = 1) %>% # Balanceamento de classes usando SMOTE
# step_upsample(diagnosis) %>% # Balanceamento de classes usando upsample
# step_downsample(diagnosis) %>% # Balanceamento de classes usando downsample
# step_nzv(all_numeric_predictors()) %>% # remove variáveis que têm variância próxima de zero
step_corr( # remove preditores que tenham alta correlação com algum outro preditor
all_numeric_predictors(),
threshold = 0.8,
method = "spearman"
)
Depois de aplicar as receitas precisamos continuar para extrair o banco aplicando as receitas.
# Dessa vez, não usaremos os "dados preparados" explicitamente, mas criarei esse objeto para verificarmos o efeito do smote e da imputação dos valores ausentes
set.seed(2024)
<-
banco_preparado %>% # usa a receita
banco_receita prep() %>% # aplica a receita no conjunto de treinamento
juice() # extrai apenas o dataframe preprocessado
<- banco_preparado %>%
tabela count(HeartDisease) %>%
as.tibble()
Warning: `as.tibble()` was deprecated in tibble 2.0.0.
ℹ Please use `as_tibble()` instead.
ℹ The signature and semantics have changed, see `?as_tibble`.
%>%
tabela ::gt() %>%
gttab_header(
title = gt::html("<b> Quantidade de Presentes de acordo com as classes</b>"),
subtitle = glue::glue("No banco de Treinamento")) %>%
::tab_source_note(
gt::html("<b> Fonte:</b> Elaboração Própria")
gt%>%
) ::cols_label(
gtHeartDisease = "Classe",
n = "Quantidade"
)
Quantidade de Presentes de acordo com as classes | |
No banco de Treinamento | |
Classe | Quantidade |
---|---|
Normal | 381 |
Doença cardiaca | 381 |
Fonte: Elaboração Própria |
Utilizamos o método \(k\)-fold cross-validation para construir um conjunto de validação com \(k\) folds. Consideramos um procedimento com \(k = 10\) folds. Os dados são particionados em 10 partes utilizando amostragem estratificada e, em cada iteração, os modelos são ajustados em um conjunto de treinamento com composto por 9 dessas partes e avaliado em um conjunto de teste composto por 1 dessas partes. Esse procedimento foi utilizado para avaliar o modelo e obter os valores ótimos dos hiperparâmetros dos modelos.
<- vfold_cv(banco_treino,
cv_folds v = 10,
strata = HeartDisease)
Os hiperparâmetros dos modelos foram otimizados no processo de validação cruzada. A busca pelos valores ótimos dos hiperparâmetros se deu através de um processo de busca em uma grade aleatória de valores definida através de um esquema de hipercubo latino, visando preencher adequadamente o espaço de valores dos hiperparâmetros.
<- nearest_neighbor(neighbors = tune()) %>% # K-NN
knn_spec set_mode("classification") %>%
set_engine("kknn")
<- naive_Bayes() %>% # Naive Bayes
nbayes_spec set_engine("naivebayes") %>%
set_mode("classification")
<- discrim_linear() %>% # Linear discriminant analysis
lda_spec set_engine("MASS") %>%
set_mode("classification")
<- discrim_quad() %>% # Quadratic discriminant analysis
qda_spec set_engine("MASS") %>%
set_mode("classification")
<- logistic_reg(penalty = tune(), mixture = tune()) %>% # RL
reg_log_spec set_engine(engine = "glmnet", standardize = FALSE) %>%
set_mode("classification")
= workflow_set(
wf preproc = list(banco_receita),
models = list(
KNN = knn_spec,
Nayve_Bayes = nbayes_spec,
LDA = lda_spec,
QDA = qda_spec,
Reg_log = reg_log_spec
)%>%
) mutate(wflow_id = gsub("(recipe_)", "", wflow_id))
= control_grid(
grid_ctrl save_pred = TRUE,
parallel_over = "resamples",
save_workflow = TRUE
)= wf %>%
grid_results workflow_map(
seed = 13,
resamples = cv_folds,
grid = 10,
control = grid_ctrl
)
autoplot(grid_results)
autoplot(grid_results, select_best = TRUE, metric = "roc_auc")
<- workflowsets::rank_results(grid_results,
results select_best = TRUE,
rank_metric = "roc_auc") %>%
filter(.metric == "roc_auc") %>%
::select(wflow_id, mean, std_err, model, rank)
dplyr
colnames(results) <- c("Método", "Média", "Desvio Padrão", "Modelo", "Ranking")
$Método[which(results$Método=="Reg_log")] <- "Regressão Logística"
results$Método[which(results$Método=="LDA")] <- "Discriminante Linear"
results$Método[which(results$Método=="QDA")] <- "Discriminante Quadrática"
results$Método[which(results$Método=="KNN")] <- "Knn - mais próximos"
results$Método[which(results$Método=="Nayve_Bayes")] <- "Nayve Bayes"
results
$Média <- round(results$Média, 4)
results$`Desvio Padrão` <- round(results$`Desvio Padrão`, 4)
results
%>% gt() %>%
results ::tab_header(
gttitle = gt::html("<b> Resultado do Treinamento dos Modelos</b>"),
subtitle = glue::glue("De acordo com a métrica da curva Roc")) %>%
::tab_source_note(
gt::html("<b> Fonte:</b> Elaboração própria")
gt%>%
) ::data_color(
gtcolumns = Média,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 5, palette = "Blue"),
domain = c(min(results$Média), max(results$Média)),
reverse = TRUE
) )
Resultado do Treinamento dos Modelos | ||||
De acordo com a métrica da curva Roc | ||||
Método | Média | Desvio Padrão | Modelo | Ranking |
---|---|---|---|---|
Regressão Logística | 0.9235 | 0.0136 | logistic_reg | 1 |
Discriminante Linear | 0.9203 | 0.0136 | discrim_linear | 2 |
Knn - mais próximos | 0.9196 | 0.0155 | nearest_neighbor | 3 |
Nayve Bayes | 0.9072 | 0.0147 | naive_Bayes | 4 |
Discriminante Quadrática | 0.9060 | 0.0147 | discrim_quad | 5 |
Fonte: Elaboração própria |
autoplot(grid_results, select_best = TRUE, metric = "accuracy")
<- workflowsets::rank_results(grid_results,
results_acc select_best = TRUE,
rank_metric = "accuracy") %>%
filter(.metric == "accuracy") %>%
::select(wflow_id, mean, std_err, model, rank)
dplyr
colnames(results_acc) <- c("Método", "Média", "Desvio Padrão", "Modelo", "Ranking")
$Método[which(results_acc$Método=="Reg_log")] <- "Regressão Logística"
results_acc$Método[which(results_acc$Método=="LDA")] <- "Discriminante Linear"
results_acc$Método[which(results_acc$Método=="QDA")] <- "Discriminante Quadrática"
results_acc$Método[which(results_acc$Método=="KNN")] <- "Knn - mais próximos"
results_acc$Método[which(results_acc$Método=="Nayve_Bayes")] <- "Nayve Bayes"
results_acc
$Média <- round(results_acc$Média, 4)
results_acc$`Desvio Padrão` <- round(results_acc$`Desvio Padrão`, 4)
results_acc
%>% gt() %>%
results_acc ::tab_header(
gttitle = gt::html("<b> Resultado do Treinamento dos Modelos</b>"),
subtitle = glue::glue("De acordo com a métrica da Acurácia")) %>%
::tab_source_note(
gt::html("<b> Fonte:</b> Elaboração própria")
gt%>%
) ::data_color(
gtcolumns = Média,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 5, palette = "Blue"),
domain = c(min(results_acc$Média), max(results_acc$Média)),
reverse = TRUE
) )
Resultado do Treinamento dos Modelos | ||||
De acordo com a métrica da Acurácia | ||||
Método | Média | Desvio Padrão | Modelo | Ranking |
---|---|---|---|---|
Regressão Logística | 0.8676 | 0.0163 | logistic_reg | 1 |
Discriminante Linear | 0.8603 | 0.0157 | discrim_linear | 2 |
Knn - mais próximos | 0.8575 | 0.0175 | nearest_neighbor | 3 |
Discriminante Quadrática | 0.8488 | 0.0144 | discrim_quad | 4 |
Nayve Bayes | 0.8459 | 0.0167 | naive_Bayes | 5 |
Fonte: Elaboração própria |
Agora é necessário selecionar o melhor modelos dos que foram treinados (um de cada modelo), com base na estimação dos hiperparâmetros para aplicar os dados de teste.
= grid_results %>%
best_set_linear extract_workflow_set_result("Reg_log") %>%
select_best(metric = "accuracy")
= grid_results %>%
best_set_knn extract_workflow_set_result("KNN") %>%
select_best(metric = "accuracy")
= grid_results %>%
best_set_nbayes extract_workflow_set_result("Nayve_Bayes") %>%
select_best(metric = "accuracy")
= grid_results %>%
best_set_lda extract_workflow_set_result("LDA") %>%
select_best(metric = "accuracy")
= grid_results %>%
best_set_qda extract_workflow_set_result("QDA") %>%
select_best(metric = "accuracy")
O modelo KNN que foi o melhor teve um número de 10 vizinhos.
%>%
best_set_linear gt() %>%
::tab_header(
gttitle = gt::html("<b> Resultado dos hiperparâmetros do melhor ajuste</b>"),
subtitle = glue::glue("Do modelo de Regressão Logística")) %>%
::tab_source_note(
gt::html("<b> Fonte:</b> Elaboração própria")
gt%>%
) ::cols_label(
gtpenalty = "Penalização",
mixture = "Mistura",
.config = "Configuração"
)
Resultado dos hiperparâmetros do melhor ajuste | ||
Do modelo de Regressão Logística | ||
Penalização | Mistura | Configuração |
---|---|---|
0.00898431 | 0.2919612 | Preprocessor1_Model03 |
Fonte: Elaboração própria |
Esses conjuntos de hiperparâmetros ótimos foram utilizados para reajustar os modelos no conjunto de treinamento completo para, em seguida, obter predições das classes da variável alvo no conjunto de teste. Foram calculadas as seguintes medidas no conjunto de teste: acurácia, área sob a curva ROC, F-measure, precision, recall, especificidade e Kappa.
<- function(rc_rslts, fit_obj, par_set, split_obj) {
resultado_teste <- rc_rslts %>%
res extract_workflow(fit_obj) %>%
finalize_workflow(par_set) %>%
last_fit(split = split_obj,
metrics = metric_set(
accuracy,roc_auc,
f_meas,precision,
recall,spec,kap))
res }
<- resultado_teste(grid_results, "Reg_log", best_set_linear, split_inicial)
resultado_teste_reg_log <- resultado_teste(grid_results, "KNN", best_set_knn, split_inicial)
resultado_teste_knn <- resultado_teste(grid_results, "LDA", best_set_lda, split_inicial)
resultado_teste_lda <- resultado_teste(grid_results, "QDA", best_set_qda, split_inicial)
resultado_teste_qda <- resultado_teste(grid_results, "Nayve_Bayes", best_set_nbayes, split_inicial) resultado_teste_naive
<- rbind(collect_metrics(resultado_teste_reg_log)$.estimate,
metrics_table collect_metrics(resultado_teste_knn)$.estimate,
collect_metrics(resultado_teste_lda)$.estimate,
collect_metrics(resultado_teste_qda)$.estimate,
collect_metrics(resultado_teste_naive)$.estimate)
Ajustando a tabela de métricas
<- round(metrics_table, 4)
metrics_table
<- c("Regressão Logística", "KNN", "Discriminante Linear", "Discriminante Quadrático", "Naive Bayes")
row_names
<- cbind(row_names, metrics_table)
metrics_table
<- metrics_table %>%
metrics_table as.tibble()
colnames(metrics_table) <- c("Método", "Acurácia", "Curva Roc", "f_means", "Precisão", "Recall", "Específicidade", "Kappa")
<- metrics_table %>%
metrics_table mutate(Acurácia = as.numeric(Acurácia),
`Curva Roc` = as.numeric(`Curva Roc`),
f_means = as.numeric(f_means),
= as.numeric(Precisão),
Precisão Recall = as.numeric(Recall),
= as.numeric(Específicidade),
Específicidade Kappa = as.numeric(Kappa)) %>%
arrange(desc(Acurácia), desc(`Curva Roc`), desc(f_means), desc(Kappa))
%>%
metrics_table ::gt() %>%
gt::tab_header(
gttitle = gt::html("<b> Resultado dos modelos nos dados de teste</b>"),
subtitle = glue::glue("De acordo com algumas métricas")) %>%
::tab_source_note(
gt::html("<b> Fonte:</b> Elaboração própria")
gt%>%
) ::data_color(
gtcolumns = Acurácia,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$Acurácia), max(metrics_table$Acurácia)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = `Curva Roc`,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$`Curva Roc`), max(metrics_table$`Curva Roc`)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = f_means,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$f_means), max(metrics_table$f_means)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = Precisão,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$Precisão), max(metrics_table$Precisão)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = Recall,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$Recall), max(metrics_table$Recall)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = Específicidade,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$Específicidade), max(metrics_table$Específicidade)),
reverse = TRUE
)%>%
) ::data_color(
gtcolumns = Kappa,
colors = scales::col_numeric(
palette = colorspace::sequential_hcl(n = 10, palette = "Blue"),
domain = c(min(metrics_table$Kappa), max(metrics_table$Kappa)),
reverse = TRUE
) )
Resultado dos modelos nos dados de teste | |||||||
De acordo com algumas métricas | |||||||
Método | Acurácia | Curva Roc | f_means | Precisão | Recall | Específicidade | Kappa |
---|---|---|---|---|---|---|---|
Naive Bayes | 0.8696 | 0.8558 | 0.8476 | 0.8641 | 0.8740 | 0.7367 | 0.9304 |
Discriminante Quadrático | 0.8652 | 0.8502 | 0.8462 | 0.8544 | 0.8740 | 0.7277 | 0.9378 |
Regressão Logística | 0.8652 | 0.8473 | 0.8600 | 0.8350 | 0.8898 | 0.7267 | 0.9305 |
Discriminante Linear | 0.8652 | 0.8442 | 0.8750 | 0.8155 | 0.9055 | 0.7257 | 0.9297 |
KNN | 0.8522 | 0.8365 | 0.8286 | 0.8447 | 0.8583 | 0.7016 | 0.8975 |
Fonte: Elaboração própria |
Na tarefa passada, sem aplicar alguns desses métodos e com menos variáveis, tivemos uma acurácia menor do que 80%, agora com a aplicação desses métodos e com a utilização de mais variáveis, podemos observar que a acurácia do melhor modelo que foi o Naive Bayes foi de 0.8696, e o pior método pela acurácia foi o método KNN.