Construir y evaluar un modelo KNN para resolver tarea de clasificación mediante la predicción de si una persona tiene daño de corazón o no.
El método K-NN es un método importantes de clasificación supervisada. En el proceso de aprendizaje no se hace ninguna suposición acerca de la distribución de las variables predictoras, es por ello que es un método de clasificación no paramétrico, que estima el valor de la función de densidad de probabilidad o directamente la probabilidad posterior de que un elemento \(x\) pertenezca a la clase \(C_j\) a partir de la información proporcionada por el conjunto de entrenamiento.
Es un método bastante sencillo y robusto que simplemente busca en las observaciones más cercanas a la que se está tratando de predecir y clasifica el punto de interés basado en la mayoría de datos que le rodean.
Es un algoritmo muy simple de implementar y de entrenar, pero tienen una carga computacional elevada y no es apropiado cuando se tienen muchos grados de libertad. (Villalba 2018)
Cargar librerías, datos y hacer lo necesario aplicando función knn de la librería class y la función train.knn de la librería kknn.
Se descargan los datos: https://raw.githubusercontent.com/rpizarrog/Analisis-Inteligente-de-datos/main/datos/heart_2020_cleaned.csv
Los datos están relacionados con aspectos médicos y son valores numéricos de varias variables que caracterizan el estado de salud de 319,795 personas.
Se pretende construir un modelo utilizando algoritmos supervisados para resolver la tarea de clasificación binaria e identificar si una persona padece del corazón o no.
Se construyen datos de entrenamiento y validación al 80% y 20% cada uno.
Se desarrollan los modelos de:
Regresión Logística binaria
Árbol de Clasificación tipo class
K Means
SVM Lineal
SVM Polinomial
SVM Radial
Los modelo se aceptan si tienen un valor de exactitud por encima del 70%.
library(readr) # Leer datos
library(kknn) # KNN modelo para kknn
library(dplyr) # Procesar filtrar
library(forcats) # para decodificar vars
library(class) # Para knn()
library(caret) # Matriz de confusión entre otros
library(reshape) # Para modificar variables
library(knitr) # Para tablas amigables
Cargar datos de manera local.
# datos <- read.csv("https://raw.githubusercontent.com/rpizarrog/Machine-Learning-con-R/main/datos/heart_2020_cleaned.csv")
datos <- read.csv("../../datos/heart_2020_cleaned.csv", encoding = "UTF-8", stringsAsFactors = TRUE)
str(datos)
## 'data.frame': 319795 obs. of 18 variables:
## $ HeartDisease : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 2 1 1 1 1 ...
## $ BMI : num 16.6 20.3 26.6 24.2 23.7 ...
## $ Smoking : Factor w/ 2 levels "No","Yes": 2 1 2 1 1 2 1 2 1 1 ...
## $ AlcoholDrinking : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
## $ Stroke : Factor w/ 2 levels "No","Yes": 1 2 1 1 1 1 1 1 1 1 ...
## $ PhysicalHealth : num 3 0 20 0 28 6 15 5 0 0 ...
## $ MentalHealth : num 30 0 30 0 0 0 0 0 0 0 ...
## $ DiffWalking : Factor w/ 2 levels "No","Yes": 1 1 1 1 2 2 1 2 1 2 ...
## $ Sex : Factor w/ 2 levels "Female","Male": 1 1 2 1 1 1 1 1 1 2 ...
## $ AgeCategory : Factor w/ 13 levels "18-24","25-29",..: 8 13 10 12 5 12 11 13 13 10 ...
## $ Race : Factor w/ 6 levels "American Indian/Alaskan Native",..: 6 6 6 6 6 3 6 6 6 6 ...
## $ Diabetic : Factor w/ 4 levels "No","No, borderline diabetes",..: 3 1 3 1 1 1 1 3 2 1 ...
## $ PhysicalActivity: Factor w/ 2 levels "No","Yes": 2 2 2 1 2 1 2 1 1 2 ...
## $ GenHealth : Factor w/ 5 levels "Excellent","Fair",..: 5 5 2 3 5 2 2 3 2 3 ...
## $ SleepTime : num 5 7 8 6 8 12 4 9 5 10 ...
## $ Asthma : Factor w/ 2 levels "No","Yes": 2 1 2 1 1 1 2 2 1 1 ...
## $ KidneyDisease : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 2 1 ...
## $ SkinCancer : Factor w/ 2 levels "No","Yes": 2 1 1 2 1 1 2 1 1 1 ...
summary(datos)
## HeartDisease BMI Smoking AlcoholDrinking Stroke
## No :292422 Min. :12.02 No :187887 No :298018 No :307726
## Yes: 27373 1st Qu.:24.03 Yes:131908 Yes: 21777 Yes: 12069
## Median :27.34
## Mean :28.33
## 3rd Qu.:31.42
## Max. :94.85
##
## PhysicalHealth MentalHealth DiffWalking Sex
## Min. : 0.000 Min. : 0.000 No :275385 Female:167805
## 1st Qu.: 0.000 1st Qu.: 0.000 Yes: 44410 Male :151990
## Median : 0.000 Median : 0.000
## Mean : 3.372 Mean : 3.898
## 3rd Qu.: 2.000 3rd Qu.: 3.000
## Max. :30.000 Max. :30.000
##
## AgeCategory Race
## 65-69 : 34151 American Indian/Alaskan Native: 5202
## 60-64 : 33686 Asian : 8068
## 70-74 : 31065 Black : 22939
## 55-59 : 29757 Hispanic : 27446
## 50-54 : 25382 Other : 10928
## 80 or older: 24153 White :245212
## (Other) :141601
## Diabetic PhysicalActivity GenHealth
## No :269653 No : 71838 Excellent: 66842
## No, borderline diabetes: 6781 Yes:247957 Fair : 34677
## Yes : 40802 Good : 93129
## Yes (during pregnancy) : 2559 Poor : 11289
## Very good:113858
##
##
## SleepTime Asthma KidneyDisease SkinCancer
## Min. : 1.000 No :276923 No :308016 No :289976
## 1st Qu.: 6.000 Yes: 42872 Yes: 11779 Yes: 29819
## Median : 7.000
## Mean : 7.097
## 3rd Qu.: 8.000
## Max. :24.000
##
No es necesario alguna transformación
Todas las variables son de entrada o variables independientes:
“BMI”: Indice de masa corporal con valores entre 12.02 y 94.85.
“Smoking”: Si la persona es fumadora o no con valores categóritos de ‘Yes’ o ‘No.’
“AlcoholDrinking” : Si consume alcohol o no, con valores categóricos de ‘Yes’ o ‘No.’
“Stroke”: Si padece alguna anomalía cerebrovascular, apoplejia o algo similar, con valores categóricos de ‘Yes’ o ‘No.’
“PhysicalHealth” Estado físico en lo general con valores entre 0 y 30.
“MentalHealth.” Estado mental en lo general con valores entre 0 y 30.
“DiffWalking” . Que si se le dificulta caminar o tiene algún padecimiento al caminar, con valores categóritoc de ‘Yes’ o ‘No.’
“Sex”: Género de la persona, con valores de ‘Female’ y ‘Male’ para distinguir al género femenino y masculino respectivamente.
“AgeCategory”: Una clasificación de la edad de la persona de entre 18 y 80 años. La primera categoría con un rango de edad entre 18-24, a partir de 25 con rangos de 5 en 5 hasta la clase de 75-80 y una última categoría mayores de 80 años.
“Race.” Raza u origen de la persona con valores categóricos de ‘American Indian/Alaskan Native’, ’Asian’,’Black’, ’Hispanic’, ’Other’ y’White’.
“Diabetic.” Si padece o ha padecido de diabetes en cuatro condiciones siendo Yes y No para si o no: ‘No,’ ‘borderline diabetes’ condición antes de detectarse diabetes tipo 2, ‘Yes,’ y ‘Yes (during pregnancy)’ durante embarazo.
“PhysicalActivity” que si realiza actividad física, con valores categóricos de ‘Yes’ o ‘No.’
“GenHealth”: EStado general de salud de la persona con valores categóricos de ‘Excellent,’ ‘Very good,’ ‘Good,’ ‘Fair’ y ‘Poor’ con significado en español de excelente, muy buena, buena, regular y pobre o deficiente.
“SleepTime”: valor numérico de las horas de sueño u horas que duerme la persona con valores en un rango entre 1 y 24.
“Asthma”: si padece de asma o no, con valores categóricos de ‘Yes’ o ‘No.’
“KidneyDisease”: si tiene algún padecimiento en los riñones, con valores categóricos de ‘Yes’ o ‘No.’
“SkinCancer”: si padece algún tipo de cancer de piel, con valores categóricos de ‘Yes’ o ‘No.’
La variable de interés como dependiente o variable de salida es la de daño al corazón (HeartDisease), con valores categóricos de ‘Yes’ o ‘No.’
Se parten los datos en en datos de entrenamiento con el 80% y datos de validación con el 20%.
set.seed(2022)
entrena <- createDataPartition(y = datos$HeartDisease,
p = 0.8,
list = FALSE,
times = 1)
# Datos entrenamiento
datos.entrenamiento <- datos[entrena, ] # [renglones, columna]
# Datos validación
datos.validacion <- datos[-entrena, ]
Se muestran los primeros 20 registros datos de entrenamiento
kable(head(datos.entrenamiento, 20), caption = "Primeros 20 registros de datos de entrenamiento")
| HeartDisease | BMI | Smoking | AlcoholDrinking | Stroke | PhysicalHealth | MentalHealth | DiffWalking | Sex | AgeCategory | Race | Diabetic | PhysicalActivity | GenHealth | SleepTime | Asthma | KidneyDisease | SkinCancer | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | No | 16.60 | Yes | No | No | 3 | 30 | No | Female | 55-59 | White | Yes | Yes | Very good | 5 | Yes | No | Yes |
| 2 | No | 20.34 | No | No | Yes | 0 | 0 | No | Female | 80 or older | White | No | Yes | Very good | 7 | No | No | No |
| 3 | No | 26.58 | Yes | No | No | 20 | 30 | No | Male | 65-69 | White | Yes | Yes | Fair | 8 | Yes | No | No |
| 4 | No | 24.21 | No | No | No | 0 | 0 | No | Female | 75-79 | White | No | No | Good | 6 | No | No | Yes |
| 5 | No | 23.71 | No | No | No | 28 | 0 | Yes | Female | 40-44 | White | No | Yes | Very good | 8 | No | No | No |
| 6 | Yes | 28.87 | Yes | No | No | 6 | 0 | Yes | Female | 75-79 | Black | No | No | Fair | 12 | No | No | No |
| 7 | No | 21.63 | No | No | No | 15 | 0 | No | Female | 70-74 | White | No | Yes | Fair | 4 | Yes | No | Yes |
| 8 | No | 31.64 | Yes | No | No | 5 | 0 | Yes | Female | 80 or older | White | Yes | No | Good | 9 | Yes | No | No |
| 9 | No | 26.45 | No | No | No | 0 | 0 | No | Female | 80 or older | White | No, borderline diabetes | No | Fair | 5 | No | Yes | No |
| 10 | No | 40.69 | No | No | No | 0 | 0 | Yes | Male | 65-69 | White | No | Yes | Good | 10 | No | No | No |
| 11 | Yes | 34.30 | Yes | No | No | 30 | 0 | Yes | Male | 60-64 | White | Yes | No | Poor | 15 | Yes | No | No |
| 12 | No | 28.71 | Yes | No | No | 0 | 0 | No | Female | 55-59 | White | No | Yes | Very good | 5 | No | No | No |
| 13 | No | 28.37 | Yes | No | No | 0 | 0 | Yes | Male | 75-79 | White | Yes | Yes | Very good | 8 | No | No | No |
| 14 | No | 28.15 | No | No | No | 7 | 0 | Yes | Female | 80 or older | White | No | No | Good | 7 | No | No | No |
| 15 | No | 29.29 | Yes | No | No | 0 | 30 | Yes | Female | 60-64 | White | No | No | Good | 5 | No | No | No |
| 16 | No | 29.18 | No | No | No | 1 | 0 | No | Female | 50-54 | White | No | Yes | Very good | 6 | No | No | No |
| 17 | No | 26.26 | No | No | No | 5 | 2 | No | Female | 70-74 | White | No | No | Very good | 10 | No | No | No |
| 18 | No | 22.59 | Yes | No | No | 0 | 30 | Yes | Male | 70-74 | White | No, borderline diabetes | Yes | Good | 8 | No | No | No |
| 20 | No | 18.13 | No | No | No | 0 | 0 | No | Male | 80 or older | White | No | Yes | Excellent | 8 | No | No | Yes |
| 21 | No | 21.16 | No | No | No | 0 | 0 | No | Female | 80 or older | Black | No, borderline diabetes | No | Good | 8 | No | No | No |
Se muestran los primeros 20 registros de datos de validación .
kable(head(datos.entrenamiento, 20), caption = "Primeros 20 registros de datos de entrenamiento")
| HeartDisease | BMI | Smoking | AlcoholDrinking | Stroke | PhysicalHealth | MentalHealth | DiffWalking | Sex | AgeCategory | Race | Diabetic | PhysicalActivity | GenHealth | SleepTime | Asthma | KidneyDisease | SkinCancer | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | No | 16.60 | Yes | No | No | 3 | 30 | No | Female | 55-59 | White | Yes | Yes | Very good | 5 | Yes | No | Yes |
| 2 | No | 20.34 | No | No | Yes | 0 | 0 | No | Female | 80 or older | White | No | Yes | Very good | 7 | No | No | No |
| 3 | No | 26.58 | Yes | No | No | 20 | 30 | No | Male | 65-69 | White | Yes | Yes | Fair | 8 | Yes | No | No |
| 4 | No | 24.21 | No | No | No | 0 | 0 | No | Female | 75-79 | White | No | No | Good | 6 | No | No | Yes |
| 5 | No | 23.71 | No | No | No | 28 | 0 | Yes | Female | 40-44 | White | No | Yes | Very good | 8 | No | No | No |
| 6 | Yes | 28.87 | Yes | No | No | 6 | 0 | Yes | Female | 75-79 | Black | No | No | Fair | 12 | No | No | No |
| 7 | No | 21.63 | No | No | No | 15 | 0 | No | Female | 70-74 | White | No | Yes | Fair | 4 | Yes | No | Yes |
| 8 | No | 31.64 | Yes | No | No | 5 | 0 | Yes | Female | 80 or older | White | Yes | No | Good | 9 | Yes | No | No |
| 9 | No | 26.45 | No | No | No | 0 | 0 | No | Female | 80 or older | White | No, borderline diabetes | No | Fair | 5 | No | Yes | No |
| 10 | No | 40.69 | No | No | No | 0 | 0 | Yes | Male | 65-69 | White | No | Yes | Good | 10 | No | No | No |
| 11 | Yes | 34.30 | Yes | No | No | 30 | 0 | Yes | Male | 60-64 | White | Yes | No | Poor | 15 | Yes | No | No |
| 12 | No | 28.71 | Yes | No | No | 0 | 0 | No | Female | 55-59 | White | No | Yes | Very good | 5 | No | No | No |
| 13 | No | 28.37 | Yes | No | No | 0 | 0 | Yes | Male | 75-79 | White | Yes | Yes | Very good | 8 | No | No | No |
| 14 | No | 28.15 | No | No | No | 7 | 0 | Yes | Female | 80 or older | White | No | No | Good | 7 | No | No | No |
| 15 | No | 29.29 | Yes | No | No | 0 | 30 | Yes | Female | 60-64 | White | No | No | Good | 5 | No | No | No |
| 16 | No | 29.18 | No | No | No | 1 | 0 | No | Female | 50-54 | White | No | Yes | Very good | 6 | No | No | No |
| 17 | No | 26.26 | No | No | No | 5 | 2 | No | Female | 70-74 | White | No | No | Very good | 10 | No | No | No |
| 18 | No | 22.59 | Yes | No | No | 0 | 30 | Yes | Male | 70-74 | White | No, borderline diabetes | Yes | Good | 8 | No | No | No |
| 20 | No | 18.13 | No | No | No | 0 | 0 | No | Male | 80 or older | White | No | Yes | Excellent | 8 | No | No | Yes |
| 21 | No | 21.16 | No | No | No | 0 | 0 | No | Female | 80 or older | Black | No, borderline diabetes | No | Good | 8 | No | No | No |
Construir el modelo bajo el algoritmo KNN en donde la variable HeartDisease depende de todos las variables.
Se construye el modelo con una muestra de 2000 mil observaciones en lugar de las 255837 que tienen el conjunto de datos de entrenamiento.
muestra <- sample(x = 1:nrow(datos.entrenamiento), size = 10000, replace = FALSE)
Se construye el modelo..
modelo.knnn <- train.kknn(data = datos.entrenamiento[muestra, ], formula = HeartDisease ~ ., kmax = 30)
summary(modelo.knnn)
##
## Call:
## train.kknn(formula = HeartDisease ~ ., data = datos.entrenamiento[muestra, ], kmax = 30)
##
## Type of response variable: nominal
## Minimal misclassification: 0.0842
## Best kernel: optimal
## Best k: 26
predicciones <- predict(object = modelo.knnn, newdata = datos.validacion)
Solo se observan los primeros 20 registros a comparar
datos.comparar <- data.frame("real" = datos.validacion$HeartDisease, "predicho" = predicciones)
kable(head(datos.comparar, 20), caption = "Datos a comparar previo a matriz de confusión" )
| real | predicho |
|---|---|
| No | No |
| No | No |
| No | No |
| No | No |
| Yes | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
| Yes | No |
| No | No |
| No | No |
| No | No |
| No | No |
| No | No |
Con la función confussion el estadístico Accuracy = Exactitud.
matriz <- confusionMatrix(datos.comparar$real, datos.comparar$predicho)
matriz
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 58121 363
## Yes 5200 274
##
## Accuracy : 0.913
## 95% CI : (0.9108, 0.9152)
## No Information Rate : 0.99
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0731
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.91788
## Specificity : 0.43014
## Pos Pred Value : 0.99379
## Neg Pred Value : 0.05005
## Prevalence : 0.99004
## Detection Rate : 0.90874
## Detection Prevalence : 0.91441
## Balanced Accuracy : 0.67401
##
## 'Positive' Class : No
##
Se tiene una Accuracy = exactitud del 91.30%
Este proceso es transformar variables
Antes de utilizar este modelo debe convertirse los valores de las variables cualitativas tipo factor a variables numéricas. Primero las variable dicotómicas Yes, No a 1, 2.
Se construye un data.frame similar pero llamado datos2 con variables numéricas en lugar de factores; esto se hace para el modelo se construya con la función knn().
datos2 <- datos %>%
mutate(Smoking = if_else(Smoking == 'Yes', 1, 2), AlcoholDrinking = if_else(AlcoholDrinking == 'Yes', 1, 2), Stroke = if_else(Stroke == 'Yes', 1, 2), DiffWalking = if_else(DiffWalking == 'Yes', 1, 2), Sex = if_else(Sex == 'Female', 1, 2), PhysicalActivity = if_else(PhysicalActivity == 'Yes', 1, 2), Asthma = if_else(Asthma == 'Yes', 1, 2), KidneyDisease = if_else(KidneyDisease == 'Yes', 1, 2), SkinCancer = if_else(SkinCancer == 'Yes', 1, 2))
datos2 <- datos2 %>%
mutate(AgeCategory = ifelse (AgeCategory == '18-24', 1, ifelse(AgeCategory == '25-29', 2, ifelse(AgeCategory == '30-34', 3, ifelse(AgeCategory == '35-39', 4, ifelse(AgeCategory == '40-44', 5, ifelse(AgeCategory == '45-49', 6, ifelse(AgeCategory == '50-54', 7, ifelse(AgeCategory == '55-59', 8, ifelse(AgeCategory == '60-64', 9, ifelse(AgeCategory == '65-69', 10, ifelse(AgeCategory == '70-74', 11, ifelse(AgeCategory == '75-79', 12, 13)))))))))))))
datos2 <- datos2 %>%
mutate(Race = ifelse (Race == 'White', 1, ifelse(Race == 'Black', 2, ifelse(Race == 'Asian', 3, ifelse(Race == 'American Indian/Alaskan Native', 4, ifelse(Race == 'Other', 5, 6 ))))))
datos2 <- datos2 %>%
mutate(Diabetic = ifelse (Diabetic == 'Yes', 1, ifelse(Diabetic == 'No', 2, ifelse(Diabetic == 'No, borderline diabetes', 3, 4))))
datos2 <- datos2 %>%
mutate(GenHealth = ifelse (Race == 'Fair', 1, ifelse(GenHealth == 'Poor', 2, ifelse(GenHealth == 'Good', 3, ifelse(GenHealth == 'Very good', 4, 5 )))))
Nuevamente partir en datos de entrenamiento y validación per ahora con los datos2
set.seed(2022)
entrena <- createDataPartition(y = datos2$HeartDisease,
p = 0.8,
list = FALSE,
times = 1)
# Datos entrenamiento
datos.entrenamiento <- datos2[entrena, ] # [renglones, columna]
# Datos validación
datos.validacion <- datos2[-entrena, ]
Nuevamente una muestra de los datos de entrenamento.
muestra <- sample(x = 1:nrow(datos.entrenamiento), size = 10000, replace = FALSE)
Se utiliza la función knn de la librería class para estimar predicciones. Se utiliza la variable predicciones.2 para diferenciar de la variable predicciones.
Se utiliza una muestra de 10000 mil registros de los datos de entrenamiento porque el modelo se tarda bastante tiempo en construirse si se utilian todos los registros de los datos de entrenamiento.
# predicciones.2 <- knn(train = datos.entrenamiento[, -1], test = datos.validacion[, -1], cl = datos.entrenamiento[,1], k = 12)
predicciones.2 <- knn(train = datos.entrenamiento[muestra, -1], test = datos.validacion[, -1], cl = datos.entrenamiento[muestra,1], k = 12)
Determinando la matriz de confusión con predicciones.2
matriz2 <- confusionMatrix(datos.validacion$HeartDisease, predicciones.2)
matriz2
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 58398 86
## Yes 5431 43
##
## Accuracy : 0.9137
## 95% CI : (0.9115, 0.9159)
## No Information Rate : 0.998
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0115
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.914913
## Specificity : 0.333333
## Pos Pred Value : 0.998530
## Neg Pred Value : 0.007855
## Prevalence : 0.997983
## Detection Rate : 0.913068
## Detection Prevalence : 0.914413
## Balanced Accuracy : 0.624123
##
## 'Positive' Class : No
##
El modelo KNN con la función train.kknn() arroja una exactitud del 91.30%, significa que el modelo acierta en 91.30% ocasiones de cada cien pacientes.
El modelo KNN con la función knn() arroja una exactitud del 91.37%, significa que el modelo acierta en 91.37 ocasiones de cada cien pacientes.
Siendo el mismo algoritmo las funciones mismas arrojan diferentes estadísticos. Esto supone el algoritmo es diferente el código de cada función ya que cada una de ellas encapsula su propio código dependiendo del paquete y del autor.
Se puede comparar contra otros modelos:
Pendiente ….