1 Ojetivo

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.

2 Descripció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%..

3 Fundamento teórico

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.

4 Desarrollo

4.1 Cargar librerías

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

4.2 Cargar los datos

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]

4.3 Exploración de datos

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

4.3.1 Visualización de datos

4.3.1.1 ¿Cuántos casos hay de cada clase?

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()

4.4 Transformar datos

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)

4.4.1 Las variables de interés

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        int32
## dtype: object

Para construir el modelo, se requiere variables de tipo numérica.

4.5 Datos de entrenamiento y validación

Datos de entrenamiento al 80% de los datos y 20% los datos de validación. Semilla 2022

X_entrena, X_valida, Y_entrena, Y_valida = train_test_split(datos.drop(columns = "HeartDisease01"), datos['HeartDisease01'],train_size = 0.80,  random_state = 2022)

4.5.1 Datos de entrenamiento

Se crea un conjunto de datos de validación con 255836 registros y 37 variables.

X_entrena
##           BMI  Smoking  AlcoholDrinking  ...  Asthma  KidneyDisease  SkinCancer
## 308965  27.12        2                2  ...       2              2           2
## 184879  22.89        1                2  ...       2              2           1
## 415     37.37        1                2  ...       2              1           2
## 194488  31.25        2                2  ...       2              2           1
## 43492   26.58        2                2  ...       2              2           2
## ...       ...      ...              ...  ...     ...            ...         ...
## 202992  23.81        1                2  ...       2              2           2
## 262129  28.41        2                2  ...       2              2           2
## 103024  25.68        1                2  ...       2              2           2
## 147629  28.32        2                2  ...       2              2           2
## 263388  26.57        1                1  ...       2              2           2
## 
## [255836 rows x 17 columns]

4.5.2 Datos de validación

Se crea un conjunto de datos de validación con 63959 registros y 37 variables.

X_valida
##           BMI  Smoking  AlcoholDrinking  ...  Asthma  KidneyDisease  SkinCancer
## 56493   23.05        2                2  ...       2              2           2
## 102187  15.77        1                2  ...       2              2           2
## 29779   28.70        1                2  ...       2              2           2
## 111891  29.86        2                2  ...       2              2           2
## 295435  19.94        1                2  ...       2              2           2
## ...       ...      ...              ...  ...     ...            ...         ...
## 271242  39.53        1                2  ...       1              2           2
## 68454   21.29        1                2  ...       2              1           2
## 59987   35.43        1                2  ...       2              2           2
## 159117  38.79        1                1  ...       2              2           2
## 63454   25.11        1                2  ...       1              2           2
## 
## [63959 rows x 17 columns]

4.6 Modelos Supervisados de vecinos mas cercanos KNN

4.6.1 Creación del modelo

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)

4.6.1.1 Entrenando al modelo

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)

4.7 Prediccions

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]

4.7.1 Tabla comparativa

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
## 56493   23.05        2  ...                  0                  0
## 102187  15.77        1  ...                  0                  0
## 29779   28.70        1  ...                  0                  0
## 111891  29.86        2  ...                  0                  0
## 295435  19.94        1  ...                  0                  0
## ...       ...      ...  ...                ...                ...
## 271242  39.53        1  ...                  1                  0
## 68454   21.29        1  ...                  0                  0
## 59987   35.43        1  ...                  0                  0
## 159117  38.79        1  ...                  0                  0
## 63454   25.11        1  ...                  0                  0
## 
## [63959 rows x 19 columns]

4.7.2 Evaluación del modelo

Se evalúa el modelo con la matriz de confusión

4.7.2.1 Matriz de confusión

print(confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
## [[58492   109]
##  [ 5262    96]]
matriz = confusion_matrix(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred'])

4.7.2.2 ¿A cuantos le atina el modelo?

print(classification_report(comparaciones['HeartDisease_Real'], comparaciones['HeartDisease_Pred']))
##               precision    recall  f1-score   support
## 
##            0       0.92      1.00      0.96     58601
##            1       0.47      0.02      0.03      5358
## 
##     accuracy                           0.92     63959
##    macro avg       0.69      0.51      0.50     63959
## weighted avg       0.88      0.92      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.60243280851795 %

4.8 Prediccions con un registro nuevo

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.

5 Interpretación

Se tarda mucho tiempo en construir el documento markdown en Python.

6 Bibliografía