Implementar el modelo de vecinos mas cercanos KNN con programación Python para resolver la tarea de clasificación de una condición de salud de las personas mediante predicción de anomalías de corazón evaluando la exactitud del modelo mediante la matriz de confusión.
Se cargan librerías y 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 construye un modelo supervisado basado en el algoritmo de vecinos mas cercanos KNN para resolver la tarea de clasificación binaria e identificar si una persona padece del corazón o no.
Se construye el modelo con sklearn.neighbors import KNeighborsClassifier
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
KNN Vecinos mas cercanos
SVM Lineal
SVM Polinomial
SVM Radial
Los modelo se aceptan si tienen un valor de exactitud por encima del 70%..
El algoritmo vecinos mas cercanos KNN clasifica cada dato nuevo en el grupo que corresponda, según tenga k vecinos más cerca de un grupo o de otro. Es decir, calcula la distancia del elemento nuevo a cada uno de los existentes, y ordena dichas distancias de menor a mayor para ir seleccionando el grupo al que pertenecer.
Este grupo será, por tanto, el de mayor frecuencia con menores distancias.
El KNN es un algoritmo de aprendizaje supervisado, es decir, que a partir de un juego de datos inicial su objetivo será el de clasificar correctamente todas las instancias nuevas. El juego de datos típico de este tipo de algoritmos está formado por varios atributos descriptivos y un solo atributo objetivo (también llamado clase).
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 \(CjCj\) 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.
Algunas librerías son nuevas, hay que instalarlas desde R, aquí se indican cuáles librerías y con comentario dado que ya se instalaron previamente.
# library(reticulate)
# py_install("statsmodels")
# Tratamiento de datos
import pandas as pd
import numpy as np
import statsmodels.api as sm
# Estadísticas
import scipy
from scipy import stats
# Para partir datos entrenamiento y validación
from sklearn.model_selection import train_test_split
# Modelo de Clasificación
from sklearn.metrics import classification_report
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
# Gráficos
import matplotlib.pyplot as plt
import seaborn as sb
Se cargan datos del enlace URL, se observan los primeros y últimos registros del conjunto de datos.
datos = pd.read_csv("https://raw.githubusercontent.com/rpizarrog/Analisis-Inteligente-de-datos/main/datos/danios%20al%20corazon%20numericos%20limpios.csv")
datos
## HeartDisease BMI Smoking ... Asthma KidneyDisease SkinCancer
## 0 No 16.60 1 ... 1 2 1
## 1 No 20.34 2 ... 2 2 2
## 2 No 26.58 1 ... 1 2 2
## 3 No 24.21 2 ... 2 2 1
## 4 No 23.71 2 ... 2 2 2
## ... ... ... ... ... ... ... ...
## 319790 Yes 27.41 1 ... 1 2 2
## 319791 No 29.84 1 ... 1 2 2
## 319792 No 24.24 2 ... 2 2 2
## 319793 No 32.81 2 ... 2 2 2
## 319794 No 46.56 2 ... 2 2 2
##
## [319795 rows x 18 columns]
Son 319795 observaciones y 18 variables
print("Observaciones y variables: ", datos.shape)
## Observaciones y variables: (319795, 18)
print("Columnas y tipo de dato")
# datos.columns
## Columnas y tipo de dato
datos.dtypes
## HeartDisease object
## BMI float64
## Smoking int64
## AlcoholDrinking int64
## Stroke int64
## PhysicalHealth int64
## MentalHealth int64
## DiffWalking int64
## Sex int64
## AgeCategory int64
## Race int64
## Diabetic int64
## PhysicalActivity int64
## GenHealth int64
## SleepTime int64
## Asthma int64
## KidneyDisease int64
## SkinCancer int64
## dtype: object
Hay 292422 casos sin daño al corazón y el resto que si tienen daño 27373.
frecuencia = (datos.groupby("HeartDisease").agg(frecuencia=("HeartDisease","count")).reset_index())
frecuencia
## HeartDisease frecuencia
## 0 No 292422
## 1 Yes 27373
fig, ax = plt.subplots()
# Colores
bar_labels = ['No', 'Yes']
bar_colors = ['tab:blue', 'tab:red']
#frecuencia['frecuencia'].plot(kind="bar")
ax.bar(frecuencia['HeartDisease'], frecuencia['frecuencia'], label=bar_labels, color=bar_colors)
## <BarContainer object of 2 artists>
ax.set_ylabel('Frecuencia')
ax.set_title('Daños al Corazón')
ax.legend(title='Daño')
plt.show()
# plt.gcf().clear()
Crear variable llamada HeartDisease01 que se utilizará en el modelo de Regresión Logística tendrá valores 0 de para ‘No’ daño y 1 para si hay daño (‘Yes’).
datos['HeartDisease01'] = np.where(datos ['HeartDisease']== "Yes", 1, 0)
Quitar la variable HeartDisease que ya tiene variable transformada a HeartDisease01
datos = datos.drop("HeartDisease", axis='columns')
Quedaron las columnas:
datos.columns.values
## array(['BMI', 'Smoking', 'AlcoholDrinking', 'Stroke', 'PhysicalHealth',
## 'MentalHealth', 'DiffWalking', 'Sex', 'AgeCategory', 'Race',
## 'Diabetic', 'PhysicalActivity', 'GenHealth', 'SleepTime', 'Asthma',
## 'KidneyDisease', 'SkinCancer', 'HeartDisease01'], dtype=object)
Todas las variables 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’. [1 | 2]
“AlcoholDrinking” : Si consume alcohol o no, con valores categóricos de ‘Yes’ o ‘No’.[1 | 2]
“Stroke”: Si padece alguna anomalía cerebrovascular, apoplejia o algo similar, con valores categóricos de ‘Yes’ o ‘No’. [1 | 2]
“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’.[1 | 2]
“Sex”: Género de la persona, con valores de ‘Female’ y ‘Male’ para distinguir al género femenino y masculino respectivamente. [1 | 2]
“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. [1 - 13]
“Race”. Raza u origen de la persona con valores categóricos de ‘American Indian/Alaskan Native’, ’Asian’,’Black’, ’Hispanic’, ’Other’ y’White’. [1 - 6]
“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. [1 - 4]
“PhysicalActivity” que si realiza actividad física, con valores categóricos de ‘Yes’ o ‘No’. [1 | 2]
“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. [1 - 5]
“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’. [1 | 2].
“KidneyDisease”: si tiene algún padecimiento en los riñones, con valores categóricos de ‘Yes’ o ‘No’. [1 | 2].
“SkinCancer”: si padece algún tipo de cáncer de piel, con valores categóricos de ‘Yes’ o ‘No’. [1 | 2].
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’ , ahora la variable HeartDisease01 con valores 1 o 0.
Nuevamente la descripción de variables y ahora son 319795 observaciones y 18 variables
print("Observaciones y variables: ", datos.shape)
## Observaciones y variables: (319795, 18)
print("Columnas y tipo de dato")
# datos.columns
## Columnas y tipo de dato
datos.dtypes
## BMI float64
## Smoking int64
## AlcoholDrinking int64
## Stroke int64
## PhysicalHealth int64
## MentalHealth int64
## DiffWalking int64
## Sex int64
## AgeCategory int64
## Race int64
## Diabetic int64
## PhysicalActivity int64
## GenHealth int64
## SleepTime int64
## Asthma int64
## KidneyDisease int64
## SkinCancer int64
## HeartDisease01 int64
## dtype: object
Para construir el modelo, se requiere variables de tipo numérica.
Datos de entrenamiento al 80% de los datos y 20% los datos de validación. Semilla 1280
X_entrena, X_valida, Y_entrena, Y_valida = train_test_split(datos.drop(columns = "HeartDisease01"), datos['HeartDisease01'],train_size = 0.80, random_state = 1280)
Se crea un conjunto de datos de validación con 255836 registros y 37 variables.
X_entrena
## BMI Smoking AlcoholDrinking ... Asthma KidneyDisease SkinCancer
## 151258 31.28 2 2 ... 2 2 2
## 239066 30.85 1 1 ... 2 2 2
## 170512 32.78 1 2 ... 2 1 2
## 138101 27.12 1 2 ... 2 2 2
## 1625 21.95 1 2 ... 2 2 2
## ... ... ... ... ... ... ... ...
## 89827 34.33 1 2 ... 2 2 2
## 37579 25.70 1 2 ... 2 2 2
## 152432 25.37 1 1 ... 2 2 2
## 17713 34.06 2 2 ... 2 2 2
## 64259 27.44 2 2 ... 1 2 2
##
## [255836 rows x 17 columns]
Se crea un conjunto de datos de validación con 63959 registros y 37 variables.
X_valida
## BMI Smoking AlcoholDrinking ... Asthma KidneyDisease SkinCancer
## 111619 35.44 2 2 ... 2 2 2
## 53644 32.92 1 2 ... 2 2 2
## 143225 27.12 1 2 ... 2 2 2
## 191086 22.86 2 2 ... 1 2 2
## 210521 21.63 2 2 ... 2 2 2
## ... ... ... ... ... ... ... ...
## 175963 27.44 1 2 ... 2 2 2
## 280784 26.58 1 2 ... 2 2 2
## 266584 20.34 2 2 ... 2 2 2
## 175596 25.10 2 2 ... 1 2 2
## 232930 31.32 2 2 ... 2 2 2
##
## [63959 rows x 17 columns]
Se crea el modelo de árbol de clasificación con datos de entrenamiento con un valor inicial de 12 vecinos \(k=12\).
knn = KNeighborsClassifier(n_neighbors=12)
Se entrena el modelo precisamente con los datos de entrenamiento contenida en las variables independientes X_entrena y la variable dependiente Y_entrena que contiene la etiqueta HeartDisease01 de 0 No daño y 1 que si tiene daño en el corazón.
knn.fit(X_entrena, Y_entrena)
KNeighborsClassifier(n_neighbors=12)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
KNeighborsClassifier(n_neighbors=12)
Se construyen predicciones con los datos de validación. Se tarda mucho en hacer predicciones dado que son 255833 observaciones en datos de entrenamiento y 63959 observaciones en datos de validación.
predicciones = knn.predict(X_valida)
print(predicciones)
## [0 0 0 ... 0 0 0]
comparaciones = pd.DataFrame(X_valida)
comparaciones = comparaciones.assign(HeartDisease_Real = Y_valida)
comparaciones = comparaciones.assign(HeartDisease_Pred = predicciones.flatten().tolist())
print(comparaciones)
## BMI Smoking ... HeartDisease_Real HeartDisease_Pred
## 111619 35.44 2 ... 0 0
## 53644 32.92 1 ... 0 0
## 143225 27.12 1 ... 0 0
## 191086 22.86 2 ... 0 0
## 210521 21.63 2 ... 0 0
## ... ... ... ... ... ...
## 175963 27.44 1 ... 1 0
## 280784 26.58 1 ... 0 0
## 266584 20.34 2 ... 0 0
## 175596 25.10 2 ... 0 0
## 232930 31.32 2 ... 0 0
##
## [63959 rows x 19 columns]
Se evalúa el modelo con la matriz de confusión
print(confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
## [[58348 128]
## [ 5396 87]]
matriz = confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred'])
print(classification_report(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
## precision recall f1-score support
##
## 0 0.92 1.00 0.95 58476
## 1 0.40 0.02 0.03 5483
##
## accuracy 0.91 63959
## macro avg 0.66 0.51 0.49 63959
## weighted avg 0.87 0.91 0.88 63959
accuracy = accuracy_score(
y_true = comparaciones['HeartDisease_Real'],
y_pred = comparaciones['HeartDisease_Pred'],
normalize = True
)
print(f" El valor de exactitud = accuracy es de: {100 * accuracy} %")
## El valor de exactitud = accuracy es de: 91.36321706092966 %
Se crea un registro de una persona con ciertas condiciones de salud a partir de un diccionario.
# Se crea un diccionario
# Mismo que R
# BMI <- 38
# Smoking <- 1 # 'Yes'
# AlcoholDrinking = 1 # 'Yes'
# Stroke <- 1 # 'Yes'
# PhysicalHealth <- 2
# MentalHealth <- 5
# DiffWalking <- 1 # 'Yes'
# Sex = 2 # 'Male
# AgeCategory = 11 # '70-74'
# Race = 2 # 'Black'
# Diabetic <- 1 # 'Yes'
# PhysicalActivity = 2 # 'No'
# GenHealth = 1 # "Fair"
# SleepTime = 12
# Asthma = 1 # 'Yes'
# KidneyDisease = 1 # 'Yes'
# SkinCancer = 2 # 'No'
registro = {'BMI': 38, 'Smoking' : 1, 'AlcoholDrinking' : 1, 'Stroke' : 1,
'PhysicalHealth': 2, 'MentalHealth': 5,
'DiffWalking': 1, 'Sex': 2, 'AgeCategory': 11,
'Race' : 2, 'Diabetic' : 1,
'PhysicalActivity' : 2, 'GenHealth' : 1,
'SleepTime' : 12,
'Asthma' : 1, 'KidneyDisease':1, 'SkinCancer': 2}
persona = pd.DataFrame()
persona = persona.append(registro, ignore_index=True)
## <string>:1: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
persona
## BMI Smoking AlcoholDrinking ... Asthma KidneyDisease SkinCancer
## 0 38 1 1 ... 1 1 2
##
## [1 rows x 17 columns]
Se hace la predicción en términos de clasificación de la persona con estos valores para saber si tiene o no daño en el corazón:
prediccion = knn.predict(persona)
print(prediccion)
## [0]
La predicción en términos de clasificación de la persona con las características proporcionadas es 0 NO que está enfermo o que NO tiene daño del corazón.
¿que predicciones se generaron en los otros modelos?. Habiendo construido modelos de basados en los algoritmos de regresión logística binaria y árbol de regresión, en estos las predicciones fueron de que ‘YES’ si tiene daño al corazón.
De entrada me parece un modelo mucho más sencillo que los anteriores algoritmos de clasificación, sin embargo, la premisa de la que parte y la manera en la que lleva a cabo la construcción de la estructura de sus operaciones son bastante prácticas y funcionales. Su métodología que consiste en buscar la información que necesita en lo que ya tiene, es decir, en la búsqueda de las menores distancias del elemento nuevo con respecto al conjunto de datos que posee, es muy interesante y ciertamente tiene potencial para desenvolverse en entornos donde exista una precisión de los datos, o en otras palabras, donde el entorno sea generalmente conocido.
El contexto de los datos corresponden al estado de salud de cierto número de personas, a los cuales se les aplicó el modelo de Vecinos Cercanos (KNN), el cual es un algoritmo de clasificación binaria, en este caso representada por las siguientes dos opciones o grupos a los que se puede pertenecer, si padece o no problemas del corazón.
Una de las desventajas de este algoritmo es su aplicación en entornos donde haya una imprecisión de los datos, además resulta que tiene un elevado nivel de latencia en lo que respecta a la construcción del modelo markdown. A juzgar por su comportamiento, a mayor cantidad de datos, mayor será su tardanza. Para estos casos sería necesario hacer uso de muestras de la cantidad de los datos de entrenamiento y validación respectivamente.
Por otro lado, pasemos a lo que son los datos que ha arrojado el modelo con la información que se le ha proporcionado. La semilla que se le propinó fue 1280. Los datos de entrenamiento están a un 80% y los datos de validación a un 20%, tal como se han venido manejando.
En este caso y con los datos especificos que se le han suministrado al modelo, el valor de exactitud = accuracy es de: 91.36321706092966 %. Es decir, que su nivel de predicción le permite acertar en el 91.36% de cada 100 casos. Naturalmente es un valor que permite tomar como valido el modelo siguiendo con la medida (mayor al 70%) que se ha establecido por nosotros mismos. Comparando este valor con el que se obtenido haciendo uso del lenguaje R, que es de 0.9132 %, este posee una ligera diferencia que es mayor en decimales. Pero en términos generales es la misma.